Стабилизировать денежные ответы и прибыль 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);
|
||||
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) {
|
||||
const entities = toRecordObject(predecomposeContract?.entities);
|
||||
return (toNonEmptyString(entities?.organization) ??
|
||||
|
|
@ -223,7 +228,8 @@ async function buildAssistantAddressOrchestrationRuntime(input) {
|
|||
effectiveMessage: addressInputMessage,
|
||||
assistantTurnMeaning: toRecordObject(orchestrationContract?.assistant_turn_meaning),
|
||||
predecomposeContract,
|
||||
followupContext: discoveryFollowupContext
|
||||
followupContext: discoveryFollowupContext,
|
||||
knownOrganizations: sessionKnownOrganizations(input.sessionOrganizationScope ?? null)
|
||||
}));
|
||||
}
|
||||
catch (error) {
|
||||
|
|
|
|||
|
|
@ -115,6 +115,12 @@ function hasBusinessOverviewDirectMoneyAnswerHint(input) {
|
|||
return true;
|
||||
}
|
||||
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);
|
||||
}
|
||||
function timeScopeNeedFor(input) {
|
||||
|
|
@ -225,6 +231,13 @@ function rankingNeedFromRawUtterance(value) {
|
|||
}
|
||||
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) {
|
||||
if (input.clarificationGaps.length > 0) {
|
||||
return "clarification_required";
|
||||
|
|
@ -383,6 +396,7 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
|
|||
const action = lower(turnMeaning?.asked_action_family);
|
||||
const unsupported = lower(turnMeaning?.unsupported_but_understood_family);
|
||||
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 seededRankingNeed = toNonEmptyString(turnMeaning?.seeded_ranking_need);
|
||||
const explicitDateScope = toNonEmptyString(turnMeaning?.explicit_date_scope);
|
||||
|
|
@ -400,15 +414,18 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
|
|||
});
|
||||
const aggregationNeed = aggregationNeedFor(aggregationAxis);
|
||||
const comparisonNeed = comparisonNeedFor(action);
|
||||
const rankingNeed = rankingNeedFromRawUtterance(rawUtterance) ?? seededRankingNeed;
|
||||
const allTimeScopeHint = hasAllTimeScopeHint(rawUtterance);
|
||||
const subjectScopedBidirectionalAllTime = businessFactFamily === "value_flow" &&
|
||||
comparisonNeed === "incoming_vs_outgoing" &&
|
||||
subjectCandidates.length > 0 &&
|
||||
!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({
|
||||
family: businessFactFamily,
|
||||
rawUtterance,
|
||||
rawUtterance: rawQuestionSignal,
|
||||
rankingNeed
|
||||
});
|
||||
const oneSidedOpenScopeTotalHint = hasOpenScopeOneSidedValueTotalHintUtf8Safe(rawUtterance, action);
|
||||
|
|
|
|||
|
|
@ -34,6 +34,30 @@ function requestsFinancialCounterpartyBoundary(turnMeaning, graph) {
|
|||
return (/(?:банк|сбербанк|финанс|кредит|депозит)/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) {
|
||||
if (!Array.isArray(value)) {
|
||||
return [];
|
||||
|
|
@ -595,6 +619,18 @@ function compactComparable(value) {
|
|||
.replace(/\s+/g, " ")
|
||||
.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) {
|
||||
const candidates = uniqueStrings([
|
||||
...toStringList(turnMeaning?.business_overview_separate_entity_candidates),
|
||||
|
|
@ -686,7 +722,7 @@ function buildCompactBusinessOverviewReply(entryPoint, draft) {
|
|||
const netDirection = overview.net_direction === "net_outgoing" ? "операционное нетто в минус" : "расчетное операционное нетто";
|
||||
const period = businessOverviewPeriodText(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 previousCounterpartySummary = buildPreviousCounterpartyValueFlowSummary(toRecordObject(turnMeaning?.previous_counterparty_value_flow_bundle), separateSubject, toRecordObject(turnMeaning?.previous_counterparty_document_bundle));
|
||||
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 vendorRiskBoundary = actionFamily === "vendor_risk_procurement_boundary" || unsupportedFamily === "vendor_risk_procurement_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) {
|
||||
const accountingFinancialResult = toRecordObject(overview.accounting_financial_result);
|
||||
if (accountingFinancialResult) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|||
exports.ASSISTANT_MCP_DISCOVERY_TURN_INPUT_SCHEMA_VERSION = void 0;
|
||||
exports.buildAssistantMcpDiscoveryTurnInput = buildAssistantMcpDiscoveryTurnInput;
|
||||
const assistantMcpDiscoveryDataNeedGraph_1 = require("./assistantMcpDiscoveryDataNeedGraph");
|
||||
const assistantOrganizationMatcher_1 = require("./assistantOrganizationMatcher");
|
||||
const addressTextRepair_1 = require("./addressTextRepair");
|
||||
exports.ASSISTANT_MCP_DISCOVERY_TURN_INPUT_SCHEMA_VERSION = "assistant_mcp_discovery_turn_input_v1";
|
||||
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 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);
|
||||
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 ||
|
||||
hasAnalystContinuationCue ||
|
||||
hasTaxContinuationCue ||
|
||||
hasFinalSummaryCue ||
|
||||
hasMoneyBreakdownCue);
|
||||
hasMoneyBreakdownCue ||
|
||||
hasCashflowPolarityCue);
|
||||
}
|
||||
function hasExplicitVatQuestionSignal(text) {
|
||||
if (!text) {
|
||||
|
|
@ -785,6 +788,9 @@ function hasBusinessOverviewFollowupSeed(followupSeed) {
|
|||
followupSeed.loopSelectedChainId === "business_overview");
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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, "")
|
||||
.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) {
|
||||
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;
|
||||
}
|
||||
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) {
|
||||
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]) {
|
||||
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;
|
||||
}
|
||||
function currentIsoDate() {
|
||||
|
|
@ -1137,6 +1196,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
const reasonCodes = [];
|
||||
const rawUserText = toNonEmptyString(input.userMessage);
|
||||
const rawEffectiveText = toNonEmptyString(input.effectiveMessage);
|
||||
const knownOrganizations = Array.isArray(input.knownOrganizations) ? input.knownOrganizations : [];
|
||||
const repairedUserText = rawUserText ? (0, addressTextRepair_1.repairAddressMojibakeText)(rawUserText) : null;
|
||||
const repairedEffectiveText = rawEffectiveText ? (0, addressTextRepair_1.repairAddressMojibakeText)(rawEffectiveText) : null;
|
||||
const rawUserSignalSourceText = repairedUserText ?? rawUserText ?? "";
|
||||
|
|
@ -1246,9 +1306,28 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
const assistantTurnMeaningOrganizationScope = isReferentialOrganizationPlaceholder(rawAssistantTurnMeaningOrganizationScope)
|
||||
? null
|
||||
: rawAssistantTurnMeaningOrganizationScope;
|
||||
const rawOrganizationMentionSignal = hasOrganizationScopeSignalUtf8(rawText);
|
||||
const rawOrganizationScope = extractOrganizationScopeFromRawText(rawUserText ?? rawEffectiveText ?? rawSignalSourceText);
|
||||
const currentTurnFreshOrganizationScope = predecomposeEntities.organization ?? rawOrganizationScope;
|
||||
const organizationSelectionFromKnown = (0, assistantOrganizationMatcher_1.resolveOrganizationSelectionFromMessage)(rawUserText ?? rawEffectiveText ?? rawSignalSourceText, knownOrganizations) ??
|
||||
(0, assistantOrganizationMatcher_1.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 = currentTurnFreshOrganizationScope ?? assistantTurnMeaningOrganizationScope;
|
||||
const predecomposeOrganizationMirrorsCounterparty = sameScopedName(predecomposeEntities.counterparty, predecomposeEntities.organization);
|
||||
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 rawTwoDigitPredecomposeYearHint = Boolean(predecomposeDateScope &&
|
||||
/^\d{4}$/.test(predecomposeDateScope) &&
|
||||
/\b\d{2}\b/u.test(rawText));
|
||||
const businessOverviewRawWithoutDateScope = Boolean(businessOverviewSignal &&
|
||||
!rawAllTimeScopeSignal &&
|
||||
!explicitDateScopeLiteralDetected &&
|
||||
!rawDateScope &&
|
||||
!rawTwoDigitPredecomposeYearHint &&
|
||||
!relativeCurrentDateHintDetected);
|
||||
const predecomposeDateScopeCountsAsCurrentTurnPeriod = Boolean(predecomposeDateScope &&
|
||||
!isImplicitCurrentDateScope(predecomposeDateScope) &&
|
||||
|
|
@ -2236,6 +2319,15 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
if (businessOverviewRawYearOverridesPredecomposeAsOf) {
|
||||
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) &&
|
||||
normalizedPredecomposeCounterparty) {
|
||||
pushReason(reasonCodes, "mcp_discovery_counterparty_from_predecompose");
|
||||
|
|
|
|||
|
|
@ -489,8 +489,24 @@ function createAssistantTransitionPolicy(deps) {
|
|||
llmPreDecomposeMeta
|
||||
})
|
||||
: 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 &&
|
||||
!hasExplicitSummaryBundleReuseSignal(userMessage, alternateMessage)) {
|
||||
!hasExplicitSummaryBundleReuseSignal(userMessage, alternateMessage) &&
|
||||
!compactCashflowFollowupSignal) {
|
||||
return null;
|
||||
}
|
||||
const latestAddressItem = deps.findLastAddressAssistantItem(items);
|
||||
|
|
@ -555,7 +571,8 @@ function createAssistantTransitionPolicy(deps) {
|
|||
const hasValueFlowCarryoverSourceHint = sourceIntentHint === "customer_revenue_and_payments" ||
|
||||
sourceDiscoveryPilotScopeHint === "counterparty_value_flow_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 navigationSessionState = (0, assistantContinuityPolicy_1.resolveNavigationSessionContextState)(addressNavigationState, deps.toNonEmptyString, deps.normalizeOrganizationScopeValue);
|
||||
const navigationFocusObjectHint = navigationSessionState.focusObject;
|
||||
|
|
@ -579,9 +596,11 @@ function createAssistantTransitionPolicy(deps) {
|
|||
? deps.resolveDebtRoleSwapFollowupIntent(String(alternateMessage ?? ""), sourceIntentHint)
|
||||
: null;
|
||||
const debtRoleSwapIntent = debtRoleSwapPrimary ?? debtRoleSwapAlternate ?? null;
|
||||
const shortValueFlowRetargetPrimary = hasValueFlowCarryoverSourceHint && hasShortValueFlowRetargetCue(userMessage);
|
||||
const shortValueFlowRetargetPrimary = hasValueFlowCarryoverSourceHint &&
|
||||
(hasShortValueFlowRetargetCue(userMessage) || hasCompactCashflowFollowupCue(userMessage));
|
||||
const shortValueFlowRetargetAlternate = hasValueFlowCarryoverSourceHint && deps.toNonEmptyString(alternateMessage)
|
||||
? hasShortValueFlowRetargetCue(String(alternateMessage ?? ""))
|
||||
? hasShortValueFlowRetargetCue(String(alternateMessage ?? "")) ||
|
||||
hasCompactCashflowFollowupCue(String(alternateMessage ?? ""))
|
||||
: false;
|
||||
const businessOverviewBoundaryFollowupPrimary = hasBusinessOverviewCarryoverSourceHint && hasBusinessOverviewBoundaryFollowupCue(userMessage);
|
||||
const businessOverviewBoundaryFollowupAlternate = hasBusinessOverviewCarryoverSourceHint && deps.toNonEmptyString(alternateMessage)
|
||||
|
|
|
|||
|
|
@ -84,6 +84,14 @@ function sessionOrganizationName(
|
|||
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(
|
||||
predecomposeContract: Record<string, unknown> | null,
|
||||
toNonEmptyString: BuildAssistantAddressOrchestrationRuntimeInput["toNonEmptyString"]
|
||||
|
|
@ -410,7 +418,8 @@ export async function buildAssistantAddressOrchestrationRuntime(
|
|||
effectiveMessage: addressInputMessage,
|
||||
assistantTurnMeaning: toRecordObject(orchestrationContract?.assistant_turn_meaning),
|
||||
predecomposeContract,
|
||||
followupContext: discoveryFollowupContext
|
||||
followupContext: discoveryFollowupContext,
|
||||
knownOrganizations: sessionKnownOrganizations(input.sessionOrganizationScope ?? null)
|
||||
})) as Record<string, unknown>;
|
||||
} catch (error) {
|
||||
mcpDiscoveryRuntimeEntryPointError = String(error instanceof Error ? error.message : error ?? "unknown_error").slice(0, 280);
|
||||
|
|
|
|||
|
|
@ -176,6 +176,18 @@ function hasBusinessOverviewDirectMoneyAnswerHint(input: {
|
|||
return true;
|
||||
}
|
||||
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
|
||||
);
|
||||
|
|
@ -330,6 +342,16 @@ function rankingNeedFromRawUtterance(value: string): string | 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: {
|
||||
family: string | null;
|
||||
clarificationGaps: string[];
|
||||
|
|
@ -503,6 +525,7 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
|||
const action = lower(turnMeaning?.asked_action_family);
|
||||
const unsupported = lower(turnMeaning?.unsupported_but_understood_family);
|
||||
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 seededRankingNeed = toNonEmptyString(turnMeaning?.seeded_ranking_need);
|
||||
const explicitDateScope = toNonEmptyString(turnMeaning?.explicit_date_scope);
|
||||
|
|
@ -520,16 +543,22 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
|||
});
|
||||
const aggregationNeed = aggregationNeedFor(aggregationAxis);
|
||||
const comparisonNeed = comparisonNeedFor(action);
|
||||
const rankingNeed = rankingNeedFromRawUtterance(rawUtterance) ?? seededRankingNeed;
|
||||
const allTimeScopeHint = hasAllTimeScopeHint(rawUtterance);
|
||||
const subjectScopedBidirectionalAllTime =
|
||||
businessFactFamily === "value_flow" &&
|
||||
comparisonNeed === "incoming_vs_outgoing" &&
|
||||
subjectCandidates.length > 0 &&
|
||||
!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({
|
||||
family: businessFactFamily,
|
||||
rawUtterance,
|
||||
rawUtterance: rawQuestionSignal,
|
||||
rankingNeed
|
||||
});
|
||||
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[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return [];
|
||||
|
|
@ -689,6 +729,21 @@ function compactComparable(value: string | null): string {
|
|||
.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(
|
||||
graph: Record<string, unknown> | null,
|
||||
turnMeaning: Record<string, unknown> | null,
|
||||
|
|
@ -802,7 +857,7 @@ function buildCompactBusinessOverviewReply(
|
|||
const netDirection = overview.net_direction === "net_outgoing" ? "операционное нетто в минус" : "расчетное операционное нетто";
|
||||
const period = businessOverviewPeriodText(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 previousCounterpartySummary = buildPreviousCounterpartyValueFlowSummary(
|
||||
toRecordObject(turnMeaning?.previous_counterparty_value_flow_bundle),
|
||||
|
|
@ -857,6 +912,28 @@ function buildCompactBusinessOverviewReply(
|
|||
actionFamily === "vendor_risk_procurement_boundary" || unsupportedFamily === "vendor_risk_procurement_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) {
|
||||
const accountingFinancialResult = toRecordObject(overview.accounting_financial_result);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
buildAssistantMcpDiscoveryDataNeedGraph,
|
||||
type AssistantMcpDiscoveryDataNeedGraphContract
|
||||
} from "./assistantMcpDiscoveryDataNeedGraph";
|
||||
import { resolveOrganizationSelectionFromMessage } from "./assistantOrganizationMatcher";
|
||||
import { normalizeRussianComparableText, repairAddressMojibakeText } from "./addressTextRepair";
|
||||
import type {
|
||||
AssistantMcpDiscoveryMetadataRecommendedPrimitive,
|
||||
|
|
@ -27,6 +28,7 @@ export interface BuildAssistantMcpDiscoveryTurnInputAdapterInput {
|
|||
followupContext?: Record<string, unknown> | null;
|
||||
userMessage?: string | null;
|
||||
effectiveMessage?: string | null;
|
||||
knownOrganizations?: unknown[] | null;
|
||||
}
|
||||
|
||||
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(
|
||||
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 ||
|
||||
hasAnalystContinuationCue ||
|
||||
hasTaxContinuationCue ||
|
||||
hasFinalSummaryCue ||
|
||||
hasMoneyBreakdownCue
|
||||
hasMoneyBreakdownCue ||
|
||||
hasCashflowPolarityCue
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1114,6 +1121,13 @@ function hasBusinessOverviewFollowupSeed(followupSeed: ReturnType<typeof collect
|
|||
}
|
||||
|
||||
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(
|
||||
text
|
||||
);
|
||||
|
|
@ -1132,6 +1146,13 @@ function hasPayoutSignal(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(
|
||||
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 {
|
||||
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
|
||||
|
|
@ -1451,7 +1512,10 @@ function metadataScopeHintFromRawText(text: string): string | null {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
|
@ -1483,6 +1547,20 @@ function collectDateScopeFromRawText(text: string): string | null {
|
|||
if (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;
|
||||
}
|
||||
|
||||
|
|
@ -1598,6 +1676,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
const reasonCodes: string[] = [];
|
||||
const rawUserText = toNonEmptyString(input.userMessage);
|
||||
const rawEffectiveText = toNonEmptyString(input.effectiveMessage);
|
||||
const knownOrganizations = Array.isArray(input.knownOrganizations) ? input.knownOrganizations : [];
|
||||
const repairedUserText = rawUserText ? repairAddressMojibakeText(rawUserText) : null;
|
||||
const repairedEffectiveText = rawEffectiveText ? repairAddressMojibakeText(rawEffectiveText) : null;
|
||||
const rawUserSignalSourceText = repairedUserText ?? rawUserText ?? "";
|
||||
|
|
@ -1742,9 +1821,36 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
)
|
||||
? null
|
||||
: rawAssistantTurnMeaningOrganizationScope;
|
||||
const rawOrganizationMentionSignal = hasOrganizationScopeSignalUtf8(rawText);
|
||||
const rawOrganizationScope = extractOrganizationScopeFromRawText(rawUserText ?? rawEffectiveText ?? rawSignalSourceText);
|
||||
const currentTurnFreshOrganizationScope = predecomposeEntities.organization ?? rawOrganizationScope;
|
||||
const organizationSelectionFromKnown =
|
||||
resolveOrganizationSelectionFromMessage(rawUserText ?? rawEffectiveText ?? rawSignalSourceText, knownOrganizations) ??
|
||||
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 =
|
||||
currentTurnFreshOrganizationScope ?? assistantTurnMeaningOrganizationScope;
|
||||
const predecomposeOrganizationMirrorsCounterparty = sameScopedName(
|
||||
|
|
@ -2411,11 +2517,17 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
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 &&
|
||||
!rawAllTimeScopeSignal &&
|
||||
!explicitDateScopeLiteralDetected &&
|
||||
!rawDateScope &&
|
||||
!rawTwoDigitPredecomposeYearHint &&
|
||||
!relativeCurrentDateHintDetected
|
||||
);
|
||||
const predecomposeDateScopeCountsAsCurrentTurnPeriod = Boolean(
|
||||
|
|
@ -2879,6 +2991,15 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
if (businessOverviewRawYearOverridesPredecomposeAsOf) {
|
||||
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) &&
|
||||
normalizedPredecomposeCounterparty
|
||||
|
|
|
|||
|
|
@ -665,9 +665,26 @@ export function createAssistantTransitionPolicy(deps) {
|
|||
llmPreDecomposeMeta
|
||||
})
|
||||
: 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 &&
|
||||
!hasExplicitSummaryBundleReuseSignal(userMessage, alternateMessage)
|
||||
!hasExplicitSummaryBundleReuseSignal(userMessage, alternateMessage) &&
|
||||
!compactCashflowFollowupSignal
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -763,7 +780,8 @@ export function createAssistantTransitionPolicy(deps) {
|
|||
sourceIntentHint === "customer_revenue_and_payments" ||
|
||||
sourceDiscoveryPilotScopeHint === "counterparty_value_flow_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 navigationSessionState = resolveNavigationSessionContextState(
|
||||
|
|
@ -807,10 +825,12 @@ export function createAssistantTransitionPolicy(deps) {
|
|||
: null;
|
||||
const debtRoleSwapIntent = debtRoleSwapPrimary ?? debtRoleSwapAlternate ?? null;
|
||||
const shortValueFlowRetargetPrimary =
|
||||
hasValueFlowCarryoverSourceHint && hasShortValueFlowRetargetCue(userMessage);
|
||||
hasValueFlowCarryoverSourceHint &&
|
||||
(hasShortValueFlowRetargetCue(userMessage) || hasCompactCashflowFollowupCue(userMessage));
|
||||
const shortValueFlowRetargetAlternate =
|
||||
hasValueFlowCarryoverSourceHint && deps.toNonEmptyString(alternateMessage)
|
||||
? hasShortValueFlowRetargetCue(String(alternateMessage ?? ""))
|
||||
? hasShortValueFlowRetargetCue(String(alternateMessage ?? "")) ||
|
||||
hasCompactCashflowFollowupCue(String(alternateMessage ?? ""))
|
||||
: false;
|
||||
const businessOverviewBoundaryFollowupPrimary =
|
||||
hasBusinessOverviewCarryoverSourceHint && hasBusinessOverviewBoundaryFollowupCue(userMessage);
|
||||
|
|
|
|||
|
|
@ -218,7 +218,8 @@ describe("assistant address orchestration runtime adapter", () => {
|
|||
root_filters: expect.objectContaining({
|
||||
organization: "Org A"
|
||||
})
|
||||
})
|
||||
}),
|
||||
knownOrganizations: ["Org A"]
|
||||
})
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -578,6 +578,76 @@ describe("assistant MCP discovery response candidate", () => {
|
|||
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", () => {
|
||||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||||
entryPoint({
|
||||
|
|
|
|||
|
|
@ -1789,6 +1789,56 @@ describe("assistant MCP discovery turn input adapter", () => {
|
|||
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", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
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");
|
||||
});
|
||||
|
||||
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", () => {
|
||||
const orgName = "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441";
|
||||
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",
|
||||
"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