Стабилизировать денежные ответы и прибыль 1С-ассистента
This commit is contained in:
parent
b18613961c
commit
473cdc3a9b
|
|
@ -0,0 +1,266 @@
|
||||||
|
{
|
||||||
|
"schema_version": "domain_truth_harness_spec_v1",
|
||||||
|
"scenario_id": "agent_cashflow_profit_directness_20260523",
|
||||||
|
"domain": "autonomy_business_answer_contract",
|
||||||
|
"title": "AGENT | Cashflow vs profit directness pack",
|
||||||
|
"description": "Targeted AGENT replay for cashflow-vs-profit directness: colloquial earnings questions must produce a compact money answer, while broad overview remains available only when explicitly requested.",
|
||||||
|
"bindings": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step_id": "step_01_direct_money_earned_explicit_shape",
|
||||||
|
"title": "Direct money earned question asks for compact cashflow shape",
|
||||||
|
"question": "Сколько денег Альтернатива заработала за 2020 год? Ответь коротко: получили, заплатили, денежное нетто, это прибыль или нет.",
|
||||||
|
"allowed_reply_types": [
|
||||||
|
"partial_coverage",
|
||||||
|
"factual_with_explanation",
|
||||||
|
"factual"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"47[\\s.]*628[\\s.]*853",
|
||||||
|
"43[\\s.]*763[\\s.]*351",
|
||||||
|
"3[\\s.]*865[\\s.]*501"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"Учтено строк",
|
||||||
|
"Первая найденная дата",
|
||||||
|
"runtime_",
|
||||||
|
"planner_",
|
||||||
|
"query_movements",
|
||||||
|
"primitive",
|
||||||
|
"7\\s*136\\s*815|7136815",
|
||||||
|
"Комитет государственных услуг"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": [
|
||||||
|
"cashflow_vs_profit",
|
||||||
|
"direct_answer",
|
||||||
|
"colloquial_earnings"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "step_02_colloquial_money_earned",
|
||||||
|
"title": "Colloquial earned wording stays cashflow, not broad overview",
|
||||||
|
"question": "скока денег альтернатива заработала за 20 год?",
|
||||||
|
"allowed_reply_types": [
|
||||||
|
"partial_coverage",
|
||||||
|
"factual_with_explanation",
|
||||||
|
"factual"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"47[\\s.]*628[\\s.]*853",
|
||||||
|
"43[\\s.]*763[\\s.]*351",
|
||||||
|
"3[\\s.]*865[\\s.]*501"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"Учтено строк",
|
||||||
|
"Первая найденная дата",
|
||||||
|
"runtime_",
|
||||||
|
"planner_",
|
||||||
|
"query_movements",
|
||||||
|
"primitive",
|
||||||
|
"7\\s*136\\s*815|7136815",
|
||||||
|
"Комитет государственных услуг",
|
||||||
|
"Что проверить дальше"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": [
|
||||||
|
"cashflow_vs_profit",
|
||||||
|
"slang_wording",
|
||||||
|
"no_overview_substitution"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "step_03_profit_followup_after_cashflow",
|
||||||
|
"title": "Clean profit follow-up keeps distinction",
|
||||||
|
"question": "а это чистая прибыль?",
|
||||||
|
"allowed_reply_types": [
|
||||||
|
"partial_coverage",
|
||||||
|
"factual_with_explanation",
|
||||||
|
"factual"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"7\\s*136\\s*815|7136815",
|
||||||
|
"90|91|99"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"Учтено строк",
|
||||||
|
"Первая найденная дата",
|
||||||
|
"runtime_",
|
||||||
|
"planner_",
|
||||||
|
"query_movements",
|
||||||
|
"primitive"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": [
|
||||||
|
"profit_vs_cashflow",
|
||||||
|
"followup_context"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "step_04_money_plus_or_minus_followup",
|
||||||
|
"title": "Money plus/minus follow-up returns cashflow net",
|
||||||
|
"question": "а по деньгам тогда плюс или минус?",
|
||||||
|
"allowed_reply_types": [
|
||||||
|
"partial_coverage",
|
||||||
|
"factual_with_explanation",
|
||||||
|
"factual"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"\\+",
|
||||||
|
"47[\\s.]*628[\\s.]*853",
|
||||||
|
"43[\\s.]*763[\\s.]*351",
|
||||||
|
"3[\\s.]*865[\\s.]*501"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"Учтено строк",
|
||||||
|
"Первая найденная дата",
|
||||||
|
"runtime_",
|
||||||
|
"planner_",
|
||||||
|
"query_movements",
|
||||||
|
"primitive",
|
||||||
|
"7\\s*136\\s*815|7136815"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": [
|
||||||
|
"cashflow_polarity",
|
||||||
|
"followup_context",
|
||||||
|
"no_profit_substitution"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "step_05_explicit_business_overview_allowed",
|
||||||
|
"title": "Explicit adult overview may include business structure and next steps",
|
||||||
|
"question": "Теперь дай взрослый обзор за 2020 по компании: входящие, исходящие, нетто, топы, но банк в топах отдельно объясни как финансовый поток.",
|
||||||
|
"allowed_reply_types": [
|
||||||
|
"partial_coverage",
|
||||||
|
"factual_with_explanation",
|
||||||
|
"factual"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"47[\\s.]*628[\\s.]*853",
|
||||||
|
"43[\\s.]*763[\\s.]*351",
|
||||||
|
"3[\\s.]*865[\\s.]*501",
|
||||||
|
"12[\\s.]*792[\\s.]*194",
|
||||||
|
"12[\\s.]*093[\\s.]*465",
|
||||||
|
"9[\\s.]*612[\\s.]*904"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"Учтено строк",
|
||||||
|
"Первая найденная дата",
|
||||||
|
"runtime_",
|
||||||
|
"planner_",
|
||||||
|
"query_movements",
|
||||||
|
"primitive"
|
||||||
|
],
|
||||||
|
"criticality": "high",
|
||||||
|
"semantic_tags": [
|
||||||
|
"business_overview",
|
||||||
|
"bank_flow_boundary",
|
||||||
|
"next_steps"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "step_06_return_to_short_money_after_overview",
|
||||||
|
"title": "Short money follow-up after overview keeps 2020 context",
|
||||||
|
"question": "а если коротко, сколько заработали деньгами без топов?",
|
||||||
|
"allowed_reply_types": [
|
||||||
|
"partial_coverage",
|
||||||
|
"factual_with_explanation",
|
||||||
|
"factual"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"2020",
|
||||||
|
"47[\\s.]*628[\\s.]*853",
|
||||||
|
"43[\\s.]*763[\\s.]*351",
|
||||||
|
"3[\\s.]*865[\\s.]*501"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"Учтено строк",
|
||||||
|
"Первая найденная дата",
|
||||||
|
"runtime_",
|
||||||
|
"planner_",
|
||||||
|
"query_movements",
|
||||||
|
"primitive",
|
||||||
|
"285[\\s.]*819[\\s.]*547",
|
||||||
|
"147[\\s.]*855[\\s.]*827",
|
||||||
|
"Комитет государственных услуг",
|
||||||
|
"Что проверить дальше"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": [
|
||||||
|
"compact_after_overview",
|
||||||
|
"temporal_carryover",
|
||||||
|
"no_all_time_leak"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "step_07_plain_money_in_out_net",
|
||||||
|
"title": "Plain money request suppresses broad overview",
|
||||||
|
"question": "не обзор, просто деньги: пришло, ушло, нетто за 2020",
|
||||||
|
"allowed_reply_types": [
|
||||||
|
"partial_coverage",
|
||||||
|
"factual_with_explanation",
|
||||||
|
"factual"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"47[\\s.]*628[\\s.]*853",
|
||||||
|
"43[\\s.]*763[\\s.]*351",
|
||||||
|
"3[\\s.]*865[\\s.]*501"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"Учтено строк",
|
||||||
|
"Первая найденная дата",
|
||||||
|
"runtime_",
|
||||||
|
"planner_",
|
||||||
|
"query_movements",
|
||||||
|
"primitive",
|
||||||
|
"Комитет государственных услуг",
|
||||||
|
"12[\\s.]*093[\\s.]*465",
|
||||||
|
"12[\\s.]*792[\\s.]*194",
|
||||||
|
"Что проверить дальше"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": [
|
||||||
|
"direct_money_only",
|
||||||
|
"ranking_suppression"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "step_08_direct_clean_profit_2020",
|
||||||
|
"title": "Explicit clean profit goes to accounting result, not cashflow",
|
||||||
|
"question": "какая чистая прибыль по Альтернативе за 2020?",
|
||||||
|
"allowed_reply_types": [
|
||||||
|
"partial_coverage",
|
||||||
|
"factual_with_explanation",
|
||||||
|
"factual"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"7\\s*136\\s*815|7136815",
|
||||||
|
"90|91|99"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"Учтено строк",
|
||||||
|
"Первая найденная дата",
|
||||||
|
"runtime_",
|
||||||
|
"planner_",
|
||||||
|
"query_movements",
|
||||||
|
"primitive",
|
||||||
|
"получили\\s+47[\\s.]*628[\\s.]*853",
|
||||||
|
"денежное\\s+нетто\\s+3[\\s.]*865[\\s.]*501"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": [
|
||||||
|
"clean_profit",
|
||||||
|
"profit_route",
|
||||||
|
"no_cashflow_substitution"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"acceptance": {
|
||||||
|
"min_score": 80,
|
||||||
|
"max_unresolved_p0": 0,
|
||||||
|
"require_all_critical_steps_pass": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,11 @@ function sessionOrganizationName(sessionOrganizationScope, toNonEmptyString) {
|
||||||
const scope = toRecordObject(sessionOrganizationScope);
|
const scope = toRecordObject(sessionOrganizationScope);
|
||||||
return toNonEmptyString(scope?.selectedOrganization) ?? toNonEmptyString(scope?.activeOrganization);
|
return toNonEmptyString(scope?.selectedOrganization) ?? toNonEmptyString(scope?.activeOrganization);
|
||||||
}
|
}
|
||||||
|
function sessionKnownOrganizations(sessionOrganizationScope) {
|
||||||
|
const scope = toRecordObject(sessionOrganizationScope);
|
||||||
|
const knownOrganizations = scope?.knownOrganizations;
|
||||||
|
return Array.isArray(knownOrganizations) ? knownOrganizations : [];
|
||||||
|
}
|
||||||
function predecomposeOrganizationName(predecomposeContract, toNonEmptyString) {
|
function predecomposeOrganizationName(predecomposeContract, toNonEmptyString) {
|
||||||
const entities = toRecordObject(predecomposeContract?.entities);
|
const entities = toRecordObject(predecomposeContract?.entities);
|
||||||
return (toNonEmptyString(entities?.organization) ??
|
return (toNonEmptyString(entities?.organization) ??
|
||||||
|
|
@ -223,7 +228,8 @@ 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: discoveryFollowupContext,
|
||||||
|
knownOrganizations: sessionKnownOrganizations(input.sessionOrganizationScope ?? null)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,12 @@ function hasBusinessOverviewDirectMoneyAnswerHint(input) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const text = input.rawUtterance;
|
const text = input.rawUtterance;
|
||||||
|
if (/(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)[\s\S]{0,80}(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u043d\u0435\u0442\u0442\u043e|\u043f\u0440\u0438\u0448\p{L}*|\u0443\u0448\p{L}*|\u043f\u043e\u043b\u0443\u0447\p{L}*|\u0437\u0430\u043f\u043b\u0430\u0442\p{L}*)|(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u043d\u0435\u0442\u0442\u043e)[\s\S]{0,80}(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)/iu.test(text)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (/(?:\u043d\u0435\s+\u043e\u0431\u0437\u043e\u0440|\u043f\u0440\u043e\u0441\u0442\u043e\s+\u0434\u0435\u043d\p{L}+|\u0431\u0435\u0437\s+\u0442\u043e\u043f(?:\u043e\u0432|\u0430)?\b|\u0434\u0435\u043d\p{L}{0,20}\s+\u043d\u0435\u0442\u0442\u043e|\u043f\u0440\u0438\u0448\u043b\p{L}*[\s\S]{0,80}\u0443\u0448\u043b\p{L}*[\s\S]{0,80}\u043d\u0435\u0442\u0442\u043e|\u0432\u0445\u043e\u0434\u044f\u0449\p{L}*[\s\S]{0,80}\u0438\u0441\u0445\u043e\u0434\u044f\u0449\p{L}*[\s\S]{0,80}\u043d\u0435\u0442\u0442\u043e)/iu.test(text)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return /(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|how\s+much)[\s\S]{0,120}(?:\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u0432\u044b\u0440\u0443\u0447|\u0434\u0435\u043d\p{L}*|\u043f\u043e\u043b\u0443\u0447|\u043f\u043e\u0441\u0442\u0443\u043f\p{L}*)|(?:\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u0432\u044b\u0440\u0443\u0447)[\s\S]{0,120}(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|\u0432\u0441\u0435\u0433\u043e|\u0432\u043e\u043e\u0431\u0449\u0435|(?:19|20)\d{2}|all\s+time)|(?:\u043a\u0430\u043a\u043e\u0439|\u043a\u0430\u043a\u0430\u044f|\u043a\u0430\u043a\u0438\u0435|which|what)[\s\S]{0,80}(?:\u0441\u0430\u043c\p{L}*|top|best|most)[\s\S]{0,80}(?:\u0434\u043e\u0445\u043e\u0434\u043d|\u0432\u044b\u0440\u0443\u0447|\u043e\u0431\u043e\u0440\u043e\u0442|revenue|turnover)[\s\S]{0,40}(?:\u0433\u043e\u0434|year)/iu.test(text);
|
return /(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|how\s+much)[\s\S]{0,120}(?:\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u0432\u044b\u0440\u0443\u0447|\u0434\u0435\u043d\p{L}*|\u043f\u043e\u043b\u0443\u0447|\u043f\u043e\u0441\u0442\u0443\u043f\p{L}*)|(?:\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u0432\u044b\u0440\u0443\u0447)[\s\S]{0,120}(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|\u0432\u0441\u0435\u0433\u043e|\u0432\u043e\u043e\u0431\u0449\u0435|(?:19|20)\d{2}|all\s+time)|(?:\u043a\u0430\u043a\u043e\u0439|\u043a\u0430\u043a\u0430\u044f|\u043a\u0430\u043a\u0438\u0435|which|what)[\s\S]{0,80}(?:\u0441\u0430\u043c\p{L}*|top|best|most)[\s\S]{0,80}(?:\u0434\u043e\u0445\u043e\u0434\u043d|\u0432\u044b\u0440\u0443\u0447|\u043e\u0431\u043e\u0440\u043e\u0442|revenue|turnover)[\s\S]{0,40}(?:\u0433\u043e\u0434|year)/iu.test(text);
|
||||||
}
|
}
|
||||||
function timeScopeNeedFor(input) {
|
function timeScopeNeedFor(input) {
|
||||||
|
|
@ -225,6 +231,13 @@ function rankingNeedFromRawUtterance(value) {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
function suppressRankingNeedFromRawUtterance(value) {
|
||||||
|
const text = lower(value);
|
||||||
|
if (!text) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return /(?:\u0431\u0435\u0437\s+\u0442\u043e\u043f(?:\u043e\u0432|\u0430)?\b|\u043d\u0435\s+\u0442\u043e\u043f\b|\u043d\u0435\s+\u043e\u0431\u0437\u043e\u0440\b|\u043f\u0440\u043e\u0441\u0442\u043e\s+\u0434\u0435\u043d\p{L}+|\u0438\u0441\u043a\u043b\u044e\u0447\w*\s+\u0442\u043e\u043f|\u0431\u0435\u0437\s+\u0440\u0435\u0439\u0442\u0438\u043d\u0433\u0430\b|без\s+топ(?:ов|а)?\b|не\s+топ\b|не\s+обзор\b|просто\s+ден\p{L}+|исключ\S*\s+топ|без\s+рейтинга\b)/iu.test(text);
|
||||||
|
}
|
||||||
function proofExpectationFor(input) {
|
function proofExpectationFor(input) {
|
||||||
if (input.clarificationGaps.length > 0) {
|
if (input.clarificationGaps.length > 0) {
|
||||||
return "clarification_required";
|
return "clarification_required";
|
||||||
|
|
@ -383,6 +396,7 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
|
||||||
const action = lower(turnMeaning?.asked_action_family);
|
const action = lower(turnMeaning?.asked_action_family);
|
||||||
const unsupported = lower(turnMeaning?.unsupported_but_understood_family);
|
const unsupported = lower(turnMeaning?.unsupported_but_understood_family);
|
||||||
const rawUtterance = lower(input.rawUtterance);
|
const rawUtterance = lower(input.rawUtterance);
|
||||||
|
const rawQuestionSignal = lower([input.rawUtterance, turnMeaning?.raw_message, turnMeaning?.effective_message].join(" "));
|
||||||
const aggregationAxis = lower(turnMeaning?.asked_aggregation_axis);
|
const aggregationAxis = lower(turnMeaning?.asked_aggregation_axis);
|
||||||
const seededRankingNeed = toNonEmptyString(turnMeaning?.seeded_ranking_need);
|
const seededRankingNeed = toNonEmptyString(turnMeaning?.seeded_ranking_need);
|
||||||
const explicitDateScope = toNonEmptyString(turnMeaning?.explicit_date_scope);
|
const explicitDateScope = toNonEmptyString(turnMeaning?.explicit_date_scope);
|
||||||
|
|
@ -400,15 +414,18 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
|
||||||
});
|
});
|
||||||
const aggregationNeed = aggregationNeedFor(aggregationAxis);
|
const aggregationNeed = aggregationNeedFor(aggregationAxis);
|
||||||
const comparisonNeed = comparisonNeedFor(action);
|
const comparisonNeed = comparisonNeedFor(action);
|
||||||
const rankingNeed = rankingNeedFromRawUtterance(rawUtterance) ?? seededRankingNeed;
|
|
||||||
const allTimeScopeHint = hasAllTimeScopeHint(rawUtterance);
|
const allTimeScopeHint = hasAllTimeScopeHint(rawUtterance);
|
||||||
const subjectScopedBidirectionalAllTime = businessFactFamily === "value_flow" &&
|
const subjectScopedBidirectionalAllTime = businessFactFamily === "value_flow" &&
|
||||||
comparisonNeed === "incoming_vs_outgoing" &&
|
comparisonNeed === "incoming_vs_outgoing" &&
|
||||||
subjectCandidates.length > 0 &&
|
subjectCandidates.length > 0 &&
|
||||||
!explicitDateScope;
|
!explicitDateScope;
|
||||||
|
const suppressRankingNeed = suppressRankingNeedFromRawUtterance(rawQuestionSignal) ||
|
||||||
|
/(?:\u0431\u0435\u0437\s+\u0442\u043e\u043f(?:\u043e\u0432|\u0430)?|\u043d\u0435\s+\u0442\u043e\u043f|\u043d\u0435\s+\u043e\u0431\u0437\u043e\u0440|\u043f\u0440\u043e\u0441\u0442\u043e\s+\u0434\u0435\u043d\p{L}+|\u0438\u0441\u043a\u043b\u044e\u0447\w*\s+\u0442\u043e\u043f|\u0431\u0435\u0437\s+\u0440\u0435\u0439\u0442\u0438\u043d\u0433\u0430)/iu.test(rawQuestionSignal);
|
||||||
|
const rawRankingNeed = rankingNeedFromRawUtterance(rawQuestionSignal);
|
||||||
|
const rankingNeed = suppressRankingNeed ? null : rawRankingNeed ?? seededRankingNeed;
|
||||||
const directBusinessOverviewMoneyAnswerHint = hasBusinessOverviewDirectMoneyAnswerHint({
|
const directBusinessOverviewMoneyAnswerHint = hasBusinessOverviewDirectMoneyAnswerHint({
|
||||||
family: businessFactFamily,
|
family: businessFactFamily,
|
||||||
rawUtterance,
|
rawUtterance: rawQuestionSignal,
|
||||||
rankingNeed
|
rankingNeed
|
||||||
});
|
});
|
||||||
const oneSidedOpenScopeTotalHint = hasOpenScopeOneSidedValueTotalHintUtf8Safe(rawUtterance, action);
|
const oneSidedOpenScopeTotalHint = hasOpenScopeOneSidedValueTotalHintUtf8Safe(rawUtterance, action);
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,30 @@ function requestsFinancialCounterpartyBoundary(turnMeaning, graph) {
|
||||||
return (/(?:банк|сбербанк|финанс|кредит|депозит)/iu.test(text) &&
|
return (/(?:банк|сбербанк|финанс|кредит|депозит)/iu.test(text) &&
|
||||||
/(?:клиент|поставщик|выручк|топ|обычн|роль|поток)/iu.test(text));
|
/(?:клиент|поставщик|выручк|топ|обычн|роль|поток)/iu.test(text));
|
||||||
}
|
}
|
||||||
|
function requestsCompactCashflowAnswer(turnMeaning, graph) {
|
||||||
|
const text = normalizeQuestionText([
|
||||||
|
turnMeaning?.raw_message,
|
||||||
|
turnMeaning?.effective_message,
|
||||||
|
graph?.source_message,
|
||||||
|
graph?.question
|
||||||
|
].join(" "));
|
||||||
|
if (!text) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (/(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)[\s\S]{0,80}(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u043d\u0435\u0442\u0442\u043e|\u043f\u0440\u0438\u0448\p{L}*|\u0443\u0448\p{L}*|\u043f\u043e\u043b\u0443\u0447\p{L}*|\u0437\u0430\u043f\u043b\u0430\u0442\p{L}*)|(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u043d\u0435\u0442\u0442\u043e)[\s\S]{0,80}(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)/iu.test(text)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return /(?:\u043d\u0435\s+\u043e\u0431\u0437\u043e\u0440|\u043f\u0440\u043e\u0441\u0442\u043e\s+\u0434\u0435\u043d\p{L}+|\u0431\u0435\u0437\s+\u0442\u043e\u043f(?:\u043e\u0432|\u0430)?\b|\u0434\u0435\u043d\p{L}{0,20}\s+\u043d\u0435\u0442\u0442\u043e|\u043f\u043e\u043b\u0443\u0447\p{L}*[\s\S]{0,80}\u0437\u0430\u043f\u043b\u0430\u0442\p{L}*[\s\S]{0,80}\u043d\u0435\u0442\u0442\u043e|\u043f\u0440\u0438\u0448\u043b\p{L}*[\s\S]{0,80}\u0443\u0448\u043b\p{L}*[\s\S]{0,80}\u043d\u0435\u0442\u0442\u043e|(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*)[\s\S]{0,120}(?:\u0434\u0435\u043d\p{L}*|\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u043f\u043e\u043b\u0443\u0447|\u043f\u0440\u0438\u0448\u043b))/iu.test(text);
|
||||||
|
}
|
||||||
|
function requestsCashflowPolarityAnswer(turnMeaning, graph) {
|
||||||
|
const text = normalizeQuestionText([
|
||||||
|
turnMeaning?.raw_message,
|
||||||
|
turnMeaning?.effective_message,
|
||||||
|
graph?.source_message,
|
||||||
|
graph?.question
|
||||||
|
].join(" "));
|
||||||
|
return /(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)[\s\S]{0,80}(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441)|(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441)[\s\S]{0,80}(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)/iu.test(text);
|
||||||
|
}
|
||||||
function toStringList(value) {
|
function toStringList(value) {
|
||||||
if (!Array.isArray(value)) {
|
if (!Array.isArray(value)) {
|
||||||
return [];
|
return [];
|
||||||
|
|
@ -595,6 +619,18 @@ function compactComparable(value) {
|
||||||
.replace(/\s+/g, " ")
|
.replace(/\s+/g, " ")
|
||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
|
function businessOverviewOrganizationScopeLabel(value) {
|
||||||
|
const text = toNonEmptyString(value);
|
||||||
|
if (!text) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const comparable = compactComparable(text);
|
||||||
|
if (/^(?:с|без)\s+разбивк/.test(comparable) ||
|
||||||
|
/\b(?:входящ|исходящ|нетто|топ|контрагент|платеж|поступлен)\b/.test(comparable)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
function businessOverviewSeparateSubjectLabel(graph, turnMeaning, organizationScope) {
|
function businessOverviewSeparateSubjectLabel(graph, turnMeaning, organizationScope) {
|
||||||
const candidates = uniqueStrings([
|
const candidates = uniqueStrings([
|
||||||
...toStringList(turnMeaning?.business_overview_separate_entity_candidates),
|
...toStringList(turnMeaning?.business_overview_separate_entity_candidates),
|
||||||
|
|
@ -686,7 +722,7 @@ function buildCompactBusinessOverviewReply(entryPoint, draft) {
|
||||||
const netDirection = overview.net_direction === "net_outgoing" ? "операционное нетто в минус" : "расчетное операционное нетто";
|
const netDirection = overview.net_direction === "net_outgoing" ? "операционное нетто в минус" : "расчетное операционное нетто";
|
||||||
const period = businessOverviewPeriodText(overview);
|
const period = businessOverviewPeriodText(overview);
|
||||||
const limitLine = businessOverviewCoverageLimitLine(overview);
|
const limitLine = businessOverviewCoverageLimitLine(overview);
|
||||||
const organizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
|
const organizationScope = businessOverviewOrganizationScopeLabel(turnMeaning?.explicit_organization_scope);
|
||||||
const separateSubject = businessOverviewSeparateSubjectLabel(graph, turnMeaning, organizationScope);
|
const separateSubject = businessOverviewSeparateSubjectLabel(graph, turnMeaning, organizationScope);
|
||||||
const previousCounterpartySummary = buildPreviousCounterpartyValueFlowSummary(toRecordObject(turnMeaning?.previous_counterparty_value_flow_bundle), separateSubject, toRecordObject(turnMeaning?.previous_counterparty_document_bundle));
|
const previousCounterpartySummary = buildPreviousCounterpartyValueFlowSummary(toRecordObject(turnMeaning?.previous_counterparty_value_flow_bundle), separateSubject, toRecordObject(turnMeaning?.previous_counterparty_document_bundle));
|
||||||
const organizationPrefix = organizationScope ? `по компании ${organizationScope} ` : "";
|
const organizationPrefix = organizationScope ? `по компании ${organizationScope} ` : "";
|
||||||
|
|
@ -730,6 +766,22 @@ 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 cashflowPolarityRequested = compactCashflowRequested && requestsCashflowPolarityAnswer(turnMeaning, graph);
|
||||||
|
if (compactCashflowRequested && !rankingNeed && (incomingAmount || outgoingAmount || netAmount)) {
|
||||||
|
const netDisplay = sentenceAmount(netAmount) ?? netAmount ?? "0 \u0440\u0443\u0431.";
|
||||||
|
const signedNetDisplay = cashflowPolarityRequested && netDisplay && !String(netDisplay).trim().startsWith("-")
|
||||||
|
? `+${netDisplay}`
|
||||||
|
: netDisplay;
|
||||||
|
const polarityLead = cashflowPolarityRequested
|
||||||
|
? `\u041a\u043e\u0440\u043e\u0442\u043a\u043e: \u043f\u043e \u0434\u0435\u043d\u044c\u0433\u0430\u043c \u043f\u043b\u044e\u0441. `
|
||||||
|
: "\u041a\u043e\u0440\u043e\u0442\u043a\u043e: ";
|
||||||
|
lines.push(`${polarityLead}${organizationPrefix}${period} \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 ${incomingAmount ?? "0 \u0440\u0443\u0431."}; \u0437\u0430\u043f\u043b\u0430\u0442\u0438\u043b\u0438/\u0441\u043f\u0438\u0441\u0430\u043b\u0438 ${outgoingAmount ?? "0 \u0440\u0443\u0431."}; \u0434\u0435\u043d\u0435\u0436\u043d\u043e\u0435 \u043d\u0435\u0442\u0442\u043e ${signedNetDisplay}.`);
|
||||||
|
lines.push(cashflowPolarityRequested
|
||||||
|
? "\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 \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);
|
||||||
|
}
|
||||||
if (profitMarginBoundary) {
|
if (profitMarginBoundary) {
|
||||||
const accountingFinancialResult = toRecordObject(overview.accounting_financial_result);
|
const accountingFinancialResult = toRecordObject(overview.accounting_financial_result);
|
||||||
if (accountingFinancialResult) {
|
if (accountingFinancialResult) {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.ASSISTANT_MCP_DISCOVERY_TURN_INPUT_SCHEMA_VERSION = void 0;
|
exports.ASSISTANT_MCP_DISCOVERY_TURN_INPUT_SCHEMA_VERSION = void 0;
|
||||||
exports.buildAssistantMcpDiscoveryTurnInput = buildAssistantMcpDiscoveryTurnInput;
|
exports.buildAssistantMcpDiscoveryTurnInput = buildAssistantMcpDiscoveryTurnInput;
|
||||||
const assistantMcpDiscoveryDataNeedGraph_1 = require("./assistantMcpDiscoveryDataNeedGraph");
|
const assistantMcpDiscoveryDataNeedGraph_1 = require("./assistantMcpDiscoveryDataNeedGraph");
|
||||||
|
const assistantOrganizationMatcher_1 = require("./assistantOrganizationMatcher");
|
||||||
const addressTextRepair_1 = require("./addressTextRepair");
|
const addressTextRepair_1 = require("./addressTextRepair");
|
||||||
exports.ASSISTANT_MCP_DISCOVERY_TURN_INPUT_SCHEMA_VERSION = "assistant_mcp_discovery_turn_input_v1";
|
exports.ASSISTANT_MCP_DISCOVERY_TURN_INPUT_SCHEMA_VERSION = "assistant_mcp_discovery_turn_input_v1";
|
||||||
function toRecordObject(value) {
|
function toRecordObject(value) {
|
||||||
|
|
@ -734,11 +735,13 @@ function hasBusinessOverviewContinuationSignal(text) {
|
||||||
const hasFinalSummaryCue = /(?:\u0447\u0442\u043e\s+\u043c\u044b\s+\u0437\u043d\u0430\u0435\u043c|\u0447\u0442\u043e\s+\u043f\u043e\u043d\u044f\u0442\u043d\u043e|\u0447\u0442\u043e\s+\u043f\u0440\u043e\u0432\u0435\u0440\w*\s+\u0434\u0430\u043b\u044c\u0448\u0435|\u0441\u043b\u0435\u0434\u0443\u044e\u0449\w*\s+\u0448\u0430\u0433|\u0438\u0442\u043e\u0433\w*\s+\u0432\u044b\u0432\u043e\u0434|\u043a\u0430\u043a\u043e\u0439\s+\u0432\u044b\u0432\u043e\u0434|\u0447\u0442\u043e\s+\u0441\s+\u044d\u0442\u0438\u043c\s+\u0434\u0435\u043b\u0430\u0442\u044c|what\s+do\s+we\s+know|what\s+is\s+missing|next\s+step|final\s+summary)/iu.test(normalized);
|
const hasFinalSummaryCue = /(?:\u0447\u0442\u043e\s+\u043c\u044b\s+\u0437\u043d\u0430\u0435\u043c|\u0447\u0442\u043e\s+\u043f\u043e\u043d\u044f\u0442\u043d\u043e|\u0447\u0442\u043e\s+\u043f\u0440\u043e\u0432\u0435\u0440\w*\s+\u0434\u0430\u043b\u044c\u0448\u0435|\u0441\u043b\u0435\u0434\u0443\u044e\u0449\w*\s+\u0448\u0430\u0433|\u0438\u0442\u043e\u0433\w*\s+\u0432\u044b\u0432\u043e\u0434|\u043a\u0430\u043a\u043e\u0439\s+\u0432\u044b\u0432\u043e\u0434|\u0447\u0442\u043e\s+\u0441\s+\u044d\u0442\u0438\u043c\s+\u0434\u0435\u043b\u0430\u0442\u044c|what\s+do\s+we\s+know|what\s+is\s+missing|next\s+step|final\s+summary)/iu.test(normalized);
|
||||||
const hasMoneyBreakdownCue = /(?:\u0440\u0430\u0441\u043a\u0440\u043e\p{L}*\s+\u0434\u0435\u043d\p{L}*|\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+\u0432\u0441\u0435\u0433\u043e\s+\u043f\u043e\u043b\u0443\u0447|\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+(?:\u0432\u0441\u0435\u0433\u043e\s+)?\u0437\u0430\u043f\u043b\u0430\u0442|\u0447\u0438\u0441\u0442\p{L}*\s+\u0434\u0435\u043d\u0435\u0436\u043d\p{L}*\s+\u043f\u043e\u0442\u043e\u043a|\u0433\u043b\u0430\u0432\u043d\p{L}*\s+(?:\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a)|top\s+(?:customer|supplier)|cash\s+breakdown)/iu.test(normalized) &&
|
const hasMoneyBreakdownCue = /(?:\u0440\u0430\u0441\u043a\u0440\u043e\p{L}*\s+\u0434\u0435\u043d\p{L}*|\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+\u0432\u0441\u0435\u0433\u043e\s+\u043f\u043e\u043b\u0443\u0447|\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+(?:\u0432\u0441\u0435\u0433\u043e\s+)?\u0437\u0430\u043f\u043b\u0430\u0442|\u0447\u0438\u0441\u0442\p{L}*\s+\u0434\u0435\u043d\u0435\u0436\u043d\p{L}*\s+\u043f\u043e\u0442\u043e\u043a|\u0433\u043b\u0430\u0432\u043d\p{L}*\s+(?:\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a)|top\s+(?:customer|supplier)|cash\s+breakdown)/iu.test(normalized) &&
|
||||||
/(?:\u043f\u043e\u043b\u0443\u0447|\u0437\u0430\u043f\u043b\u0430\u0442|\u043d\u0435\u0442\u0442\u043e|\u0434\u0435\u043d\p{L}*|\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|received|paid|net|cash|customer|supplier)/iu.test(normalized);
|
/(?:\u043f\u043e\u043b\u0443\u0447|\u0437\u0430\u043f\u043b\u0430\u0442|\u043d\u0435\u0442\u0442\u043e|\u0434\u0435\u043d\p{L}*|\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|received|paid|net|cash|customer|supplier)/iu.test(normalized);
|
||||||
|
const hasCashflowPolarityCue = /(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)[\s\S]{0,80}(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u043d\u0435\u0442\u0442\u043e|\u043f\u0440\u0438\u0448\p{L}*|\u0443\u0448\p{L}*|\u043f\u043e\u043b\u0443\u0447\p{L}*|\u0437\u0430\u043f\u043b\u0430\u0442\p{L}*)|(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u043d\u0435\u0442\u0442\u043e)[\s\S]{0,80}(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)/iu.test(normalized);
|
||||||
return (hasEvidenceContinuationCue ||
|
return (hasEvidenceContinuationCue ||
|
||||||
hasAnalystContinuationCue ||
|
hasAnalystContinuationCue ||
|
||||||
hasTaxContinuationCue ||
|
hasTaxContinuationCue ||
|
||||||
hasFinalSummaryCue ||
|
hasFinalSummaryCue ||
|
||||||
hasMoneyBreakdownCue);
|
hasMoneyBreakdownCue ||
|
||||||
|
hasCashflowPolarityCue);
|
||||||
}
|
}
|
||||||
function hasExplicitVatQuestionSignal(text) {
|
function hasExplicitVatQuestionSignal(text) {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
|
|
@ -785,6 +788,9 @@ function hasBusinessOverviewFollowupSeed(followupSeed) {
|
||||||
followupSeed.loopSelectedChainId === "business_overview");
|
followupSeed.loopSelectedChainId === "business_overview");
|
||||||
}
|
}
|
||||||
function hasValueFlowSignal(text) {
|
function hasValueFlowSignal(text) {
|
||||||
|
if (/(?:\u043e\u0431\u043e\u0440\u043e\u0442|\u0432\u044b\u0440\u0443\u0447\u043a|\u043e\u043f\u043b\u0430\u0442|\u043f\u043b\u0430\u0442[\u0435\u0451]\u0436|\u0437\u0430\u043f\u043b\u0430\u0442|\u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b|\u0441\u043f\u0438\u0441\u0430\u043d|\u0440\u0430\u0441\u0445\u043e\u0434|\u0438\u0441\u0445\u043e\u0434\u044f\u0449|\u0432\u0445\u043e\u0434\u044f\u0449|\u043f\u043e\u043b\u0443\u0447(?:\u0438\u043b|\u0435\u043d\u043e|\u0435\u043d)|\u043f\u043e\u0441\u0442\u0443\u043f\u0438\u043b|\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d|\u0434\u0435\u043d\p{L}*|\u0437\u0430\u0440\u0430\u0431\u043e\u0442|supplier|value[-\s]?flow|turnover|revenue|payment|payout|outflow|cash\s+flow|\bearn(?:ed|ing|ings)?\b)/iu.test(text)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return /(?:оборот|выручк|оплат|плат[её]ж|заплат|перечисл|списан|расход|исходящ|входящ|получ(?:ил|ено|ен)|поступил|поступлен|денежн[а-яёa-z0-9_-]*\s+поток|(?<!\p{L})заработ(?:ал|али|ало|аем|ает|ать|ано|ок)(?!\p{L})|supplier|value[-\s]?flow|turnover|revenue|payment|payout|outflow|cash\s+flow|\bearn(?:ed|ing|ings)?\b)/iu.test(text);
|
return /(?:оборот|выручк|оплат|плат[её]ж|заплат|перечисл|списан|расход|исходящ|входящ|получ(?:ил|ено|ен)|поступил|поступлен|денежн[а-яёa-z0-9_-]*\s+поток|(?<!\p{L})заработ(?:ал|али|ало|аем|ает|ать|ано|ок)(?!\p{L})|supplier|value[-\s]?flow|turnover|revenue|payment|payout|outflow|cash\s+flow|\bearn(?:ed|ing|ings)?\b)/iu.test(text);
|
||||||
}
|
}
|
||||||
function hasValueFlowAggregateQuestionSignal(text) {
|
function hasValueFlowAggregateQuestionSignal(text) {
|
||||||
|
|
@ -794,6 +800,9 @@ function hasPayoutSignal(text) {
|
||||||
return /(?:\bмы\s+(?:за)?плат|(?:за)?платил|оплатил|перечисл|списан|расход|поставщик|исходящ|supplier|payout|outflow|paid\s+to|payment\s+to)/iu.test(text);
|
return /(?:\bмы\s+(?:за)?плат|(?:за)?платил|оплатил|перечисл|списан|расход|поставщик|исходящ|supplier|payout|outflow|paid\s+to|payment\s+to)/iu.test(text);
|
||||||
}
|
}
|
||||||
function hasBidirectionalValueFlowSignal(text) {
|
function hasBidirectionalValueFlowSignal(text) {
|
||||||
|
if (/(?:\u043d\u0435\u0442\u0442\u043e|\u0441\u0430\u043b\u044c\u0434\u043e|\u0431\u0430\u043b\u0430\u043d\u0441\s+(?:\u043f\u043b\u0430\u0442|\u0434\u0435\u043d\u0435\u0433|\u0434\u0435\u043d\u0435\u0436)|\u0432\u0437\u0430\u0438\u043c\u043e\u0440\u0430\u0441\u0447[\u0435\u0451]\u0442|\u043f\u043e\u043b\u0443\u0447\p{L}*.*(?:\u0437\u0430)?\u043f\u043b\u0430\u0442\p{L}*|(?:\u0437\u0430)?\u043f\u043b\u0430\u0442\p{L}*.*\u043f\u043e\u043b\u0443\u0447\p{L}*|\u0432\u0445\u043e\u0434\u044f\u0449.*\u0438\u0441\u0445\u043e\u0434\u044f\u0449|\u0438\u0441\u0445\u043e\u0434\u044f\u0449.*\u0432\u0445\u043e\u0434\u044f\u0449|\u043f\u0440\u0438\u0448\p{L}*.*\u0443\u0448\p{L}*|\u0443\u0448\p{L}*.*\u043f\u0440\u0438\u0448\p{L}*|\u043f\u043b\u044e\u0441.*\u043c\u0438\u043d\u0443\u0441|\u043c\u0438\u043d\u0443\u0441.*\u043f\u043b\u044e\u0441|net\s+(?:flow|cash|payment)|cash\s+net|incoming\s+and\s+outgoing|received\s+and\s+paid|paid\s+and\s+received)/iu.test(text)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return /(?:нетто|сальдо|баланс\s+(?:плат|денег|денеж)|взаиморасч[её]т|получил[иа]?.*(?:за)?платил|(?:за)?платил[иа]?.*получил|входящ.*исходящ|исходящ.*входящ|дебет.*кредит|кредит.*дебет|net\s+(?:flow|cash|payment)|cash\s+net|incoming\s+and\s+outgoing|received\s+and\s+paid|paid\s+and\s+received)/iu.test(text);
|
return /(?:нетто|сальдо|баланс\s+(?:плат|денег|денеж)|взаиморасч[её]т|получил[иа]?.*(?:за)?платил|(?:за)?платил[иа]?.*получил|входящ.*исходящ|исходящ.*входящ|дебет.*кредит|кредит.*дебет|net\s+(?:flow|cash|payment)|cash\s+net|incoming\s+and\s+outgoing|received\s+and\s+paid|paid\s+and\s+received)/iu.test(text);
|
||||||
}
|
}
|
||||||
function hasValueRankingSignal(text) {
|
function hasValueRankingSignal(text) {
|
||||||
|
|
@ -820,6 +829,39 @@ function extractOrganizationScopeFromRawText(value) {
|
||||||
.replace(/\s+(?:\u043f\u043e\s+\u0434\u0430\u043d\u043d\u044b\u043c\s+1\u0441|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u0437\u0430\s+(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{2}|(?:19|20)\d{2})(?:\s+\u0433(?:\u043e\u0434|\.)?)?|\u043d\u0430\s+(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{2}|(?:19|20)\d{2})|\u0437\u0430\s+\u0432\u0441[\u0435\u0451]\s+(?:\u0434\u043e\u0441\u0442\u0443\u043f\p{L}+\s+)?\u0432\u0440\u0435\u043c\u044f|\u0437\u0430\s+\u0432\u0435\u0441\u044c\s+(?:\u0434\u043e\u0441\u0442\u0443\u043f\p{L}+\s+)?\u043f\u0435\u0440\u0438\u043e\u0434|\u0437\u0430\s+\u0432\u0441\u044e\s+\u0438\u0441\u0442\u043e\u0440\u0438(?:\u044e|\u0438)|\u0434\u0430\u0439\s+\u0431\u0438\u0437\u043d\u0435\u0441-?\u043e\u0431\u0437\u043e\u0440|\u0431\u0438\u0437\u043d\u0435\u0441-?\u043e\u0431\u0437\u043e\u0440|\u043d\u043e\s+\u043d\u0435).*$/iu, "")
|
.replace(/\s+(?:\u043f\u043e\s+\u0434\u0430\u043d\u043d\u044b\u043c\s+1\u0441|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u0437\u0430\s+(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{2}|(?:19|20)\d{2})(?:\s+\u0433(?:\u043e\u0434|\.)?)?|\u043d\u0430\s+(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{2}|(?:19|20)\d{2})|\u0437\u0430\s+\u0432\u0441[\u0435\u0451]\s+(?:\u0434\u043e\u0441\u0442\u0443\u043f\p{L}+\s+)?\u0432\u0440\u0435\u043c\u044f|\u0437\u0430\s+\u0432\u0435\u0441\u044c\s+(?:\u0434\u043e\u0441\u0442\u0443\u043f\p{L}+\s+)?\u043f\u0435\u0440\u0438\u043e\u0434|\u0437\u0430\s+\u0432\u0441\u044e\s+\u0438\u0441\u0442\u043e\u0440\u0438(?:\u044e|\u0438)|\u0434\u0430\u0439\s+\u0431\u0438\u0437\u043d\u0435\u0441-?\u043e\u0431\u0437\u043e\u0440|\u0431\u0438\u0437\u043d\u0435\u0441-?\u043e\u0431\u0437\u043e\u0440|\u043d\u043e\s+\u043d\u0435).*$/iu, "")
|
||||||
.trim());
|
.trim());
|
||||||
}
|
}
|
||||||
|
function extractOrganizationScopeFromSemanticText(value) {
|
||||||
|
const text = toNonEmptyString(value);
|
||||||
|
if (!text) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const match = text.match(/(?:^|[\s,;:])(?:\u043a\u043e\u043c\u043f\u0430\u043d(?:\u0438\u0438|\u0438\u044f)|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446(?:\u0438\u0438|\u0438\u044f))\s+(.+?)(?=$|[\n,.;:!?]|\s+\u0437\u0430\s+(?:\d{4}|\d{2}\s*\u0433|\d{2}\s+\u0433\u043e\u0434)|\s+\u043d\u0430\s+(?:\d{4}|\d{2}\s*\u0433|\d{2}\s+\u0433\u043e\u0434))/iu);
|
||||||
|
return toNonEmptyString(match?.[1]);
|
||||||
|
}
|
||||||
|
function normalizeLooseOrganizationAlias(value) {
|
||||||
|
const text = toNonEmptyString(value);
|
||||||
|
if (!text) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const normalized = text
|
||||||
|
.replace(/^[«"'“”]+|[»"'“”]+$/gu, "")
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.trim();
|
||||||
|
if (!normalized) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const comparable = (0, addressTextRepair_1.normalizeRussianComparableText)(normalized);
|
||||||
|
const normalizedTokens = normalized.split(/\s+/u).filter(Boolean);
|
||||||
|
const hasYearOnlyTimeTail = /\b(?:19|20)\d{2}\b/u.test(normalized) &&
|
||||||
|
normalizedTokens.length <= 4 &&
|
||||||
|
!/(?:\u043e\u043e\u043e|\u0438\u043f|\u0430\u043e|\u043f\u0430\u043e|\u0437\u0430\u043e|llc|inc|corp)/iu.test(comparable);
|
||||||
|
if (hasYearOnlyTimeTail) {
|
||||||
|
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)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
function hasMonthlyAggregationSignal(text) {
|
function hasMonthlyAggregationSignal(text) {
|
||||||
return /(?:\u043f\u043e\s+\u043c\u0435\u0441\u044f\u0446\u0430\u043c|\u043f\u043e\u043c\u0435\u0441\u044f\u0447\u043d\u043e|\u0435\u0436\u0435\u043c\u0435\u0441\u044f\u0447\u043d\u043e|month\s+by\s+month|by\s+month|monthly)/iu.test(text);
|
return /(?:\u043f\u043e\s+\u043c\u0435\u0441\u044f\u0446\u0430\u043c|\u043f\u043e\u043c\u0435\u0441\u044f\u0447\u043d\u043e|\u0435\u0436\u0435\u043c\u0435\u0441\u044f\u0447\u043d\u043e|month\s+by\s+month|by\s+month|monthly)/iu.test(text);
|
||||||
}
|
}
|
||||||
|
|
@ -1030,7 +1072,10 @@ function metadataScopeHintFromRawText(text) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
function hasExplicitDateScopeLiteral(text) {
|
function hasExplicitDateScopeLiteral(text) {
|
||||||
return /(?:\b(?:19|20)\d{2}\b|\b\d{4}-\d{2}-\d{2}\b|\b\d{4}-\d{2}\b)/iu.test(text);
|
if (/\b\d{2}\s*(?:\u0433(?:\u043e\u0434(?:\u0430|\u0443)?)?\.?)\b/iu.test(text)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return /(?:\b(?:19|20)\d{2}\b|\b\d{4}-\d{2}-\d{2}\b|\b\d{4}-\d{2}\b|\b\d{2}\s*(?:г(?:од(?:а|у)?)?\.?))\b/iu.test(text);
|
||||||
}
|
}
|
||||||
function stripNegatedTaxDateScopeClauses(text) {
|
function stripNegatedTaxDateScopeClauses(text) {
|
||||||
const dateScopeLiteral = String.raw `\b(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{2}|(?:19|20)\d{2})\b`;
|
const dateScopeLiteral = String.raw `\b(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{2}|(?:19|20)\d{2})\b`;
|
||||||
|
|
@ -1054,6 +1099,20 @@ function collectDateScopeFromRawText(text) {
|
||||||
if (year?.[1]) {
|
if (year?.[1]) {
|
||||||
return year[1];
|
return year[1];
|
||||||
}
|
}
|
||||||
|
const utf8ShortYear = text.match(/\b(\d{2})\s*(?:\u0433(?:\u043e\u0434(?:\u0430|\u0443)?)?\.?)\b/iu);
|
||||||
|
if (utf8ShortYear?.[1]) {
|
||||||
|
const numericYear = Number.parseInt(utf8ShortYear[1], 10);
|
||||||
|
if (Number.isFinite(numericYear)) {
|
||||||
|
return String(numericYear <= 30 ? 2000 + numericYear : 1900 + numericYear);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const shortYear = text.match(/\b(\d{2})\s*(?:г(?:од(?:а|у)?)?\.?)\b/iu);
|
||||||
|
if (shortYear?.[1]) {
|
||||||
|
const numericYear = Number.parseInt(shortYear[1], 10);
|
||||||
|
if (Number.isFinite(numericYear)) {
|
||||||
|
return String(numericYear <= 30 ? 2000 + numericYear : 1900 + numericYear);
|
||||||
|
}
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
function currentIsoDate() {
|
function currentIsoDate() {
|
||||||
|
|
@ -1137,6 +1196,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
const reasonCodes = [];
|
const reasonCodes = [];
|
||||||
const rawUserText = toNonEmptyString(input.userMessage);
|
const rawUserText = toNonEmptyString(input.userMessage);
|
||||||
const rawEffectiveText = toNonEmptyString(input.effectiveMessage);
|
const rawEffectiveText = toNonEmptyString(input.effectiveMessage);
|
||||||
|
const knownOrganizations = Array.isArray(input.knownOrganizations) ? input.knownOrganizations : [];
|
||||||
const repairedUserText = rawUserText ? (0, addressTextRepair_1.repairAddressMojibakeText)(rawUserText) : null;
|
const repairedUserText = rawUserText ? (0, addressTextRepair_1.repairAddressMojibakeText)(rawUserText) : null;
|
||||||
const repairedEffectiveText = rawEffectiveText ? (0, addressTextRepair_1.repairAddressMojibakeText)(rawEffectiveText) : null;
|
const repairedEffectiveText = rawEffectiveText ? (0, addressTextRepair_1.repairAddressMojibakeText)(rawEffectiveText) : null;
|
||||||
const rawUserSignalSourceText = repairedUserText ?? rawUserText ?? "";
|
const rawUserSignalSourceText = repairedUserText ?? rawUserText ?? "";
|
||||||
|
|
@ -1246,9 +1306,28 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
const assistantTurnMeaningOrganizationScope = isReferentialOrganizationPlaceholder(rawAssistantTurnMeaningOrganizationScope)
|
const assistantTurnMeaningOrganizationScope = isReferentialOrganizationPlaceholder(rawAssistantTurnMeaningOrganizationScope)
|
||||||
? null
|
? null
|
||||||
: rawAssistantTurnMeaningOrganizationScope;
|
: rawAssistantTurnMeaningOrganizationScope;
|
||||||
const rawOrganizationMentionSignal = hasOrganizationScopeSignalUtf8(rawText);
|
const organizationSelectionFromKnown = (0, assistantOrganizationMatcher_1.resolveOrganizationSelectionFromMessage)(rawUserText ?? rawEffectiveText ?? rawSignalSourceText, knownOrganizations) ??
|
||||||
const rawOrganizationScope = extractOrganizationScopeFromRawText(rawUserText ?? rawEffectiveText ?? rawSignalSourceText);
|
(0, assistantOrganizationMatcher_1.resolveOrganizationSelectionFromMessage)(rawSignalSourceText, knownOrganizations) ??
|
||||||
const currentTurnFreshOrganizationScope = predecomposeEntities.organization ?? rawOrganizationScope;
|
null;
|
||||||
|
const rawOrganizationScope = extractOrganizationScopeFromRawText(rawUserText) ??
|
||||||
|
extractOrganizationScopeFromRawText(rawEffectiveText) ??
|
||||||
|
extractOrganizationScopeFromRawText(rawSignalSourceText);
|
||||||
|
const semanticOrganizationScope = normalizeLooseOrganizationAlias(extractOrganizationScopeFromSemanticText(rawEffectiveText) ??
|
||||||
|
extractOrganizationScopeFromSemanticText(rawUserText) ??
|
||||||
|
extractOrganizationScopeFromSemanticText(rawSignalSourceText));
|
||||||
|
const predecomposeCounterpartyAsOrganizationScope = (rawBusinessOverviewSignal || seededBusinessOverviewSignal) &&
|
||||||
|
semanticOrganizationScope &&
|
||||||
|
!rawScopedEntityCandidate &&
|
||||||
|
!predecomposeEntities.organization
|
||||||
|
? normalizeLooseOrganizationAlias(predecomposeEntities.counterparty)
|
||||||
|
: null;
|
||||||
|
const rawOrganizationMentionSignal = hasOrganizationScopeSignalUtf8(rawText) ||
|
||||||
|
Boolean(organizationSelectionFromKnown || rawOrganizationScope || semanticOrganizationScope || predecomposeCounterpartyAsOrganizationScope);
|
||||||
|
const currentTurnFreshOrganizationScope = organizationSelectionFromKnown ??
|
||||||
|
predecomposeEntities.organization ??
|
||||||
|
rawOrganizationScope ??
|
||||||
|
semanticOrganizationScope ??
|
||||||
|
predecomposeCounterpartyAsOrganizationScope;
|
||||||
const currentTurnOrganizationScope = currentTurnFreshOrganizationScope ?? assistantTurnMeaningOrganizationScope;
|
const currentTurnOrganizationScope = currentTurnFreshOrganizationScope ?? assistantTurnMeaningOrganizationScope;
|
||||||
const predecomposeOrganizationMirrorsCounterparty = sameScopedName(predecomposeEntities.counterparty, predecomposeEntities.organization);
|
const predecomposeOrganizationMirrorsCounterparty = sameScopedName(predecomposeEntities.counterparty, predecomposeEntities.organization);
|
||||||
const organizationMirrorsPredecomposeCounterpartyForPivot = Boolean(sameScopedName(predecomposeEntities.counterparty, assistantTurnMeaningOrganizationScope) ||
|
const organizationMirrorsPredecomposeCounterpartyForPivot = Boolean(sameScopedName(predecomposeEntities.counterparty, assistantTurnMeaningOrganizationScope) ||
|
||||||
|
|
@ -1805,10 +1884,14 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const clarificationLoopStillNeedsPeriod = Boolean(followupSeed.loopStatus === "awaiting_clarification" && followupSeed.loopPendingAxes.includes("period"));
|
const clarificationLoopStillNeedsPeriod = Boolean(followupSeed.loopStatus === "awaiting_clarification" && followupSeed.loopPendingAxes.includes("period"));
|
||||||
|
const rawTwoDigitPredecomposeYearHint = Boolean(predecomposeDateScope &&
|
||||||
|
/^\d{4}$/.test(predecomposeDateScope) &&
|
||||||
|
/\b\d{2}\b/u.test(rawText));
|
||||||
const businessOverviewRawWithoutDateScope = Boolean(businessOverviewSignal &&
|
const businessOverviewRawWithoutDateScope = Boolean(businessOverviewSignal &&
|
||||||
!rawAllTimeScopeSignal &&
|
!rawAllTimeScopeSignal &&
|
||||||
!explicitDateScopeLiteralDetected &&
|
!explicitDateScopeLiteralDetected &&
|
||||||
!rawDateScope &&
|
!rawDateScope &&
|
||||||
|
!rawTwoDigitPredecomposeYearHint &&
|
||||||
!relativeCurrentDateHintDetected);
|
!relativeCurrentDateHintDetected);
|
||||||
const predecomposeDateScopeCountsAsCurrentTurnPeriod = Boolean(predecomposeDateScope &&
|
const predecomposeDateScopeCountsAsCurrentTurnPeriod = Boolean(predecomposeDateScope &&
|
||||||
!isImplicitCurrentDateScope(predecomposeDateScope) &&
|
!isImplicitCurrentDateScope(predecomposeDateScope) &&
|
||||||
|
|
@ -2236,6 +2319,15 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
if (businessOverviewRawYearOverridesPredecomposeAsOf) {
|
if (businessOverviewRawYearOverridesPredecomposeAsOf) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_business_overview_raw_year_overrode_predecompose_as_of_scope");
|
pushReason(reasonCodes, "mcp_discovery_business_overview_raw_year_overrode_predecompose_as_of_scope");
|
||||||
}
|
}
|
||||||
|
if (organizationSelectionFromKnown) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_organization_scope_from_known_organizations");
|
||||||
|
}
|
||||||
|
if (semanticOrganizationScope) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_organization_scope_from_semantic_text");
|
||||||
|
}
|
||||||
|
if (predecomposeCounterpartyAsOrganizationScope) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_counterparty_reinterpreted_as_organization_scope");
|
||||||
|
}
|
||||||
if (!(valueFlowOrganizationStaysScope && normalizedPredecomposeCounterparty === explicitOrganizationScope) &&
|
if (!(valueFlowOrganizationStaysScope && normalizedPredecomposeCounterparty === explicitOrganizationScope) &&
|
||||||
normalizedPredecomposeCounterparty) {
|
normalizedPredecomposeCounterparty) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_counterparty_from_predecompose");
|
pushReason(reasonCodes, "mcp_discovery_counterparty_from_predecompose");
|
||||||
|
|
|
||||||
|
|
@ -489,8 +489,24 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
llmPreDecomposeMeta
|
llmPreDecomposeMeta
|
||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
|
const hasCompactCashflowFollowupCue = (value) => {
|
||||||
|
const normalized = normalizeFollowupText(value);
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasCompactCue = /(?:\u043a\u043e\u0440\u043e\u0442\p{L}*|\u043d\u0435\s+\u043e\u0431\u0437\u043e\u0440|\u0431\u0435\u0437\s+\u0442\u043e\u043f\p{L}*)/iu.test(normalized);
|
||||||
|
const hasCashflowCue = /(?:\u0434\u0435\u043d\p{L}*|\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u043f\u043e\u043b\u0443\u0447|\u0437\u0430\u043f\u043b\u0430\u0442|\u043f\u0440\u0438\u0448\p{L}*|\u0443\u0448\p{L}*|\u043d\u0435\u0442\u0442\u043e)/iu.test(normalized);
|
||||||
|
return hasCompactCue && hasCashflowCue;
|
||||||
|
};
|
||||||
|
const compactCashflowFollowupSignal = hasShortValueFlowRetargetCue(userMessage) ||
|
||||||
|
hasCompactCashflowFollowupCue(userMessage) ||
|
||||||
|
(deps.toNonEmptyString(alternateMessage)
|
||||||
|
? hasShortValueFlowRetargetCue(String(alternateMessage ?? "")) ||
|
||||||
|
hasCompactCashflowFollowupCue(String(alternateMessage ?? ""))
|
||||||
|
: false);
|
||||||
if (assistantTurnMeaning?.stale_replay_forbidden === true &&
|
if (assistantTurnMeaning?.stale_replay_forbidden === true &&
|
||||||
!hasExplicitSummaryBundleReuseSignal(userMessage, alternateMessage)) {
|
!hasExplicitSummaryBundleReuseSignal(userMessage, alternateMessage) &&
|
||||||
|
!compactCashflowFollowupSignal) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const latestAddressItem = deps.findLastAddressAssistantItem(items);
|
const latestAddressItem = deps.findLastAddressAssistantItem(items);
|
||||||
|
|
@ -555,7 +571,8 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
const hasValueFlowCarryoverSourceHint = sourceIntentHint === "customer_revenue_and_payments" ||
|
const hasValueFlowCarryoverSourceHint = sourceIntentHint === "customer_revenue_and_payments" ||
|
||||||
sourceDiscoveryPilotScopeHint === "counterparty_value_flow_query_movements_v1" ||
|
sourceDiscoveryPilotScopeHint === "counterparty_value_flow_query_movements_v1" ||
|
||||||
sourceDiscoveryPilotScopeHint === "counterparty_supplier_payout_query_movements_v1" ||
|
sourceDiscoveryPilotScopeHint === "counterparty_supplier_payout_query_movements_v1" ||
|
||||||
sourceDiscoveryPilotScopeHint === "counterparty_bidirectional_value_flow_query_movements_v1";
|
sourceDiscoveryPilotScopeHint === "counterparty_bidirectional_value_flow_query_movements_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 = (0, assistantContinuityPolicy_1.resolveNavigationSessionContextState)(addressNavigationState, deps.toNonEmptyString, deps.normalizeOrganizationScopeValue);
|
||||||
const navigationFocusObjectHint = navigationSessionState.focusObject;
|
const navigationFocusObjectHint = navigationSessionState.focusObject;
|
||||||
|
|
@ -579,9 +596,11 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
? deps.resolveDebtRoleSwapFollowupIntent(String(alternateMessage ?? ""), sourceIntentHint)
|
? deps.resolveDebtRoleSwapFollowupIntent(String(alternateMessage ?? ""), sourceIntentHint)
|
||||||
: null;
|
: null;
|
||||||
const debtRoleSwapIntent = debtRoleSwapPrimary ?? debtRoleSwapAlternate ?? null;
|
const debtRoleSwapIntent = debtRoleSwapPrimary ?? debtRoleSwapAlternate ?? null;
|
||||||
const shortValueFlowRetargetPrimary = hasValueFlowCarryoverSourceHint && hasShortValueFlowRetargetCue(userMessage);
|
const shortValueFlowRetargetPrimary = hasValueFlowCarryoverSourceHint &&
|
||||||
|
(hasShortValueFlowRetargetCue(userMessage) || hasCompactCashflowFollowupCue(userMessage));
|
||||||
const shortValueFlowRetargetAlternate = hasValueFlowCarryoverSourceHint && deps.toNonEmptyString(alternateMessage)
|
const shortValueFlowRetargetAlternate = hasValueFlowCarryoverSourceHint && deps.toNonEmptyString(alternateMessage)
|
||||||
? hasShortValueFlowRetargetCue(String(alternateMessage ?? ""))
|
? hasShortValueFlowRetargetCue(String(alternateMessage ?? "")) ||
|
||||||
|
hasCompactCashflowFollowupCue(String(alternateMessage ?? ""))
|
||||||
: false;
|
: false;
|
||||||
const businessOverviewBoundaryFollowupPrimary = hasBusinessOverviewCarryoverSourceHint && hasBusinessOverviewBoundaryFollowupCue(userMessage);
|
const businessOverviewBoundaryFollowupPrimary = hasBusinessOverviewCarryoverSourceHint && hasBusinessOverviewBoundaryFollowupCue(userMessage);
|
||||||
const businessOverviewBoundaryFollowupAlternate = hasBusinessOverviewCarryoverSourceHint && deps.toNonEmptyString(alternateMessage)
|
const businessOverviewBoundaryFollowupAlternate = hasBusinessOverviewCarryoverSourceHint && deps.toNonEmptyString(alternateMessage)
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,14 @@ function sessionOrganizationName(
|
||||||
return toNonEmptyString(scope?.selectedOrganization) ?? toNonEmptyString(scope?.activeOrganization);
|
return toNonEmptyString(scope?.selectedOrganization) ?? toNonEmptyString(scope?.activeOrganization);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sessionKnownOrganizations(
|
||||||
|
sessionOrganizationScope: BuildAssistantAddressOrchestrationRuntimeInput["sessionOrganizationScope"]
|
||||||
|
): unknown[] {
|
||||||
|
const scope = toRecordObject(sessionOrganizationScope);
|
||||||
|
const knownOrganizations = scope?.knownOrganizations;
|
||||||
|
return Array.isArray(knownOrganizations) ? knownOrganizations : [];
|
||||||
|
}
|
||||||
|
|
||||||
function predecomposeOrganizationName(
|
function predecomposeOrganizationName(
|
||||||
predecomposeContract: Record<string, unknown> | null,
|
predecomposeContract: Record<string, unknown> | null,
|
||||||
toNonEmptyString: BuildAssistantAddressOrchestrationRuntimeInput["toNonEmptyString"]
|
toNonEmptyString: BuildAssistantAddressOrchestrationRuntimeInput["toNonEmptyString"]
|
||||||
|
|
@ -410,7 +418,8 @@ 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: discoveryFollowupContext,
|
||||||
|
knownOrganizations: sessionKnownOrganizations(input.sessionOrganizationScope ?? null)
|
||||||
})) as Record<string, unknown>;
|
})) as Record<string, unknown>;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
mcpDiscoveryRuntimeEntryPointError = String(error instanceof Error ? error.message : error ?? "unknown_error").slice(0, 280);
|
mcpDiscoveryRuntimeEntryPointError = String(error instanceof Error ? error.message : error ?? "unknown_error").slice(0, 280);
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,18 @@ function hasBusinessOverviewDirectMoneyAnswerHint(input: {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const text = input.rawUtterance;
|
const text = input.rawUtterance;
|
||||||
|
if (
|
||||||
|
/(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)[\s\S]{0,80}(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u043d\u0435\u0442\u0442\u043e|\u043f\u0440\u0438\u0448\p{L}*|\u0443\u0448\p{L}*|\u043f\u043e\u043b\u0443\u0447\p{L}*|\u0437\u0430\u043f\u043b\u0430\u0442\p{L}*)|(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u043d\u0435\u0442\u0442\u043e)[\s\S]{0,80}(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)/iu.test(
|
||||||
|
text
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
/(?:\u043d\u0435\s+\u043e\u0431\u0437\u043e\u0440|\u043f\u0440\u043e\u0441\u0442\u043e\s+\u0434\u0435\u043d\p{L}+|\u0431\u0435\u0437\s+\u0442\u043e\u043f(?:\u043e\u0432|\u0430)?\b|\u0434\u0435\u043d\p{L}{0,20}\s+\u043d\u0435\u0442\u0442\u043e|\u043f\u0440\u0438\u0448\u043b\p{L}*[\s\S]{0,80}\u0443\u0448\u043b\p{L}*[\s\S]{0,80}\u043d\u0435\u0442\u0442\u043e|\u0432\u0445\u043e\u0434\u044f\u0449\p{L}*[\s\S]{0,80}\u0438\u0441\u0445\u043e\u0434\u044f\u0449\p{L}*[\s\S]{0,80}\u043d\u0435\u0442\u0442\u043e)/iu.test(text)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return /(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|how\s+much)[\s\S]{0,120}(?:\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u0432\u044b\u0440\u0443\u0447|\u0434\u0435\u043d\p{L}*|\u043f\u043e\u043b\u0443\u0447|\u043f\u043e\u0441\u0442\u0443\u043f\p{L}*)|(?:\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u0432\u044b\u0440\u0443\u0447)[\s\S]{0,120}(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|\u0432\u0441\u0435\u0433\u043e|\u0432\u043e\u043e\u0431\u0449\u0435|(?:19|20)\d{2}|all\s+time)|(?:\u043a\u0430\u043a\u043e\u0439|\u043a\u0430\u043a\u0430\u044f|\u043a\u0430\u043a\u0438\u0435|which|what)[\s\S]{0,80}(?:\u0441\u0430\u043c\p{L}*|top|best|most)[\s\S]{0,80}(?:\u0434\u043e\u0445\u043e\u0434\u043d|\u0432\u044b\u0440\u0443\u0447|\u043e\u0431\u043e\u0440\u043e\u0442|revenue|turnover)[\s\S]{0,40}(?:\u0433\u043e\u0434|year)/iu.test(
|
return /(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|how\s+much)[\s\S]{0,120}(?:\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u0432\u044b\u0440\u0443\u0447|\u0434\u0435\u043d\p{L}*|\u043f\u043e\u043b\u0443\u0447|\u043f\u043e\u0441\u0442\u0443\u043f\p{L}*)|(?:\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u0432\u044b\u0440\u0443\u0447)[\s\S]{0,120}(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|\u0432\u0441\u0435\u0433\u043e|\u0432\u043e\u043e\u0431\u0449\u0435|(?:19|20)\d{2}|all\s+time)|(?:\u043a\u0430\u043a\u043e\u0439|\u043a\u0430\u043a\u0430\u044f|\u043a\u0430\u043a\u0438\u0435|which|what)[\s\S]{0,80}(?:\u0441\u0430\u043c\p{L}*|top|best|most)[\s\S]{0,80}(?:\u0434\u043e\u0445\u043e\u0434\u043d|\u0432\u044b\u0440\u0443\u0447|\u043e\u0431\u043e\u0440\u043e\u0442|revenue|turnover)[\s\S]{0,40}(?:\u0433\u043e\u0434|year)/iu.test(
|
||||||
text
|
text
|
||||||
);
|
);
|
||||||
|
|
@ -330,6 +342,16 @@ function rankingNeedFromRawUtterance(value: string): string | null {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function suppressRankingNeedFromRawUtterance(value: string): boolean {
|
||||||
|
const text = lower(value);
|
||||||
|
if (!text) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return /(?:\u0431\u0435\u0437\s+\u0442\u043e\u043f(?:\u043e\u0432|\u0430)?\b|\u043d\u0435\s+\u0442\u043e\u043f\b|\u043d\u0435\s+\u043e\u0431\u0437\u043e\u0440\b|\u043f\u0440\u043e\u0441\u0442\u043e\s+\u0434\u0435\u043d\p{L}+|\u0438\u0441\u043a\u043b\u044e\u0447\w*\s+\u0442\u043e\u043f|\u0431\u0435\u0437\s+\u0440\u0435\u0439\u0442\u0438\u043d\u0433\u0430\b|без\s+топ(?:ов|а)?\b|не\s+топ\b|не\s+обзор\b|просто\s+ден\p{L}+|исключ\S*\s+топ|без\s+рейтинга\b)/iu.test(
|
||||||
|
text
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function proofExpectationFor(input: {
|
function proofExpectationFor(input: {
|
||||||
family: string | null;
|
family: string | null;
|
||||||
clarificationGaps: string[];
|
clarificationGaps: string[];
|
||||||
|
|
@ -503,6 +525,7 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
||||||
const action = lower(turnMeaning?.asked_action_family);
|
const action = lower(turnMeaning?.asked_action_family);
|
||||||
const unsupported = lower(turnMeaning?.unsupported_but_understood_family);
|
const unsupported = lower(turnMeaning?.unsupported_but_understood_family);
|
||||||
const rawUtterance = lower(input.rawUtterance);
|
const rawUtterance = lower(input.rawUtterance);
|
||||||
|
const rawQuestionSignal = lower([input.rawUtterance, turnMeaning?.raw_message, turnMeaning?.effective_message].join(" "));
|
||||||
const aggregationAxis = lower(turnMeaning?.asked_aggregation_axis);
|
const aggregationAxis = lower(turnMeaning?.asked_aggregation_axis);
|
||||||
const seededRankingNeed = toNonEmptyString(turnMeaning?.seeded_ranking_need);
|
const seededRankingNeed = toNonEmptyString(turnMeaning?.seeded_ranking_need);
|
||||||
const explicitDateScope = toNonEmptyString(turnMeaning?.explicit_date_scope);
|
const explicitDateScope = toNonEmptyString(turnMeaning?.explicit_date_scope);
|
||||||
|
|
@ -520,16 +543,22 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
||||||
});
|
});
|
||||||
const aggregationNeed = aggregationNeedFor(aggregationAxis);
|
const aggregationNeed = aggregationNeedFor(aggregationAxis);
|
||||||
const comparisonNeed = comparisonNeedFor(action);
|
const comparisonNeed = comparisonNeedFor(action);
|
||||||
const rankingNeed = rankingNeedFromRawUtterance(rawUtterance) ?? seededRankingNeed;
|
|
||||||
const allTimeScopeHint = hasAllTimeScopeHint(rawUtterance);
|
const allTimeScopeHint = hasAllTimeScopeHint(rawUtterance);
|
||||||
const subjectScopedBidirectionalAllTime =
|
const subjectScopedBidirectionalAllTime =
|
||||||
businessFactFamily === "value_flow" &&
|
businessFactFamily === "value_flow" &&
|
||||||
comparisonNeed === "incoming_vs_outgoing" &&
|
comparisonNeed === "incoming_vs_outgoing" &&
|
||||||
subjectCandidates.length > 0 &&
|
subjectCandidates.length > 0 &&
|
||||||
!explicitDateScope;
|
!explicitDateScope;
|
||||||
|
const suppressRankingNeed =
|
||||||
|
suppressRankingNeedFromRawUtterance(rawQuestionSignal) ||
|
||||||
|
/(?:\u0431\u0435\u0437\s+\u0442\u043e\u043f(?:\u043e\u0432|\u0430)?|\u043d\u0435\s+\u0442\u043e\u043f|\u043d\u0435\s+\u043e\u0431\u0437\u043e\u0440|\u043f\u0440\u043e\u0441\u0442\u043e\s+\u0434\u0435\u043d\p{L}+|\u0438\u0441\u043a\u043b\u044e\u0447\w*\s+\u0442\u043e\u043f|\u0431\u0435\u0437\s+\u0440\u0435\u0439\u0442\u0438\u043d\u0433\u0430)/iu.test(
|
||||||
|
rawQuestionSignal
|
||||||
|
);
|
||||||
|
const rawRankingNeed = rankingNeedFromRawUtterance(rawQuestionSignal);
|
||||||
|
const rankingNeed = suppressRankingNeed ? null : rawRankingNeed ?? seededRankingNeed;
|
||||||
const directBusinessOverviewMoneyAnswerHint = hasBusinessOverviewDirectMoneyAnswerHint({
|
const directBusinessOverviewMoneyAnswerHint = hasBusinessOverviewDirectMoneyAnswerHint({
|
||||||
family: businessFactFamily,
|
family: businessFactFamily,
|
||||||
rawUtterance,
|
rawUtterance: rawQuestionSignal,
|
||||||
rankingNeed
|
rankingNeed
|
||||||
});
|
});
|
||||||
const oneSidedOpenScopeTotalHint = hasOpenScopeOneSidedValueTotalHintUtf8Safe(rawUtterance, action);
|
const oneSidedOpenScopeTotalHint = hasOpenScopeOneSidedValueTotalHintUtf8Safe(rawUtterance, action);
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,46 @@ function requestsFinancialCounterpartyBoundary(turnMeaning: Record<string, unkno
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function requestsCompactCashflowAnswer(
|
||||||
|
turnMeaning: Record<string, unknown> | null,
|
||||||
|
graph: Record<string, unknown> | null
|
||||||
|
): boolean {
|
||||||
|
const text = normalizeQuestionText([
|
||||||
|
turnMeaning?.raw_message,
|
||||||
|
turnMeaning?.effective_message,
|
||||||
|
graph?.source_message,
|
||||||
|
graph?.question
|
||||||
|
].join(" "));
|
||||||
|
if (!text) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
/(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)[\s\S]{0,80}(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u043d\u0435\u0442\u0442\u043e|\u043f\u0440\u0438\u0448\p{L}*|\u0443\u0448\p{L}*|\u043f\u043e\u043b\u0443\u0447\p{L}*|\u0437\u0430\u043f\u043b\u0430\u0442\p{L}*)|(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u043d\u0435\u0442\u0442\u043e)[\s\S]{0,80}(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)/iu.test(
|
||||||
|
text
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return /(?:\u043d\u0435\s+\u043e\u0431\u0437\u043e\u0440|\u043f\u0440\u043e\u0441\u0442\u043e\s+\u0434\u0435\u043d\p{L}+|\u0431\u0435\u0437\s+\u0442\u043e\u043f(?:\u043e\u0432|\u0430)?\b|\u0434\u0435\u043d\p{L}{0,20}\s+\u043d\u0435\u0442\u0442\u043e|\u043f\u043e\u043b\u0443\u0447\p{L}*[\s\S]{0,80}\u0437\u0430\u043f\u043b\u0430\u0442\p{L}*[\s\S]{0,80}\u043d\u0435\u0442\u0442\u043e|\u043f\u0440\u0438\u0448\u043b\p{L}*[\s\S]{0,80}\u0443\u0448\u043b\p{L}*[\s\S]{0,80}\u043d\u0435\u0442\u0442\u043e|(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*)[\s\S]{0,120}(?:\u0434\u0435\u043d\p{L}*|\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u043f\u043e\u043b\u0443\u0447|\u043f\u0440\u0438\u0448\u043b))/iu.test(
|
||||||
|
text
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestsCashflowPolarityAnswer(
|
||||||
|
turnMeaning: Record<string, unknown> | null,
|
||||||
|
graph: Record<string, unknown> | null
|
||||||
|
): boolean {
|
||||||
|
const text = normalizeQuestionText([
|
||||||
|
turnMeaning?.raw_message,
|
||||||
|
turnMeaning?.effective_message,
|
||||||
|
graph?.source_message,
|
||||||
|
graph?.question
|
||||||
|
].join(" "));
|
||||||
|
return /(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)[\s\S]{0,80}(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441)|(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441)[\s\S]{0,80}(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)/iu.test(
|
||||||
|
text
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function toStringList(value: unknown): string[] {
|
function toStringList(value: unknown): string[] {
|
||||||
if (!Array.isArray(value)) {
|
if (!Array.isArray(value)) {
|
||||||
return [];
|
return [];
|
||||||
|
|
@ -689,6 +729,21 @@ function compactComparable(value: string | null): string {
|
||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function businessOverviewOrganizationScopeLabel(value: unknown): string | null {
|
||||||
|
const text = toNonEmptyString(value);
|
||||||
|
if (!text) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const comparable = compactComparable(text);
|
||||||
|
if (
|
||||||
|
/^(?:с|без)\s+разбивк/.test(comparable) ||
|
||||||
|
/\b(?:входящ|исходящ|нетто|топ|контрагент|платеж|поступлен)\b/.test(comparable)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
function businessOverviewSeparateSubjectLabel(
|
function businessOverviewSeparateSubjectLabel(
|
||||||
graph: Record<string, unknown> | null,
|
graph: Record<string, unknown> | null,
|
||||||
turnMeaning: Record<string, unknown> | null,
|
turnMeaning: Record<string, unknown> | null,
|
||||||
|
|
@ -802,7 +857,7 @@ function buildCompactBusinessOverviewReply(
|
||||||
const netDirection = overview.net_direction === "net_outgoing" ? "операционное нетто в минус" : "расчетное операционное нетто";
|
const netDirection = overview.net_direction === "net_outgoing" ? "операционное нетто в минус" : "расчетное операционное нетто";
|
||||||
const period = businessOverviewPeriodText(overview);
|
const period = businessOverviewPeriodText(overview);
|
||||||
const limitLine = businessOverviewCoverageLimitLine(overview);
|
const limitLine = businessOverviewCoverageLimitLine(overview);
|
||||||
const organizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
|
const organizationScope = businessOverviewOrganizationScopeLabel(turnMeaning?.explicit_organization_scope);
|
||||||
const separateSubject = businessOverviewSeparateSubjectLabel(graph, turnMeaning, organizationScope);
|
const separateSubject = businessOverviewSeparateSubjectLabel(graph, turnMeaning, organizationScope);
|
||||||
const previousCounterpartySummary = buildPreviousCounterpartyValueFlowSummary(
|
const previousCounterpartySummary = buildPreviousCounterpartyValueFlowSummary(
|
||||||
toRecordObject(turnMeaning?.previous_counterparty_value_flow_bundle),
|
toRecordObject(turnMeaning?.previous_counterparty_value_flow_bundle),
|
||||||
|
|
@ -857,6 +912,28 @@ 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 cashflowPolarityRequested = compactCashflowRequested && requestsCashflowPolarityAnswer(turnMeaning, graph);
|
||||||
|
|
||||||
|
if (compactCashflowRequested && !rankingNeed && (incomingAmount || outgoingAmount || netAmount)) {
|
||||||
|
const netDisplay = sentenceAmount(netAmount) ?? netAmount ?? "0 \u0440\u0443\u0431.";
|
||||||
|
const signedNetDisplay =
|
||||||
|
cashflowPolarityRequested && netDisplay && !String(netDisplay).trim().startsWith("-")
|
||||||
|
? `+${netDisplay}`
|
||||||
|
: netDisplay;
|
||||||
|
const polarityLead = cashflowPolarityRequested
|
||||||
|
? `\u041a\u043e\u0440\u043e\u0442\u043a\u043e: \u043f\u043e \u0434\u0435\u043d\u044c\u0433\u0430\u043c \u043f\u043b\u044e\u0441. `
|
||||||
|
: "\u041a\u043e\u0440\u043e\u0442\u043a\u043e: ";
|
||||||
|
lines.push(
|
||||||
|
`${polarityLead}${organizationPrefix}${period} \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 ${incomingAmount ?? "0 \u0440\u0443\u0431."}; \u0437\u0430\u043f\u043b\u0430\u0442\u0438\u043b\u0438/\u0441\u043f\u0438\u0441\u0430\u043b\u0438 ${outgoingAmount ?? "0 \u0440\u0443\u0431."}; \u0434\u0435\u043d\u0435\u0436\u043d\u043e\u0435 \u043d\u0435\u0442\u0442\u043e ${signedNetDisplay}.`
|
||||||
|
);
|
||||||
|
lines.push(
|
||||||
|
cashflowPolarityRequested
|
||||||
|
? "\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 \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);
|
||||||
|
}
|
||||||
|
|
||||||
if (profitMarginBoundary) {
|
if (profitMarginBoundary) {
|
||||||
const accountingFinancialResult = toRecordObject(overview.accounting_financial_result);
|
const accountingFinancialResult = toRecordObject(overview.accounting_financial_result);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import {
|
||||||
buildAssistantMcpDiscoveryDataNeedGraph,
|
buildAssistantMcpDiscoveryDataNeedGraph,
|
||||||
type AssistantMcpDiscoveryDataNeedGraphContract
|
type AssistantMcpDiscoveryDataNeedGraphContract
|
||||||
} from "./assistantMcpDiscoveryDataNeedGraph";
|
} from "./assistantMcpDiscoveryDataNeedGraph";
|
||||||
|
import { resolveOrganizationSelectionFromMessage } from "./assistantOrganizationMatcher";
|
||||||
import { normalizeRussianComparableText, repairAddressMojibakeText } from "./addressTextRepair";
|
import { normalizeRussianComparableText, repairAddressMojibakeText } from "./addressTextRepair";
|
||||||
import type {
|
import type {
|
||||||
AssistantMcpDiscoveryMetadataRecommendedPrimitive,
|
AssistantMcpDiscoveryMetadataRecommendedPrimitive,
|
||||||
|
|
@ -27,6 +28,7 @@ export interface BuildAssistantMcpDiscoveryTurnInputAdapterInput {
|
||||||
followupContext?: Record<string, unknown> | null;
|
followupContext?: Record<string, unknown> | null;
|
||||||
userMessage?: string | null;
|
userMessage?: string | null;
|
||||||
effectiveMessage?: string | null;
|
effectiveMessage?: string | null;
|
||||||
|
knownOrganizations?: unknown[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AssistantMcpDiscoveryTurnInputContract {
|
export interface AssistantMcpDiscoveryTurnInputContract {
|
||||||
|
|
@ -1044,12 +1046,17 @@ function hasBusinessOverviewContinuationSignal(text: string): boolean {
|
||||||
/(?:\u043f\u043e\u043b\u0443\u0447|\u0437\u0430\u043f\u043b\u0430\u0442|\u043d\u0435\u0442\u0442\u043e|\u0434\u0435\u043d\p{L}*|\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|received|paid|net|cash|customer|supplier)/iu.test(
|
/(?:\u043f\u043e\u043b\u0443\u0447|\u0437\u0430\u043f\u043b\u0430\u0442|\u043d\u0435\u0442\u0442\u043e|\u0434\u0435\u043d\p{L}*|\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|received|paid|net|cash|customer|supplier)/iu.test(
|
||||||
normalized
|
normalized
|
||||||
);
|
);
|
||||||
|
const hasCashflowPolarityCue =
|
||||||
|
/(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)[\s\S]{0,80}(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u043d\u0435\u0442\u0442\u043e|\u043f\u0440\u0438\u0448\p{L}*|\u0443\u0448\p{L}*|\u043f\u043e\u043b\u0443\u0447\p{L}*|\u0437\u0430\u043f\u043b\u0430\u0442\p{L}*)|(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u043d\u0435\u0442\u0442\u043e)[\s\S]{0,80}(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)/iu.test(
|
||||||
|
normalized
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
hasEvidenceContinuationCue ||
|
hasEvidenceContinuationCue ||
|
||||||
hasAnalystContinuationCue ||
|
hasAnalystContinuationCue ||
|
||||||
hasTaxContinuationCue ||
|
hasTaxContinuationCue ||
|
||||||
hasFinalSummaryCue ||
|
hasFinalSummaryCue ||
|
||||||
hasMoneyBreakdownCue
|
hasMoneyBreakdownCue ||
|
||||||
|
hasCashflowPolarityCue
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1114,6 +1121,13 @@ function hasBusinessOverviewFollowupSeed(followupSeed: ReturnType<typeof collect
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasValueFlowSignal(text: string): boolean {
|
function hasValueFlowSignal(text: string): boolean {
|
||||||
|
if (
|
||||||
|
/(?:\u043e\u0431\u043e\u0440\u043e\u0442|\u0432\u044b\u0440\u0443\u0447\u043a|\u043e\u043f\u043b\u0430\u0442|\u043f\u043b\u0430\u0442[\u0435\u0451]\u0436|\u0437\u0430\u043f\u043b\u0430\u0442|\u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b|\u0441\u043f\u0438\u0441\u0430\u043d|\u0440\u0430\u0441\u0445\u043e\u0434|\u0438\u0441\u0445\u043e\u0434\u044f\u0449|\u0432\u0445\u043e\u0434\u044f\u0449|\u043f\u043e\u043b\u0443\u0447(?:\u0438\u043b|\u0435\u043d\u043e|\u0435\u043d)|\u043f\u043e\u0441\u0442\u0443\u043f\u0438\u043b|\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d|\u0434\u0435\u043d\p{L}*|\u0437\u0430\u0440\u0430\u0431\u043e\u0442|supplier|value[-\s]?flow|turnover|revenue|payment|payout|outflow|cash\s+flow|\bearn(?:ed|ing|ings)?\b)/iu.test(
|
||||||
|
text
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return /(?:оборот|выручк|оплат|плат[её]ж|заплат|перечисл|списан|расход|исходящ|входящ|получ(?:ил|ено|ен)|поступил|поступлен|денежн[а-яёa-z0-9_-]*\s+поток|(?<!\p{L})заработ(?:ал|али|ало|аем|ает|ать|ано|ок)(?!\p{L})|supplier|value[-\s]?flow|turnover|revenue|payment|payout|outflow|cash\s+flow|\bearn(?:ed|ing|ings)?\b)/iu.test(
|
return /(?:оборот|выручк|оплат|плат[её]ж|заплат|перечисл|списан|расход|исходящ|входящ|получ(?:ил|ено|ен)|поступил|поступлен|денежн[а-яёa-z0-9_-]*\s+поток|(?<!\p{L})заработ(?:ал|али|ало|аем|ает|ать|ано|ок)(?!\p{L})|supplier|value[-\s]?flow|turnover|revenue|payment|payout|outflow|cash\s+flow|\bearn(?:ed|ing|ings)?\b)/iu.test(
|
||||||
text
|
text
|
||||||
);
|
);
|
||||||
|
|
@ -1132,6 +1146,13 @@ function hasPayoutSignal(text: string): boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasBidirectionalValueFlowSignal(text: string): boolean {
|
function hasBidirectionalValueFlowSignal(text: string): boolean {
|
||||||
|
if (
|
||||||
|
/(?:\u043d\u0435\u0442\u0442\u043e|\u0441\u0430\u043b\u044c\u0434\u043e|\u0431\u0430\u043b\u0430\u043d\u0441\s+(?:\u043f\u043b\u0430\u0442|\u0434\u0435\u043d\u0435\u0433|\u0434\u0435\u043d\u0435\u0436)|\u0432\u0437\u0430\u0438\u043c\u043e\u0440\u0430\u0441\u0447[\u0435\u0451]\u0442|\u043f\u043e\u043b\u0443\u0447\p{L}*.*(?:\u0437\u0430)?\u043f\u043b\u0430\u0442\p{L}*|(?:\u0437\u0430)?\u043f\u043b\u0430\u0442\p{L}*.*\u043f\u043e\u043b\u0443\u0447\p{L}*|\u0432\u0445\u043e\u0434\u044f\u0449.*\u0438\u0441\u0445\u043e\u0434\u044f\u0449|\u0438\u0441\u0445\u043e\u0434\u044f\u0449.*\u0432\u0445\u043e\u0434\u044f\u0449|\u043f\u0440\u0438\u0448\p{L}*.*\u0443\u0448\p{L}*|\u0443\u0448\p{L}*.*\u043f\u0440\u0438\u0448\p{L}*|\u043f\u043b\u044e\u0441.*\u043c\u0438\u043d\u0443\u0441|\u043c\u0438\u043d\u0443\u0441.*\u043f\u043b\u044e\u0441|net\s+(?:flow|cash|payment)|cash\s+net|incoming\s+and\s+outgoing|received\s+and\s+paid|paid\s+and\s+received)/iu.test(
|
||||||
|
text
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return /(?:нетто|сальдо|баланс\s+(?:плат|денег|денеж)|взаиморасч[её]т|получил[иа]?.*(?:за)?платил|(?:за)?платил[иа]?.*получил|входящ.*исходящ|исходящ.*входящ|дебет.*кредит|кредит.*дебет|net\s+(?:flow|cash|payment)|cash\s+net|incoming\s+and\s+outgoing|received\s+and\s+paid|paid\s+and\s+received)/iu.test(
|
return /(?:нетто|сальдо|баланс\s+(?:плат|денег|денеж)|взаиморасч[её]т|получил[иа]?.*(?:за)?платил|(?:за)?платил[иа]?.*получил|входящ.*исходящ|исходящ.*входящ|дебет.*кредит|кредит.*дебет|net\s+(?:flow|cash|payment)|cash\s+net|incoming\s+and\s+outgoing|received\s+and\s+paid|paid\s+and\s+received)/iu.test(
|
||||||
text
|
text
|
||||||
);
|
);
|
||||||
|
|
@ -1178,6 +1199,46 @@ function extractOrganizationScopeFromRawText(value: unknown): string | null {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractOrganizationScopeFromSemanticText(value: unknown): string | null {
|
||||||
|
const text = toNonEmptyString(value);
|
||||||
|
if (!text) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const match = text.match(
|
||||||
|
/(?:^|[\s,;:])(?:\u043a\u043e\u043c\u043f\u0430\u043d(?:\u0438\u0438|\u0438\u044f)|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446(?:\u0438\u0438|\u0438\u044f))\s+(.+?)(?=$|[\n,.;:!?]|\s+\u0437\u0430\s+(?:\d{4}|\d{2}\s*\u0433|\d{2}\s+\u0433\u043e\u0434)|\s+\u043d\u0430\s+(?:\d{4}|\d{2}\s*\u0433|\d{2}\s+\u0433\u043e\u0434))/iu
|
||||||
|
);
|
||||||
|
return toNonEmptyString(match?.[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLooseOrganizationAlias(value: string | null): string | null {
|
||||||
|
const text = toNonEmptyString(value);
|
||||||
|
if (!text) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const normalized = text
|
||||||
|
.replace(/^[«"'“”]+|[»"'“”]+$/gu, "")
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.trim();
|
||||||
|
if (!normalized) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const comparable = normalizeRussianComparableText(normalized);
|
||||||
|
const normalizedTokens = normalized.split(/\s+/u).filter(Boolean);
|
||||||
|
const hasYearOnlyTimeTail =
|
||||||
|
/\b(?:19|20)\d{2}\b/u.test(normalized) &&
|
||||||
|
normalizedTokens.length <= 4 &&
|
||||||
|
!/(?:\u043e\u043e\u043e|\u0438\u043f|\u0430\u043e|\u043f\u0430\u043e|\u0437\u0430\u043e|llc|inc|corp)/iu.test(
|
||||||
|
comparable
|
||||||
|
);
|
||||||
|
if (hasYearOnlyTimeTail) {
|
||||||
|
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)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
function hasMonthlyAggregationSignal(text: string): boolean {
|
function hasMonthlyAggregationSignal(text: string): boolean {
|
||||||
return /(?:\u043f\u043e\s+\u043c\u0435\u0441\u044f\u0446\u0430\u043c|\u043f\u043e\u043c\u0435\u0441\u044f\u0447\u043d\u043e|\u0435\u0436\u0435\u043c\u0435\u0441\u044f\u0447\u043d\u043e|month\s+by\s+month|by\s+month|monthly)/iu.test(
|
return /(?:\u043f\u043e\s+\u043c\u0435\u0441\u044f\u0446\u0430\u043c|\u043f\u043e\u043c\u0435\u0441\u044f\u0447\u043d\u043e|\u0435\u0436\u0435\u043c\u0435\u0441\u044f\u0447\u043d\u043e|month\s+by\s+month|by\s+month|monthly)/iu.test(
|
||||||
text
|
text
|
||||||
|
|
@ -1451,7 +1512,10 @@ function metadataScopeHintFromRawText(text: string): string | null {
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasExplicitDateScopeLiteral(text: string): boolean {
|
function hasExplicitDateScopeLiteral(text: string): boolean {
|
||||||
return /(?:\b(?:19|20)\d{2}\b|\b\d{4}-\d{2}-\d{2}\b|\b\d{4}-\d{2}\b)/iu.test(text);
|
if (/\b\d{2}\s*(?:\u0433(?:\u043e\u0434(?:\u0430|\u0443)?)?\.?)\b/iu.test(text)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return /(?:\b(?:19|20)\d{2}\b|\b\d{4}-\d{2}-\d{2}\b|\b\d{4}-\d{2}\b|\b\d{2}\s*(?:г(?:од(?:а|у)?)?\.?))\b/iu.test(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
function stripNegatedTaxDateScopeClauses(text: string): string {
|
function stripNegatedTaxDateScopeClauses(text: string): string {
|
||||||
|
|
@ -1483,6 +1547,20 @@ function collectDateScopeFromRawText(text: string): string | null {
|
||||||
if (year?.[1]) {
|
if (year?.[1]) {
|
||||||
return year[1];
|
return year[1];
|
||||||
}
|
}
|
||||||
|
const utf8ShortYear = text.match(/\b(\d{2})\s*(?:\u0433(?:\u043e\u0434(?:\u0430|\u0443)?)?\.?)\b/iu);
|
||||||
|
if (utf8ShortYear?.[1]) {
|
||||||
|
const numericYear = Number.parseInt(utf8ShortYear[1], 10);
|
||||||
|
if (Number.isFinite(numericYear)) {
|
||||||
|
return String(numericYear <= 30 ? 2000 + numericYear : 1900 + numericYear);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const shortYear = text.match(/\b(\d{2})\s*(?:г(?:од(?:а|у)?)?\.?)\b/iu);
|
||||||
|
if (shortYear?.[1]) {
|
||||||
|
const numericYear = Number.parseInt(shortYear[1], 10);
|
||||||
|
if (Number.isFinite(numericYear)) {
|
||||||
|
return String(numericYear <= 30 ? 2000 + numericYear : 1900 + numericYear);
|
||||||
|
}
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1598,6 +1676,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
const reasonCodes: string[] = [];
|
const reasonCodes: string[] = [];
|
||||||
const rawUserText = toNonEmptyString(input.userMessage);
|
const rawUserText = toNonEmptyString(input.userMessage);
|
||||||
const rawEffectiveText = toNonEmptyString(input.effectiveMessage);
|
const rawEffectiveText = toNonEmptyString(input.effectiveMessage);
|
||||||
|
const knownOrganizations = Array.isArray(input.knownOrganizations) ? input.knownOrganizations : [];
|
||||||
const repairedUserText = rawUserText ? repairAddressMojibakeText(rawUserText) : null;
|
const repairedUserText = rawUserText ? repairAddressMojibakeText(rawUserText) : null;
|
||||||
const repairedEffectiveText = rawEffectiveText ? repairAddressMojibakeText(rawEffectiveText) : null;
|
const repairedEffectiveText = rawEffectiveText ? repairAddressMojibakeText(rawEffectiveText) : null;
|
||||||
const rawUserSignalSourceText = repairedUserText ?? rawUserText ?? "";
|
const rawUserSignalSourceText = repairedUserText ?? rawUserText ?? "";
|
||||||
|
|
@ -1742,9 +1821,36 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
)
|
)
|
||||||
? null
|
? null
|
||||||
: rawAssistantTurnMeaningOrganizationScope;
|
: rawAssistantTurnMeaningOrganizationScope;
|
||||||
const rawOrganizationMentionSignal = hasOrganizationScopeSignalUtf8(rawText);
|
const organizationSelectionFromKnown =
|
||||||
const rawOrganizationScope = extractOrganizationScopeFromRawText(rawUserText ?? rawEffectiveText ?? rawSignalSourceText);
|
resolveOrganizationSelectionFromMessage(rawUserText ?? rawEffectiveText ?? rawSignalSourceText, knownOrganizations) ??
|
||||||
const currentTurnFreshOrganizationScope = predecomposeEntities.organization ?? rawOrganizationScope;
|
resolveOrganizationSelectionFromMessage(rawSignalSourceText, knownOrganizations) ??
|
||||||
|
null;
|
||||||
|
const rawOrganizationScope =
|
||||||
|
extractOrganizationScopeFromRawText(rawUserText) ??
|
||||||
|
extractOrganizationScopeFromRawText(rawEffectiveText) ??
|
||||||
|
extractOrganizationScopeFromRawText(rawSignalSourceText);
|
||||||
|
const semanticOrganizationScope =
|
||||||
|
normalizeLooseOrganizationAlias(
|
||||||
|
extractOrganizationScopeFromSemanticText(rawEffectiveText) ??
|
||||||
|
extractOrganizationScopeFromSemanticText(rawUserText) ??
|
||||||
|
extractOrganizationScopeFromSemanticText(rawSignalSourceText)
|
||||||
|
);
|
||||||
|
const predecomposeCounterpartyAsOrganizationScope =
|
||||||
|
(rawBusinessOverviewSignal || seededBusinessOverviewSignal) &&
|
||||||
|
semanticOrganizationScope &&
|
||||||
|
!rawScopedEntityCandidate &&
|
||||||
|
!predecomposeEntities.organization
|
||||||
|
? normalizeLooseOrganizationAlias(predecomposeEntities.counterparty)
|
||||||
|
: null;
|
||||||
|
const rawOrganizationMentionSignal =
|
||||||
|
hasOrganizationScopeSignalUtf8(rawText) ||
|
||||||
|
Boolean(organizationSelectionFromKnown || rawOrganizationScope || semanticOrganizationScope || predecomposeCounterpartyAsOrganizationScope);
|
||||||
|
const currentTurnFreshOrganizationScope =
|
||||||
|
organizationSelectionFromKnown ??
|
||||||
|
predecomposeEntities.organization ??
|
||||||
|
rawOrganizationScope ??
|
||||||
|
semanticOrganizationScope ??
|
||||||
|
predecomposeCounterpartyAsOrganizationScope;
|
||||||
const currentTurnOrganizationScope =
|
const currentTurnOrganizationScope =
|
||||||
currentTurnFreshOrganizationScope ?? assistantTurnMeaningOrganizationScope;
|
currentTurnFreshOrganizationScope ?? assistantTurnMeaningOrganizationScope;
|
||||||
const predecomposeOrganizationMirrorsCounterparty = sameScopedName(
|
const predecomposeOrganizationMirrorsCounterparty = sameScopedName(
|
||||||
|
|
@ -2411,11 +2517,17 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
const clarificationLoopStillNeedsPeriod = Boolean(
|
const clarificationLoopStillNeedsPeriod = Boolean(
|
||||||
followupSeed.loopStatus === "awaiting_clarification" && followupSeed.loopPendingAxes.includes("period")
|
followupSeed.loopStatus === "awaiting_clarification" && followupSeed.loopPendingAxes.includes("period")
|
||||||
);
|
);
|
||||||
|
const rawTwoDigitPredecomposeYearHint = Boolean(
|
||||||
|
predecomposeDateScope &&
|
||||||
|
/^\d{4}$/.test(predecomposeDateScope) &&
|
||||||
|
/\b\d{2}\b/u.test(rawText)
|
||||||
|
);
|
||||||
const businessOverviewRawWithoutDateScope = Boolean(
|
const businessOverviewRawWithoutDateScope = Boolean(
|
||||||
businessOverviewSignal &&
|
businessOverviewSignal &&
|
||||||
!rawAllTimeScopeSignal &&
|
!rawAllTimeScopeSignal &&
|
||||||
!explicitDateScopeLiteralDetected &&
|
!explicitDateScopeLiteralDetected &&
|
||||||
!rawDateScope &&
|
!rawDateScope &&
|
||||||
|
!rawTwoDigitPredecomposeYearHint &&
|
||||||
!relativeCurrentDateHintDetected
|
!relativeCurrentDateHintDetected
|
||||||
);
|
);
|
||||||
const predecomposeDateScopeCountsAsCurrentTurnPeriod = Boolean(
|
const predecomposeDateScopeCountsAsCurrentTurnPeriod = Boolean(
|
||||||
|
|
@ -2879,6 +2991,15 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
if (businessOverviewRawYearOverridesPredecomposeAsOf) {
|
if (businessOverviewRawYearOverridesPredecomposeAsOf) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_business_overview_raw_year_overrode_predecompose_as_of_scope");
|
pushReason(reasonCodes, "mcp_discovery_business_overview_raw_year_overrode_predecompose_as_of_scope");
|
||||||
}
|
}
|
||||||
|
if (organizationSelectionFromKnown) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_organization_scope_from_known_organizations");
|
||||||
|
}
|
||||||
|
if (semanticOrganizationScope) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_organization_scope_from_semantic_text");
|
||||||
|
}
|
||||||
|
if (predecomposeCounterpartyAsOrganizationScope) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_counterparty_reinterpreted_as_organization_scope");
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
!(valueFlowOrganizationStaysScope && normalizedPredecomposeCounterparty === explicitOrganizationScope) &&
|
!(valueFlowOrganizationStaysScope && normalizedPredecomposeCounterparty === explicitOrganizationScope) &&
|
||||||
normalizedPredecomposeCounterparty
|
normalizedPredecomposeCounterparty
|
||||||
|
|
|
||||||
|
|
@ -665,9 +665,26 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
llmPreDecomposeMeta
|
llmPreDecomposeMeta
|
||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
|
const hasCompactCashflowFollowupCue = (value) => {
|
||||||
|
const normalized = normalizeFollowupText(value);
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasCompactCue = /(?:\u043a\u043e\u0440\u043e\u0442\p{L}*|\u043d\u0435\s+\u043e\u0431\u0437\u043e\u0440|\u0431\u0435\u0437\s+\u0442\u043e\u043f\p{L}*)/iu.test(normalized);
|
||||||
|
const hasCashflowCue = /(?:\u0434\u0435\u043d\p{L}*|\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u043f\u043e\u043b\u0443\u0447|\u0437\u0430\u043f\u043b\u0430\u0442|\u043f\u0440\u0438\u0448\p{L}*|\u0443\u0448\p{L}*|\u043d\u0435\u0442\u0442\u043e)/iu.test(normalized);
|
||||||
|
return hasCompactCue && hasCashflowCue;
|
||||||
|
};
|
||||||
|
const compactCashflowFollowupSignal =
|
||||||
|
hasShortValueFlowRetargetCue(userMessage) ||
|
||||||
|
hasCompactCashflowFollowupCue(userMessage) ||
|
||||||
|
(deps.toNonEmptyString(alternateMessage)
|
||||||
|
? hasShortValueFlowRetargetCue(String(alternateMessage ?? "")) ||
|
||||||
|
hasCompactCashflowFollowupCue(String(alternateMessage ?? ""))
|
||||||
|
: false);
|
||||||
if (
|
if (
|
||||||
assistantTurnMeaning?.stale_replay_forbidden === true &&
|
assistantTurnMeaning?.stale_replay_forbidden === true &&
|
||||||
!hasExplicitSummaryBundleReuseSignal(userMessage, alternateMessage)
|
!hasExplicitSummaryBundleReuseSignal(userMessage, alternateMessage) &&
|
||||||
|
!compactCashflowFollowupSignal
|
||||||
) {
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -763,7 +780,8 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
sourceIntentHint === "customer_revenue_and_payments" ||
|
sourceIntentHint === "customer_revenue_and_payments" ||
|
||||||
sourceDiscoveryPilotScopeHint === "counterparty_value_flow_query_movements_v1" ||
|
sourceDiscoveryPilotScopeHint === "counterparty_value_flow_query_movements_v1" ||
|
||||||
sourceDiscoveryPilotScopeHint === "counterparty_supplier_payout_query_movements_v1" ||
|
sourceDiscoveryPilotScopeHint === "counterparty_supplier_payout_query_movements_v1" ||
|
||||||
sourceDiscoveryPilotScopeHint === "counterparty_bidirectional_value_flow_query_movements_v1";
|
sourceDiscoveryPilotScopeHint === "counterparty_bidirectional_value_flow_query_movements_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 = resolveNavigationSessionContextState(
|
||||||
|
|
@ -807,10 +825,12 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
: null;
|
: null;
|
||||||
const debtRoleSwapIntent = debtRoleSwapPrimary ?? debtRoleSwapAlternate ?? null;
|
const debtRoleSwapIntent = debtRoleSwapPrimary ?? debtRoleSwapAlternate ?? null;
|
||||||
const shortValueFlowRetargetPrimary =
|
const shortValueFlowRetargetPrimary =
|
||||||
hasValueFlowCarryoverSourceHint && hasShortValueFlowRetargetCue(userMessage);
|
hasValueFlowCarryoverSourceHint &&
|
||||||
|
(hasShortValueFlowRetargetCue(userMessage) || hasCompactCashflowFollowupCue(userMessage));
|
||||||
const shortValueFlowRetargetAlternate =
|
const shortValueFlowRetargetAlternate =
|
||||||
hasValueFlowCarryoverSourceHint && deps.toNonEmptyString(alternateMessage)
|
hasValueFlowCarryoverSourceHint && deps.toNonEmptyString(alternateMessage)
|
||||||
? hasShortValueFlowRetargetCue(String(alternateMessage ?? ""))
|
? hasShortValueFlowRetargetCue(String(alternateMessage ?? "")) ||
|
||||||
|
hasCompactCashflowFollowupCue(String(alternateMessage ?? ""))
|
||||||
: false;
|
: false;
|
||||||
const businessOverviewBoundaryFollowupPrimary =
|
const businessOverviewBoundaryFollowupPrimary =
|
||||||
hasBusinessOverviewCarryoverSourceHint && hasBusinessOverviewBoundaryFollowupCue(userMessage);
|
hasBusinessOverviewCarryoverSourceHint && hasBusinessOverviewBoundaryFollowupCue(userMessage);
|
||||||
|
|
|
||||||
|
|
@ -218,7 +218,8 @@ describe("assistant address orchestration runtime adapter", () => {
|
||||||
root_filters: expect.objectContaining({
|
root_filters: expect.objectContaining({
|
||||||
organization: "Org A"
|
organization: "Org A"
|
||||||
})
|
})
|
||||||
})
|
}),
|
||||||
|
knownOrganizations: ["Org A"]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -578,6 +578,76 @@ describe("assistant MCP discovery response candidate", () => {
|
||||||
expect(candidate.reply_text).not.toContain("Складской срез");
|
expect(candidate.reply_text).not.toContain("Складской срез");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("lets compact cashflow wording override profit-boundary and overview prose", () => {
|
||||||
|
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||||||
|
entryPoint({
|
||||||
|
turn_input: {
|
||||||
|
adapter_status: "ready",
|
||||||
|
turn_meaning_ref: {
|
||||||
|
raw_message:
|
||||||
|
"\u0421\u043a\u043e\u043b\u044c\u043a\u043e \u0434\u0435\u043d\u0435\u0433 \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u0437\u0430\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0430 \u0437\u0430 2020 \u0433\u043e\u0434? \u041e\u0442\u0432\u0435\u0442\u044c \u043a\u043e\u0440\u043e\u0442\u043a\u043e: \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438, \u0437\u0430\u043f\u043b\u0430\u0442\u0438\u043b\u0438, \u0434\u0435\u043d\u0435\u0436\u043d\u043e\u0435 \u043d\u0435\u0442\u0442\u043e, \u044d\u0442\u043e \u043f\u0440\u0438\u0431\u044b\u043b\u044c \u0438\u043b\u0438 \u043d\u0435\u0442.",
|
||||||
|
asked_action_family: "profit_margin_boundary",
|
||||||
|
unsupported_but_understood_family: "profit_margin_boundary",
|
||||||
|
explicit_organization_scope:
|
||||||
|
"\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441",
|
||||||
|
explicit_date_scope: "2020"
|
||||||
|
},
|
||||||
|
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 руб." }],
|
||||||
|
top_suppliers: [],
|
||||||
|
accounting_financial_result: {
|
||||||
|
final_result_direction: "loss",
|
||||||
|
final_result_amount_human_ru: "7 136 815,85 руб.",
|
||||||
|
period_scope: "2020"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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).not.toContain("СБЕРБАНК");
|
||||||
|
expect(candidate.reply_text).not.toContain("7 136 815,85");
|
||||||
|
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({
|
||||||
|
|
|
||||||
|
|
@ -1789,6 +1789,56 @@ 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("grounds organization aliases from known organizations for clean-session earnings questions", () => {
|
||||||
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
|
userMessage: "скока денег альтернатива заработала за 20 год?",
|
||||||
|
effectiveMessage: "скока денег альтернатива заработала за 20 год?",
|
||||||
|
knownOrganizations: ["ООО Альтернатива Плюс", "ООО Лайсвуд", "РАЙМ"],
|
||||||
|
predecomposeContract: {
|
||||||
|
period: {
|
||||||
|
period_from: "2020-01-01",
|
||||||
|
period_to: "2020-12-31",
|
||||||
|
has_explicit_period: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.adapter_status).toBe("ready");
|
||||||
|
expect(result.should_run_discovery).toBe(true);
|
||||||
|
expect(result.data_need_graph?.business_fact_family).toBe("business_overview");
|
||||||
|
expect(result.turn_meaning_ref?.explicit_organization_scope).toBe("ООО Альтернатива Плюс");
|
||||||
|
expect(result.turn_meaning_ref?.explicit_date_scope).toBe("2020");
|
||||||
|
expect(result.reason_codes).toContain("mcp_discovery_organization_scope_from_known_organizations");
|
||||||
|
expect(result.data_need_graph?.clarification_gaps ?? []).not.toContain("organization");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("treats a business-overview company alias misread as counterparty as organization scope", () => {
|
||||||
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
|
userMessage: "Сколько денег Альтернатива заработала за 2020 год?",
|
||||||
|
effectiveMessage: "Определить финансовый результат компании Альтернатива за 2020 год",
|
||||||
|
assistantTurnMeaning: {
|
||||||
|
asked_domain_family: "business_summary",
|
||||||
|
asked_action_family: "broad_evaluation",
|
||||||
|
unsupported_but_understood_family: "broad_business_evaluation"
|
||||||
|
},
|
||||||
|
predecomposeContract: {
|
||||||
|
entities: { counterparty: "Альтернатива", organization: null },
|
||||||
|
period: {
|
||||||
|
period_from: "2020-01-01",
|
||||||
|
period_to: "2020-12-31",
|
||||||
|
has_explicit_period: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.adapter_status).toBe("ready");
|
||||||
|
expect(result.data_need_graph?.business_fact_family).toBe("business_overview");
|
||||||
|
expect(result.turn_meaning_ref?.explicit_organization_scope).toBe("Альтернатива");
|
||||||
|
expect(result.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||||||
|
expect(result.data_need_graph?.clarification_gaps ?? []).not.toContain("organization");
|
||||||
|
expect(result.reason_codes).toContain("mcp_discovery_counterparty_reinterpreted_as_organization_scope");
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps all-time business overview from reusing a negated VAT period as active scope", () => {
|
it("keeps all-time business overview from reusing a negated VAT period as active scope", () => {
|
||||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
userMessage:
|
userMessage:
|
||||||
|
|
@ -2308,6 +2358,28 @@ describe("assistant MCP discovery turn input adapter", () => {
|
||||||
expect(result.data_need_graph?.reason_codes).toContain("data_need_graph_business_overview_direct_money_answer");
|
expect(result.data_need_graph?.reason_codes).toContain("data_need_graph_business_overview_direct_money_answer");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not inherit ranking when a cashflow follow-up explicitly says no overview or tops", () => {
|
||||||
|
const orgName = "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441";
|
||||||
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
|
userMessage:
|
||||||
|
"\u043d\u0435 \u043e\u0431\u0437\u043e\u0440, \u043f\u0440\u043e\u0441\u0442\u043e \u0434\u0435\u043d\u044c\u0433\u0438: \u043f\u0440\u0438\u0448\u043b\u043e, \u0443\u0448\u043b\u043e, \u043d\u0435\u0442\u0442\u043e \u0437\u0430 2020 \u0431\u0435\u0437 \u0442\u043e\u043f\u043e\u0432",
|
||||||
|
followupContext: {
|
||||||
|
previous_discovery_pilot_scope: "business_overview_route_template_v1",
|
||||||
|
previous_filters: {
|
||||||
|
organization: orgName
|
||||||
|
},
|
||||||
|
previous_seeded_ranking_need: "top_desc"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.adapter_status).toBe("ready");
|
||||||
|
expect(result.should_run_discovery).toBe(true);
|
||||||
|
expect(result.data_need_graph?.business_fact_family).toBe("business_overview");
|
||||||
|
expect(result.data_need_graph?.ranking_need).toBeNull();
|
||||||
|
expect(result.turn_meaning_ref?.explicit_date_scope).toBe("2020");
|
||||||
|
expect(result.data_need_graph?.reason_codes).toContain("data_need_graph_business_overview_direct_money_answer");
|
||||||
|
});
|
||||||
|
|
||||||
it("routes organization-level profit and margin wording to business overview instead of exact value recipes", () => {
|
it("routes organization-level profit and margin wording to business overview instead of exact value recipes", () => {
|
||||||
const orgName = "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441";
|
const orgName = "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441";
|
||||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,66 @@
|
||||||
[
|
[
|
||||||
|
{
|
||||||
|
"generation_id": "gen-ag05231011-cec910",
|
||||||
|
"created_at": "2026-05-23T10:11:40+00:00",
|
||||||
|
"mode": "saved_user_sessions",
|
||||||
|
"title": "AGENT | Cashflow vs profit directness pack",
|
||||||
|
"count": 8,
|
||||||
|
"domain": "autonomy_business_answer_contract",
|
||||||
|
"questions": [
|
||||||
|
"Сколько денег Альтернатива заработала за 2020 год? Ответь коротко: получили, заплатили, денежное нетто, это прибыль или нет.",
|
||||||
|
"скока денег альтернатива заработала за 20 год?",
|
||||||
|
"а это чистая прибыль?",
|
||||||
|
"а по деньгам тогда плюс или минус?",
|
||||||
|
"Теперь дай взрослый обзор за 2020 по компании: входящие, исходящие, нетто, топы, но банк в топах отдельно объясни как финансовый поток.",
|
||||||
|
"а если коротко, сколько заработали деньгами без топов?",
|
||||||
|
"не обзор, просто деньги: пришло, ушло, нетто за 2020",
|
||||||
|
"какая чистая прибыль по Альтернативе за 2020?"
|
||||||
|
],
|
||||||
|
"generated_by": "codex_agent",
|
||||||
|
"saved_case_set_file": "assistant_autogen_saved_user_sessions_20260523101140_gen-ag05231011-cec910.json",
|
||||||
|
"context": {
|
||||||
|
"llm_provider": null,
|
||||||
|
"model": null,
|
||||||
|
"assistant_prompt_version": null,
|
||||||
|
"decomposition_prompt_version": null,
|
||||||
|
"prompt_fingerprint": null,
|
||||||
|
"autogen_personality_id": null,
|
||||||
|
"autogen_personality_prompt": null,
|
||||||
|
"source_session_id": null,
|
||||||
|
"saved_session_file": "assistant_saved_session_20260523101140_gen-ag05231011-cec910.json",
|
||||||
|
"saved_case_set_kind": "agent_semantic_scenario",
|
||||||
|
"agent_run": true,
|
||||||
|
"agent_focus": "Targeted AGENT replay for cashflow-vs-profit directness: colloquial earnings questions must produce a compact money answer, while broad overview remains available only when explicitly requested.",
|
||||||
|
"architecture_phase": "turnaround_11",
|
||||||
|
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\agent_cashflow_profit_directness_20260523.json",
|
||||||
|
"scenario_id": "agent_cashflow_profit_directness_20260523",
|
||||||
|
"semantic_tags": [
|
||||||
|
"bank_flow_boundary",
|
||||||
|
"business_overview",
|
||||||
|
"cashflow_polarity",
|
||||||
|
"cashflow_vs_profit",
|
||||||
|
"clean_profit",
|
||||||
|
"colloquial_earnings",
|
||||||
|
"compact_after_overview",
|
||||||
|
"direct_answer",
|
||||||
|
"direct_money_only",
|
||||||
|
"followup_context",
|
||||||
|
"next_steps",
|
||||||
|
"no_all_time_leak",
|
||||||
|
"no_cashflow_substitution",
|
||||||
|
"no_overview_substitution",
|
||||||
|
"no_profit_substitution",
|
||||||
|
"profit_route",
|
||||||
|
"profit_vs_cashflow",
|
||||||
|
"ranking_suppression",
|
||||||
|
"slang_wording",
|
||||||
|
"temporal_carryover"
|
||||||
|
],
|
||||||
|
"validation_status": "accepted_live_replay",
|
||||||
|
"validated_run_dir": "artifacts\\domain_runs\\agent_cashflow_profit_directness_live11",
|
||||||
|
"saved_after_validated_replay": true
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"generation_id": "gen-ag05230604-098bda",
|
"generation_id": "gen-ag05230604-098bda",
|
||||||
"created_at": "2026-05-23T06:04:40+00:00",
|
"created_at": "2026-05-23T06:04:40+00:00",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,195 @@
|
||||||
|
{
|
||||||
|
"saved_at": "2026-05-23T10:11:40+00:00",
|
||||||
|
"generation_id": "gen-ag05231011-cec910",
|
||||||
|
"mode": "saved_user_sessions",
|
||||||
|
"title": "AGENT | Cashflow vs profit directness pack",
|
||||||
|
"agent_run": true,
|
||||||
|
"questions": [
|
||||||
|
"Сколько денег Альтернатива заработала за 2020 год? Ответь коротко: получили, заплатили, денежное нетто, это прибыль или нет.",
|
||||||
|
"скока денег альтернатива заработала за 20 год?",
|
||||||
|
"а это чистая прибыль?",
|
||||||
|
"а по деньгам тогда плюс или минус?",
|
||||||
|
"Теперь дай взрослый обзор за 2020 по компании: входящие, исходящие, нетто, топы, но банк в топах отдельно объясни как финансовый поток.",
|
||||||
|
"а если коротко, сколько заработали деньгами без топов?",
|
||||||
|
"не обзор, просто деньги: пришло, ушло, нетто за 2020",
|
||||||
|
"какая чистая прибыль по Альтернативе за 2020?"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"assistant_prompt_version": null,
|
||||||
|
"decomposition_prompt_version": null,
|
||||||
|
"prompt_fingerprint": null,
|
||||||
|
"agent_focus": "Targeted AGENT replay for cashflow-vs-profit directness: colloquial earnings questions must produce a compact money answer, while broad overview remains available only when explicitly requested.",
|
||||||
|
"architecture_phase": "turnaround_11",
|
||||||
|
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\agent_cashflow_profit_directness_20260523.json",
|
||||||
|
"scenario_id": "agent_cashflow_profit_directness_20260523",
|
||||||
|
"semantic_tags": [
|
||||||
|
"bank_flow_boundary",
|
||||||
|
"business_overview",
|
||||||
|
"cashflow_polarity",
|
||||||
|
"cashflow_vs_profit",
|
||||||
|
"clean_profit",
|
||||||
|
"colloquial_earnings",
|
||||||
|
"compact_after_overview",
|
||||||
|
"direct_answer",
|
||||||
|
"direct_money_only",
|
||||||
|
"followup_context",
|
||||||
|
"next_steps",
|
||||||
|
"no_all_time_leak",
|
||||||
|
"no_cashflow_substitution",
|
||||||
|
"no_overview_substitution",
|
||||||
|
"no_profit_substitution",
|
||||||
|
"profit_route",
|
||||||
|
"profit_vs_cashflow",
|
||||||
|
"ranking_suppression",
|
||||||
|
"slang_wording",
|
||||||
|
"temporal_carryover"
|
||||||
|
],
|
||||||
|
"validation_status": "accepted_live_replay",
|
||||||
|
"validated_run_dir": "artifacts\\domain_runs\\agent_cashflow_profit_directness_live11",
|
||||||
|
"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_cashflow_profit_directness_live11",
|
||||||
|
"final_status": "accepted",
|
||||||
|
"review_overall_status": "pass",
|
||||||
|
"business_overall_status": "pass",
|
||||||
|
"steps_total": 8,
|
||||||
|
"steps_passed": 8,
|
||||||
|
"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-23T10:11:40+00:00",
|
||||||
|
"reply_type": null,
|
||||||
|
"trace_id": null,
|
||||||
|
"debug": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": "agent-user-002",
|
||||||
|
"role": "user",
|
||||||
|
"text": "скока денег альтернатива заработала за 20 год?",
|
||||||
|
"created_at": "2026-05-23T10:11:40+00:00",
|
||||||
|
"reply_type": null,
|
||||||
|
"trace_id": null,
|
||||||
|
"debug": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": "agent-user-003",
|
||||||
|
"role": "user",
|
||||||
|
"text": "а это чистая прибыль?",
|
||||||
|
"created_at": "2026-05-23T10:11:40+00:00",
|
||||||
|
"reply_type": null,
|
||||||
|
"trace_id": null,
|
||||||
|
"debug": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": "agent-user-004",
|
||||||
|
"role": "user",
|
||||||
|
"text": "а по деньгам тогда плюс или минус?",
|
||||||
|
"created_at": "2026-05-23T10:11:40+00:00",
|
||||||
|
"reply_type": null,
|
||||||
|
"trace_id": null,
|
||||||
|
"debug": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": "agent-user-005",
|
||||||
|
"role": "user",
|
||||||
|
"text": "Теперь дай взрослый обзор за 2020 по компании: входящие, исходящие, нетто, топы, но банк в топах отдельно объясни как финансовый поток.",
|
||||||
|
"created_at": "2026-05-23T10:11:40+00:00",
|
||||||
|
"reply_type": null,
|
||||||
|
"trace_id": null,
|
||||||
|
"debug": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": "agent-user-006",
|
||||||
|
"role": "user",
|
||||||
|
"text": "а если коротко, сколько заработали деньгами без топов?",
|
||||||
|
"created_at": "2026-05-23T10:11:40+00:00",
|
||||||
|
"reply_type": null,
|
||||||
|
"trace_id": null,
|
||||||
|
"debug": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": "agent-user-007",
|
||||||
|
"role": "user",
|
||||||
|
"text": "не обзор, просто деньги: пришло, ушло, нетто за 2020",
|
||||||
|
"created_at": "2026-05-23T10:11:40+00:00",
|
||||||
|
"reply_type": null,
|
||||||
|
"trace_id": null,
|
||||||
|
"debug": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": "agent-user-008",
|
||||||
|
"role": "user",
|
||||||
|
"text": "какая чистая прибыль по Альтернативе за 2020?",
|
||||||
|
"created_at": "2026-05-23T10:11:40+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 cashflow-vs-profit directness: colloquial earnings questions must produce a compact money answer, while broad overview remains available only when explicitly requested.",
|
||||||
|
"architecture_phase": "turnaround_11",
|
||||||
|
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\agent_cashflow_profit_directness_20260523.json",
|
||||||
|
"scenario_id": "agent_cashflow_profit_directness_20260523",
|
||||||
|
"semantic_tags": [
|
||||||
|
"bank_flow_boundary",
|
||||||
|
"business_overview",
|
||||||
|
"cashflow_polarity",
|
||||||
|
"cashflow_vs_profit",
|
||||||
|
"clean_profit",
|
||||||
|
"colloquial_earnings",
|
||||||
|
"compact_after_overview",
|
||||||
|
"direct_answer",
|
||||||
|
"direct_money_only",
|
||||||
|
"followup_context",
|
||||||
|
"next_steps",
|
||||||
|
"no_all_time_leak",
|
||||||
|
"no_cashflow_substitution",
|
||||||
|
"no_overview_substitution",
|
||||||
|
"no_profit_substitution",
|
||||||
|
"profit_route",
|
||||||
|
"profit_vs_cashflow",
|
||||||
|
"ranking_suppression",
|
||||||
|
"slang_wording",
|
||||||
|
"temporal_carryover"
|
||||||
|
],
|
||||||
|
"validation_status": "accepted_live_replay",
|
||||||
|
"validated_run_dir": "artifacts\\domain_runs\\agent_cashflow_profit_directness_live11",
|
||||||
|
"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_cashflow_profit_directness_live11",
|
||||||
|
"final_status": "accepted",
|
||||||
|
"review_overall_status": "pass",
|
||||||
|
"business_overall_status": "pass",
|
||||||
|
"steps_total": 8,
|
||||||
|
"steps_passed": 8,
|
||||||
|
"steps_failed": 0,
|
||||||
|
"steps_with_business_failures": 0,
|
||||||
|
"steps_with_business_warnings": 0,
|
||||||
|
"acceptance_gate_passed": true,
|
||||||
|
"saved_after_validated_replay": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
{
|
||||||
|
"suite_id": "assistant_saved_session_gen-ag05231011-cec910",
|
||||||
|
"suite_version": "0.1.0",
|
||||||
|
"schema_version": "assistant_saved_session_suite_v0_1",
|
||||||
|
"generated_at": "2026-05-23T10:11:40+00:00",
|
||||||
|
"generation_id": "gen-ag05231011-cec910",
|
||||||
|
"mode": "saved_user_sessions",
|
||||||
|
"title": "AGENT | Cashflow vs profit directness pack",
|
||||||
|
"domain": "autonomy_business_answer_contract",
|
||||||
|
"scenario_count": 1,
|
||||||
|
"case_ids": [
|
||||||
|
"SAVED-001"
|
||||||
|
],
|
||||||
|
"cases": [
|
||||||
|
{
|
||||||
|
"case_id": "SAVED-001",
|
||||||
|
"scenario_tag": "agent_saved_user_sessions",
|
||||||
|
"title": "AGENT | Cashflow vs profit directness pack",
|
||||||
|
"question_type": "followup",
|
||||||
|
"broadness_level": "medium",
|
||||||
|
"turns": [
|
||||||
|
{
|
||||||
|
"user_message": "Сколько денег Альтернатива заработала за 2020 год? Ответь коротко: получили, заплатили, денежное нетто, это прибыль или нет."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "скока денег альтернатива заработала за 20 год?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "а это чистая прибыль?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "а по деньгам тогда плюс или минус?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "Теперь дай взрослый обзор за 2020 по компании: входящие, исходящие, нетто, топы, но банк в топах отдельно объясни как финансовый поток."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "а если коротко, сколько заработали деньгами без топов?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "не обзор, просто деньги: пришло, ушло, нетто за 2020"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "какая чистая прибыль по Альтернативе за 2020?"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue