ARCH: добить all-time и period-first clarification loops
This commit is contained in:
parent
053a3bf157
commit
8a1ae696dc
|
|
@ -0,0 +1,70 @@
|
|||
{
|
||||
"schema_version": "domain_truth_harness_spec_v1",
|
||||
"scenario_id": "address_truth_harness_phase47_multi_hop_open_net_period_first_clarification_loop",
|
||||
"domain": "address_phase47_multi_hop_open_net_period_first_clarification_loop",
|
||||
"title": "Phase 47 multi-hop open net period-first clarification loop",
|
||||
"description": "Targeted AGENT replay for Big Block F where an open-scope net question asks for both organization and period, then keeps the same net loop after a period-only clarification, and finally answers after the second clarification provides the organization.",
|
||||
"bindings": {},
|
||||
"steps": [
|
||||
{
|
||||
"step_id": "step_01_open_net_requires_org_and_period",
|
||||
"title": "Open net question asks for both organization and period",
|
||||
"question": "Какое нетто по деньгам: сколько получили и сколько заплатили?",
|
||||
"allowed_reply_types": ["clarification_required", "partial_coverage", "factual_with_explanation"],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)уточн|нужно",
|
||||
"(?i)организац",
|
||||
"(?i)период"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)уточните контрагента",
|
||||
"(?i)не найден контрагент",
|
||||
"(?i)по какому контрагенту",
|
||||
"(?i)не найдено контрагента"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": ["open_scope_net", "multi_hop_clarification", "organization_scope", "period_scope", "bounded_autonomy"]
|
||||
},
|
||||
{
|
||||
"step_id": "step_02_period_only_clarification_keeps_same_net_loop",
|
||||
"title": "Period-only clarification preserves the same net loop and asks only for the organization",
|
||||
"question": "за 2020 год",
|
||||
"allowed_reply_types": ["clarification_required", "partial_coverage"],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)уточн|нужно",
|
||||
"(?i)организац"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)период",
|
||||
"(?i)уточните контрагента",
|
||||
"(?i)не найден контрагент"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": ["open_scope_net", "multi_hop_clarification", "period_followup_reuse", "bounded_autonomy"]
|
||||
},
|
||||
{
|
||||
"step_id": "step_03_org_clarification_completes_same_net_loop",
|
||||
"title": "Organization clarification completes the same net loop and yields a bounded net answer",
|
||||
"question": "по ООО Альтернатива Плюс",
|
||||
"allowed_reply_types": ["factual_with_explanation", "partial_coverage"],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)2020",
|
||||
"(?i)получ|входящ|заплат|исходящ|нетто",
|
||||
"(?i)руб"
|
||||
],
|
||||
"required_answer_patterns_any": [
|
||||
"(?i)альтернатива",
|
||||
"(?i)проверенн|найденн",
|
||||
"(?i)разниц|нетто"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)уточните организацию",
|
||||
"(?i)уточните период",
|
||||
"(?i)уточните контрагента",
|
||||
"(?i)не найден контрагент"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": ["open_scope_net", "multi_hop_clarification", "organization_followup_reuse", "bounded_autonomy"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"schema_version": "domain_truth_harness_spec_v1",
|
||||
"scenario_id": "address_truth_harness_phase48_multi_hop_open_total_all_time_clarification_loop",
|
||||
"domain": "address_phase48_multi_hop_open_total_all_time_clarification_loop",
|
||||
"title": "Phase 48 multi-hop open total all-time clarification loop",
|
||||
"description": "Targeted AGENT replay for Big Block F where an open-scope incoming-total question asks for both organization and period, then keeps the same total loop after an organization-only clarification, and finally accepts 'за все время' as the period clarification that clears the remaining gap and produces a bounded answer.",
|
||||
"bindings": {},
|
||||
"steps": [
|
||||
{
|
||||
"step_id": "step_01_open_total_requires_org_and_period",
|
||||
"title": "Open total question asks for both organization and period",
|
||||
"question": "Сколько вообще входящих денег было?",
|
||||
"allowed_reply_types": ["clarification_required", "partial_coverage", "factual_with_explanation"],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)уточн|нужно",
|
||||
"(?i)организац",
|
||||
"(?i)период"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)уточните контрагента",
|
||||
"(?i)не найден контрагент",
|
||||
"(?i)по какому контрагенту",
|
||||
"(?i)не найдено контрагента"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": ["open_scope_total", "multi_hop_clarification", "organization_scope", "period_scope", "bounded_autonomy"]
|
||||
},
|
||||
{
|
||||
"step_id": "step_02_org_only_clarification_keeps_same_total_loop",
|
||||
"title": "Organization-only clarification preserves the same total loop and asks only for the period",
|
||||
"question": "по ООО Альтернатива Плюс",
|
||||
"allowed_reply_types": ["clarification_required", "partial_coverage"],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)уточн|нужно",
|
||||
"(?i)период"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)организац",
|
||||
"(?i)уточните контрагента",
|
||||
"(?i)не найден контрагент"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": ["open_scope_total", "multi_hop_clarification", "organization_followup_reuse", "bounded_autonomy"]
|
||||
},
|
||||
{
|
||||
"step_id": "step_03_all_time_clarification_completes_same_total_loop",
|
||||
"title": "All-time clarification clears the remaining period gap and yields a bounded total answer",
|
||||
"question": "за все время",
|
||||
"allowed_reply_types": ["factual_with_explanation", "partial_coverage"],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)входящ|получ",
|
||||
"(?i)руб"
|
||||
],
|
||||
"required_answer_patterns_any": [
|
||||
"(?i)все время|доступное время|проверенн",
|
||||
"(?i)альтернатива",
|
||||
"(?i)найденн"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)уточните организацию",
|
||||
"(?i)уточните период",
|
||||
"(?i)уточните контрагента",
|
||||
"(?i)не найден контрагент"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": ["open_scope_total", "all_time_scope", "multi_hop_clarification", "bounded_autonomy"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -65,7 +65,13 @@ const PRIMITIVE_CONTRACTS = [
|
|||
supported_fact_families: ["value_flow", "movement_evidence"],
|
||||
supported_action_families: ["turnover", "payout", "net_value_flow", "list_movements"],
|
||||
planning_tags: ["movement", "comparison", "ranking", "aggregation", "monthly_aggregation"],
|
||||
required_axes_any_of: [["period", "account"], ["period", "counterparty"], ["period", "organization"]],
|
||||
required_axes_any_of: [
|
||||
["period", "account"],
|
||||
["period", "counterparty"],
|
||||
["period", "organization"],
|
||||
["all_time_scope", "counterparty"],
|
||||
["all_time_scope", "organization"]
|
||||
],
|
||||
optional_axes: ["contract", "document", "amount", "item", "warehouse"],
|
||||
output_fact_kinds: ["movement_rows", "turnover", "balance_delta"],
|
||||
evidence_floor: "rows_matched",
|
||||
|
|
@ -93,7 +99,13 @@ const PRIMITIVE_CONTRACTS = [
|
|||
supported_fact_families: ["value_flow"],
|
||||
supported_action_families: ["turnover", "payout", "net_value_flow"],
|
||||
planning_tags: ["aggregation", "ranking", "monthly_aggregation"],
|
||||
required_axes_any_of: [["aggregate_axis", "period"], ["aggregate_axis", "counterparty"], ["aggregate_axis", "account"]],
|
||||
required_axes_any_of: [
|
||||
["aggregate_axis", "period"],
|
||||
["aggregate_axis", "counterparty"],
|
||||
["aggregate_axis", "account"],
|
||||
["aggregate_axis", "counterparty", "all_time_scope"],
|
||||
["aggregate_axis", "organization", "all_time_scope"]
|
||||
],
|
||||
optional_axes: ["organization", "contract", "document", "amount"],
|
||||
output_fact_kinds: ["aggregate_totals", "ranked_axis_values"],
|
||||
evidence_floor: "rows_matched",
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@ function isInternalMechanicsLine(value) {
|
|||
text.includes("runtime_") ||
|
||||
text.includes("planner_") ||
|
||||
text.includes("catalog_") ||
|
||||
text.includes("mcp discovery") ||
|
||||
text.includes("needs more scope before execution") ||
|
||||
text.includes("mcp_execution_performed"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,10 +100,10 @@ function hasOpenScopeOneSidedValueTotalHint(rawUtterance, action) {
|
|||
return false;
|
||||
}
|
||||
if (action === "turnover") {
|
||||
return /(?:\bсколько\s+(?:мы\s+)?(?:получили|получено|входящих(?:\s+денег)?|поступлений|денег\s+пришло)\b|(?:сумма|объем)\s+(?:входящих|поступлений)|поступлений\s+за\b)/iu.test(rawUtterance);
|
||||
return /(?:\bсколько\s+(?:(?:вообще|всего|реально)\s+){0,2}(?:мы\s+)?(?:получили|получено|входящих(?:\s+денег)?(?:\s+было)?|поступлений|денег\s+пришло)\b|(?:сумма|объем)\s+(?:входящих|поступлений)|поступлений\s+за\b)/iu.test(rawUtterance);
|
||||
}
|
||||
if (action === "payout") {
|
||||
return /(?:\bсколько\s+(?:мы\s+)?(?:заплатили|выплатили|потратили|исходящих(?:\s+денег)?|платежей|списаний)\b|(?:сумма|объем)\s+(?:исходящих|платежей|списаний)|(?:платежей|списаний)\s+за\b)/iu.test(rawUtterance);
|
||||
return /(?:\bсколько\s+(?:(?:вообще|всего|реально)\s+){0,2}(?:мы\s+)?(?:заплатили|выплатили|потратили|исходящих(?:\s+денег)?(?:\s+было)?|платежей(?:\s+было)?|списаний(?:\s+было)?)\b|(?:сумма|объем)\s+(?:исходящих|платежей|списаний)|(?:платежей|списаний)\s+за\b)/iu.test(rawUtterance);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -112,10 +112,10 @@ function hasOpenScopeOneSidedValueTotalHintUtf8Safe(rawUtterance, action) {
|
|||
return false;
|
||||
}
|
||||
if (action === "turnover") {
|
||||
return /(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+(?:\u043c\u044b\s+)?(?:\u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438|\u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043e|\u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445(?:\s+\u0434\u0435\u043d\u0435\u0433)?|\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0439|\u0434\u0435\u043d\u0435\u0433\s+\u043f\u0440\u0438\u0448\u043b\u043e)|(?:\u0441\u0443\u043c\u043c\u0430|\u043e\u0431\u044a\u0435\u043c)\s+(?:\u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445|\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0439)|\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0439\s+\u0437\u0430)/u.test(rawUtterance);
|
||||
return /(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+(?:(?:\u0432\u043e\u043e\u0431\u0449\u0435|\u0432\u0441\u0435\u0433\u043e|\u0440\u0435\u0430\u043b\u044c\u043d\u043e)\s+){0,2}(?:\u043c\u044b\s+)?(?:\u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438|\u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043e|\u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445(?:\s+\u0434\u0435\u043d\u0435\u0433)?(?:\s+\u0431\u044b\u043b\u043e)?|\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0439|\u0434\u0435\u043d\u0435\u0433\s+\u043f\u0440\u0438\u0448\u043b\u043e)|(?:\u0441\u0443\u043c\u043c\u0430|\u043e\u0431\u044a\u0435\u043c)\s+(?:\u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445|\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0439)|\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0439\s+\u0437\u0430)/u.test(rawUtterance);
|
||||
}
|
||||
if (action === "payout") {
|
||||
return /(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+(?:\u043c\u044b\s+)?(?:\u0437\u0430\u043f\u043b\u0430\u0442\u0438\u043b\u0438|\u0432\u044b\u043f\u043b\u0430\u0442\u0438\u043b\u0438|\u043f\u043e\u0442\u0440\u0430\u0442\u0438\u043b\u0438|\u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0445(?:\s+\u0434\u0435\u043d\u0435\u0433)?|\u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439|\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0439)|(?:\u0441\u0443\u043c\u043c\u0430|\u043e\u0431\u044a\u0435\u043c)\s+(?:\u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0445|\u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439|\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0439)|(?:\u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439|\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0439)\s+\u0437\u0430)/u.test(rawUtterance);
|
||||
return /(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+(?:(?:\u0432\u043e\u043e\u0431\u0449\u0435|\u0432\u0441\u0435\u0433\u043e|\u0440\u0435\u0430\u043b\u044c\u043d\u043e)\s+){0,2}(?:\u043c\u044b\s+)?(?:\u0437\u0430\u043f\u043b\u0430\u0442\u0438\u043b\u0438|\u0432\u044b\u043f\u043b\u0430\u0442\u0438\u043b\u0438|\u043f\u043e\u0442\u0440\u0430\u0442\u0438\u043b\u0438|\u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0445(?:\s+\u0434\u0435\u043d\u0435\u0433)?(?:\s+\u0431\u044b\u043b\u043e)?|\u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439(?:\s+\u0431\u044b\u043b\u043e)?|\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0439(?:\s+\u0431\u044b\u043b\u043e)?)|(?:\u0441\u0443\u043c\u043c\u0430|\u043e\u0431\u044a\u0435\u043c)\s+(?:\u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0445|\u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439|\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0439)|(?:\u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439|\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0439)\s+\u0437\u0430)/u.test(rawUtterance);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,11 @@ function addScopeAxes(axes, meaning) {
|
|||
pushUnique(axes, "period");
|
||||
}
|
||||
}
|
||||
function addTimeScopeAxes(axes, dataNeedGraph) {
|
||||
if (dataNeedGraph?.time_scope_need === "all_time_scope") {
|
||||
pushUnique(axes, "all_time_scope");
|
||||
}
|
||||
}
|
||||
function includesAny(text, tokens) {
|
||||
return tokens.some((token) => text.includes(token));
|
||||
}
|
||||
|
|
@ -276,6 +281,7 @@ function recipeFor(input) {
|
|||
const axes = [];
|
||||
const requestedAggregationAxis = aggregationAxis(meaning);
|
||||
addScopeAxes(axes, meaning);
|
||||
addTimeScopeAxes(axes, dataNeedGraph);
|
||||
if (graphClarificationGaps.includes("lane_family_choice")) {
|
||||
pushUnique(axes, "lane_family_choice");
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ function hasInternalMechanics(value) {
|
|||
text.includes("planner_") ||
|
||||
text.includes("catalog_") ||
|
||||
text.includes("select ") ||
|
||||
text.includes("mcp discovery") ||
|
||||
text.includes("needs more scope before execution") ||
|
||||
text.includes("mcp_execution_performed"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -768,14 +768,27 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
? null
|
||||
: predecomposeEntities.counterparty;
|
||||
const predecomposeDateScope = collectDateScope(predecomposeContract);
|
||||
const periodClarificationFollowupApplicable = Boolean(followupSeed.domain &&
|
||||
followupSeed.loopStatus === "awaiting_clarification" &&
|
||||
followupSeed.loopPendingAxes.includes("period") &&
|
||||
!rawLifecycleSignal &&
|
||||
!rawMetadataSignal &&
|
||||
(rawAllTimeScopeSignal ||
|
||||
explicitDateScopeLiteralDetected ||
|
||||
rawDateScope ||
|
||||
relativeCurrentDateHintDetected ||
|
||||
(predecomposeDateScope && !isImplicitCurrentDateScope(predecomposeDateScope))));
|
||||
const followupDiscoverySeedApplicable = Boolean(followupSeed.domain &&
|
||||
!rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
(monthlyAggregationSignal ||
|
||||
explicitDateScopeLiteralDetected ||
|
||||
predecomposeDateScope ||
|
||||
explicitOrganizationScopeSignal ||
|
||||
organizationClarificationFollowupApplicable));
|
||||
!rawMetadataSignal &&
|
||||
(periodClarificationFollowupApplicable ||
|
||||
(!rawValueFlowSignal &&
|
||||
(monthlyAggregationSignal ||
|
||||
rawAllTimeScopeSignal ||
|
||||
explicitDateScopeLiteralDetected ||
|
||||
predecomposeDateScope ||
|
||||
explicitOrganizationScopeSignal ||
|
||||
organizationClarificationFollowupApplicable))));
|
||||
const metadataFollowupSeedApplicable = Boolean(followupSeed.domain === "metadata" &&
|
||||
!rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
|
|
@ -1197,6 +1210,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
groundedValueFlowFollowupApplicable,
|
||||
forceDiscoveryOverExplicitIntent: Boolean(entityResolutionClarificationCandidate) ||
|
||||
organizationClarificationFollowupApplicable ||
|
||||
periodClarificationFollowupApplicable ||
|
||||
metadataAmbiguityLaneClarificationApplicable ||
|
||||
metadataGroundedMovementLaneApplicable ||
|
||||
metadataGroundedDocumentLaneApplicable ||
|
||||
|
|
@ -1249,6 +1263,9 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
if (organizationClarificationFollowupApplicable) {
|
||||
pushReason(reasonCodes, "mcp_discovery_organization_clarification_followup_from_followup_context");
|
||||
}
|
||||
if (periodClarificationFollowupApplicable) {
|
||||
pushReason(reasonCodes, "mcp_discovery_period_clarification_followup_from_followup_context");
|
||||
}
|
||||
if (payoutSignal) {
|
||||
pushReason(reasonCodes, "mcp_discovery_payout_signal_detected");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,7 +98,13 @@ const PRIMITIVE_CONTRACTS: AssistantMcpCatalogPrimitiveContract[] = [
|
|||
supported_fact_families: ["value_flow", "movement_evidence"],
|
||||
supported_action_families: ["turnover", "payout", "net_value_flow", "list_movements"],
|
||||
planning_tags: ["movement", "comparison", "ranking", "aggregation", "monthly_aggregation"],
|
||||
required_axes_any_of: [["period", "account"], ["period", "counterparty"], ["period", "organization"]],
|
||||
required_axes_any_of: [
|
||||
["period", "account"],
|
||||
["period", "counterparty"],
|
||||
["period", "organization"],
|
||||
["all_time_scope", "counterparty"],
|
||||
["all_time_scope", "organization"]
|
||||
],
|
||||
optional_axes: ["contract", "document", "amount", "item", "warehouse"],
|
||||
output_fact_kinds: ["movement_rows", "turnover", "balance_delta"],
|
||||
evidence_floor: "rows_matched",
|
||||
|
|
@ -126,7 +132,13 @@ const PRIMITIVE_CONTRACTS: AssistantMcpCatalogPrimitiveContract[] = [
|
|||
supported_fact_families: ["value_flow"],
|
||||
supported_action_families: ["turnover", "payout", "net_value_flow"],
|
||||
planning_tags: ["aggregation", "ranking", "monthly_aggregation"],
|
||||
required_axes_any_of: [["aggregate_axis", "period"], ["aggregate_axis", "counterparty"], ["aggregate_axis", "account"]],
|
||||
required_axes_any_of: [
|
||||
["aggregate_axis", "period"],
|
||||
["aggregate_axis", "counterparty"],
|
||||
["aggregate_axis", "account"],
|
||||
["aggregate_axis", "counterparty", "all_time_scope"],
|
||||
["aggregate_axis", "organization", "all_time_scope"]
|
||||
],
|
||||
optional_axes: ["organization", "contract", "document", "amount"],
|
||||
output_fact_kinds: ["aggregate_totals", "ranked_axis_values"],
|
||||
evidence_floor: "rows_matched",
|
||||
|
|
|
|||
|
|
@ -153,12 +153,12 @@ function hasOpenScopeOneSidedValueTotalHint(rawUtterance: string, action: string
|
|||
return false;
|
||||
}
|
||||
if (action === "turnover") {
|
||||
return /(?:\bсколько\s+(?:мы\s+)?(?:получили|получено|входящих(?:\s+денег)?|поступлений|денег\s+пришло)\b|(?:сумма|объем)\s+(?:входящих|поступлений)|поступлений\s+за\b)/iu.test(
|
||||
return /(?:\bсколько\s+(?:(?:вообще|всего|реально)\s+){0,2}(?:мы\s+)?(?:получили|получено|входящих(?:\s+денег)?(?:\s+было)?|поступлений|денег\s+пришло)\b|(?:сумма|объем)\s+(?:входящих|поступлений)|поступлений\s+за\b)/iu.test(
|
||||
rawUtterance
|
||||
);
|
||||
}
|
||||
if (action === "payout") {
|
||||
return /(?:\bсколько\s+(?:мы\s+)?(?:заплатили|выплатили|потратили|исходящих(?:\s+денег)?|платежей|списаний)\b|(?:сумма|объем)\s+(?:исходящих|платежей|списаний)|(?:платежей|списаний)\s+за\b)/iu.test(
|
||||
return /(?:\bсколько\s+(?:(?:вообще|всего|реально)\s+){0,2}(?:мы\s+)?(?:заплатили|выплатили|потратили|исходящих(?:\s+денег)?(?:\s+было)?|платежей(?:\s+было)?|списаний(?:\s+было)?)\b|(?:сумма|объем)\s+(?:исходящих|платежей|списаний)|(?:платежей|списаний)\s+за\b)/iu.test(
|
||||
rawUtterance
|
||||
);
|
||||
}
|
||||
|
|
@ -170,12 +170,12 @@ function hasOpenScopeOneSidedValueTotalHintUtf8Safe(rawUtterance: string, action
|
|||
return false;
|
||||
}
|
||||
if (action === "turnover") {
|
||||
return /(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+(?:\u043c\u044b\s+)?(?:\u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438|\u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043e|\u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445(?:\s+\u0434\u0435\u043d\u0435\u0433)?|\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0439|\u0434\u0435\u043d\u0435\u0433\s+\u043f\u0440\u0438\u0448\u043b\u043e)|(?:\u0441\u0443\u043c\u043c\u0430|\u043e\u0431\u044a\u0435\u043c)\s+(?:\u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445|\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0439)|\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0439\s+\u0437\u0430)/u.test(
|
||||
return /(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+(?:(?:\u0432\u043e\u043e\u0431\u0449\u0435|\u0432\u0441\u0435\u0433\u043e|\u0440\u0435\u0430\u043b\u044c\u043d\u043e)\s+){0,2}(?:\u043c\u044b\s+)?(?:\u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438|\u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043e|\u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445(?:\s+\u0434\u0435\u043d\u0435\u0433)?(?:\s+\u0431\u044b\u043b\u043e)?|\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0439|\u0434\u0435\u043d\u0435\u0433\s+\u043f\u0440\u0438\u0448\u043b\u043e)|(?:\u0441\u0443\u043c\u043c\u0430|\u043e\u0431\u044a\u0435\u043c)\s+(?:\u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445|\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0439)|\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0439\s+\u0437\u0430)/u.test(
|
||||
rawUtterance
|
||||
);
|
||||
}
|
||||
if (action === "payout") {
|
||||
return /(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+(?:\u043c\u044b\s+)?(?:\u0437\u0430\u043f\u043b\u0430\u0442\u0438\u043b\u0438|\u0432\u044b\u043f\u043b\u0430\u0442\u0438\u043b\u0438|\u043f\u043e\u0442\u0440\u0430\u0442\u0438\u043b\u0438|\u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0445(?:\s+\u0434\u0435\u043d\u0435\u0433)?|\u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439|\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0439)|(?:\u0441\u0443\u043c\u043c\u0430|\u043e\u0431\u044a\u0435\u043c)\s+(?:\u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0445|\u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439|\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0439)|(?:\u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439|\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0439)\s+\u0437\u0430)/u.test(
|
||||
return /(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+(?:(?:\u0432\u043e\u043e\u0431\u0449\u0435|\u0432\u0441\u0435\u0433\u043e|\u0440\u0435\u0430\u043b\u044c\u043d\u043e)\s+){0,2}(?:\u043c\u044b\s+)?(?:\u0437\u0430\u043f\u043b\u0430\u0442\u0438\u043b\u0438|\u0432\u044b\u043f\u043b\u0430\u0442\u0438\u043b\u0438|\u043f\u043e\u0442\u0440\u0430\u0442\u0438\u043b\u0438|\u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0445(?:\s+\u0434\u0435\u043d\u0435\u0433)?(?:\s+\u0431\u044b\u043b\u043e)?|\u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439(?:\s+\u0431\u044b\u043b\u043e)?|\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0439(?:\s+\u0431\u044b\u043b\u043e)?)|(?:\u0441\u0443\u043c\u043c\u0430|\u043e\u0431\u044a\u0435\u043c)\s+(?:\u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0445|\u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439|\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0439)|(?:\u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439|\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0439)\s+\u0437\u0430)/u.test(
|
||||
rawUtterance
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,6 +150,15 @@ function addScopeAxes(axes: string[], meaning: AssistantMcpDiscoveryTurnMeaningR
|
|||
}
|
||||
}
|
||||
|
||||
function addTimeScopeAxes(
|
||||
axes: string[],
|
||||
dataNeedGraph: AssistantMcpDiscoveryDataNeedGraphContract | null | undefined
|
||||
): void {
|
||||
if (dataNeedGraph?.time_scope_need === "all_time_scope") {
|
||||
pushUnique(axes, "all_time_scope");
|
||||
}
|
||||
}
|
||||
|
||||
function includesAny(text: string, tokens: string[]): boolean {
|
||||
return tokens.some((token) => text.includes(token));
|
||||
}
|
||||
|
|
@ -417,6 +426,7 @@ function recipeFor(input: AssistantMcpDiscoveryPlannerInput): PlannerRecipe {
|
|||
const axes: string[] = [];
|
||||
const requestedAggregationAxis = aggregationAxis(meaning);
|
||||
addScopeAxes(axes, meaning);
|
||||
addTimeScopeAxes(axes, dataNeedGraph);
|
||||
|
||||
if (graphClarificationGaps.includes("lane_family_choice")) {
|
||||
pushUnique(axes, "lane_family_choice");
|
||||
|
|
|
|||
|
|
@ -1053,15 +1053,30 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
? null
|
||||
: predecomposeEntities.counterparty;
|
||||
const predecomposeDateScope = collectDateScope(predecomposeContract);
|
||||
const periodClarificationFollowupApplicable = Boolean(
|
||||
followupSeed.domain &&
|
||||
followupSeed.loopStatus === "awaiting_clarification" &&
|
||||
followupSeed.loopPendingAxes.includes("period") &&
|
||||
!rawLifecycleSignal &&
|
||||
!rawMetadataSignal &&
|
||||
(rawAllTimeScopeSignal ||
|
||||
explicitDateScopeLiteralDetected ||
|
||||
rawDateScope ||
|
||||
relativeCurrentDateHintDetected ||
|
||||
(predecomposeDateScope && !isImplicitCurrentDateScope(predecomposeDateScope)))
|
||||
);
|
||||
const followupDiscoverySeedApplicable = Boolean(
|
||||
followupSeed.domain &&
|
||||
!rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
(monthlyAggregationSignal ||
|
||||
explicitDateScopeLiteralDetected ||
|
||||
predecomposeDateScope ||
|
||||
explicitOrganizationScopeSignal ||
|
||||
organizationClarificationFollowupApplicable)
|
||||
!rawMetadataSignal &&
|
||||
(periodClarificationFollowupApplicable ||
|
||||
(!rawValueFlowSignal &&
|
||||
(monthlyAggregationSignal ||
|
||||
rawAllTimeScopeSignal ||
|
||||
explicitDateScopeLiteralDetected ||
|
||||
predecomposeDateScope ||
|
||||
explicitOrganizationScopeSignal ||
|
||||
organizationClarificationFollowupApplicable)))
|
||||
);
|
||||
const metadataFollowupSeedApplicable = Boolean(
|
||||
followupSeed.domain === "metadata" &&
|
||||
|
|
@ -1563,6 +1578,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
forceDiscoveryOverExplicitIntent:
|
||||
Boolean(entityResolutionClarificationCandidate) ||
|
||||
organizationClarificationFollowupApplicable ||
|
||||
periodClarificationFollowupApplicable ||
|
||||
metadataAmbiguityLaneClarificationApplicable ||
|
||||
metadataGroundedMovementLaneApplicable ||
|
||||
metadataGroundedDocumentLaneApplicable ||
|
||||
|
|
@ -1616,6 +1632,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
if (organizationClarificationFollowupApplicable) {
|
||||
pushReason(reasonCodes, "mcp_discovery_organization_clarification_followup_from_followup_context");
|
||||
}
|
||||
if (periodClarificationFollowupApplicable) {
|
||||
pushReason(reasonCodes, "mcp_discovery_period_clarification_followup_from_followup_context");
|
||||
}
|
||||
if (payoutSignal) {
|
||||
pushReason(reasonCodes, "mcp_discovery_payout_signal_detected");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,17 @@ describe("assistant MCP catalog index", () => {
|
|||
expect(primitives).toEqual(["resolve_entity_reference", "query_documents", "probe_coverage"]);
|
||||
});
|
||||
|
||||
it("treats all-time organization-scoped value-flow as catalog-compatible without inventing a fake period axis", () => {
|
||||
const primitives = searchAssistantMcpCatalogPrimitivesByFactAxis({
|
||||
business_fact_family: "value_flow",
|
||||
action_family: "turnover",
|
||||
has_subject_candidates: false,
|
||||
required_axes: ["organization", "all_time_scope", "aggregate_axis", "amount", "coverage_target"]
|
||||
});
|
||||
|
||||
expect(primitives).toEqual(["query_movements", "aggregate_by_axis", "probe_coverage"]);
|
||||
});
|
||||
|
||||
it("can search reviewed primitives directly from a confirmed document metadata surface", () => {
|
||||
const primitives = searchAssistantMcpCatalogPrimitivesByMetadataSurface({
|
||||
downstream_route_family: "document_evidence",
|
||||
|
|
@ -150,6 +161,25 @@ describe("assistant MCP catalog index", () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it("marks an all-time organization-scoped value-flow plan as catalog-compatible without requiring an explicit period", () => {
|
||||
const plan = buildAssistantMcpDiscoveryPlan({
|
||||
semanticDataNeed: "organization-scoped value-flow evidence",
|
||||
turnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "turnover",
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс"
|
||||
},
|
||||
proposedPrimitives: ["query_movements", "aggregate_by_axis", "probe_coverage"],
|
||||
requiredAxes: ["organization", "all_time_scope", "aggregate_axis", "amount", "coverage_target"]
|
||||
});
|
||||
|
||||
const review = reviewAssistantMcpDiscoveryPlanAgainstCatalog(plan);
|
||||
|
||||
expect(review.review_status).toBe("catalog_compatible");
|
||||
expect(review.reason_codes).toContain("catalog_plan_compatible");
|
||||
expect(review.missing_axes_by_primitive).toEqual({});
|
||||
});
|
||||
|
||||
it("preserves source-summary evidence floors for metadata and coverage primitives", () => {
|
||||
expect(getAssistantMcpCatalogPrimitive("inspect_1c_metadata").evidence_floor).toBe("source_summary");
|
||||
expect(getAssistantMcpCatalogPrimitive("probe_coverage").evidence_floor).toBe("source_summary");
|
||||
|
|
|
|||
|
|
@ -182,6 +182,29 @@ describe("assistant MCP discovery data need graph", () => {
|
|||
expect(result.reason_codes).toContain("data_need_graph_open_scope_total_needs_organization");
|
||||
});
|
||||
|
||||
it("treats colloquial open incoming total wording with filler words as an open-scope ask rather than a missing subject", () => {
|
||||
const result = buildAssistantMcpDiscoveryDataNeedGraph({
|
||||
semanticDataNeed: "counterparty value-flow evidence",
|
||||
rawUtterance: "сколько вообще входящих денег было?",
|
||||
turnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "turnover"
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.business_fact_family).toBe("value_flow");
|
||||
expect(result.subject_candidates).toEqual([]);
|
||||
expect(result.clarification_gaps).toEqual(["organization", "period"]);
|
||||
expect(result.proof_expectation).toBe("clarification_required");
|
||||
expect(result.decomposition_candidates).toEqual([
|
||||
"collect_scoped_movements",
|
||||
"aggregate_checked_amounts",
|
||||
"probe_coverage"
|
||||
]);
|
||||
expect(result.reason_codes).toContain("data_need_graph_open_scope_total_without_subject");
|
||||
expect(result.reason_codes).toContain("data_need_graph_open_scope_total_needs_organization");
|
||||
});
|
||||
|
||||
it("treats all-time open-scope totals as an open-ended period rather than a missing period", () => {
|
||||
const result = buildAssistantMcpDiscoveryDataNeedGraph({
|
||||
semanticDataNeed: "counterparty value-flow evidence",
|
||||
|
|
|
|||
|
|
@ -779,4 +779,48 @@ describe("assistant MCP discovery planner", () => {
|
|||
expect(result.reason_codes).toContain("planner_selected_open_scope_value_flow_total_from_data_need_graph");
|
||||
expect(result.selected_chain_id).not.toBe("entity_resolution");
|
||||
});
|
||||
|
||||
it("treats all-time organization-scoped open totals as execution-ready without reintroducing a fake period gap", () => {
|
||||
const result = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: [],
|
||||
business_fact_family: "value_flow",
|
||||
action_family: "turnover",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "all_time_scope",
|
||||
comparison_need: null,
|
||||
ranking_need: null,
|
||||
proof_expectation: "coverage_checked_fact",
|
||||
clarification_gaps: [],
|
||||
decomposition_candidates: ["collect_scoped_movements", "aggregate_checked_amounts", "probe_coverage"],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: [
|
||||
"data_need_graph_built",
|
||||
"data_need_graph_open_scope_total_without_subject",
|
||||
"data_need_graph_all_time_scope_hint"
|
||||
]
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "turnover",
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс"
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.planner_status).toBe("ready_for_execution");
|
||||
expect(result.semantic_data_need).toBe("organization-scoped value-flow evidence");
|
||||
expect(result.selected_chain_id).toBe("value_flow");
|
||||
expect(result.proposed_primitives).toEqual(["query_movements", "aggregate_by_axis", "probe_coverage"]);
|
||||
expect(result.required_axes).toEqual([
|
||||
"organization",
|
||||
"all_time_scope",
|
||||
"aggregate_axis",
|
||||
"amount",
|
||||
"coverage_target"
|
||||
]);
|
||||
expect(result.catalog_review.review_status).toBe("catalog_compatible");
|
||||
expect(result.reason_codes).toContain("planner_selected_open_scope_value_flow_total_from_data_need_graph");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1500,6 +1500,27 @@ describe("assistant MCP discovery turn input adapter", () => {
|
|||
expect(result.data_need_graph?.reason_codes).toContain("data_need_graph_open_scope_total_needs_organization");
|
||||
});
|
||||
|
||||
it("treats colloquial open incoming total wording with filler words as an open-scope ask that needs organization and period", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage: "сколько вообще входящих денег было?"
|
||||
});
|
||||
|
||||
expect(result.adapter_status).toBe("ready");
|
||||
expect(result.should_run_discovery).toBe(true);
|
||||
expect(result.semantic_data_need).toBe("counterparty value-flow evidence");
|
||||
expect(result.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "turnover",
|
||||
unsupported_but_understood_family: "counterparty_value_or_turnover",
|
||||
stale_replay_forbidden: true
|
||||
});
|
||||
expect(result.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||||
expect(result.data_need_graph?.subject_candidates).toEqual([]);
|
||||
expect(result.data_need_graph?.clarification_gaps).toEqual(["organization", "period"]);
|
||||
expect(result.data_need_graph?.reason_codes).toContain("data_need_graph_open_scope_total_without_subject");
|
||||
expect(result.data_need_graph?.reason_codes).toContain("data_need_graph_open_scope_total_needs_organization");
|
||||
});
|
||||
|
||||
it("resumes an open-scope incoming total from follow-up context when the user clarifies only the organization", () => {
|
||||
const orgName = "ООО Альтернатива Плюс";
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
|
|
@ -1564,6 +1585,46 @@ describe("assistant MCP discovery turn input adapter", () => {
|
|||
expect(result.data_need_graph?.reason_codes).toContain("data_need_graph_all_time_scope_hint");
|
||||
});
|
||||
|
||||
it("resumes an open-scope total clarification loop from saved state when the user resolves the pending period with all-time wording", () => {
|
||||
const orgName = "ООО Альтернатива Плюс";
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage: "за все время",
|
||||
assistantTurnMeaning: {
|
||||
explicit_intent_candidate: "customer_revenue_and_payments"
|
||||
},
|
||||
followupContext: {
|
||||
previous_discovery_loop_status: "awaiting_clarification",
|
||||
previous_discovery_loop_selected_chain_id: "value_flow",
|
||||
previous_discovery_loop_pending_axes: ["period"],
|
||||
previous_discovery_loop_provided_axes: ["organization", "aggregate_axis", "amount", "coverage_target"],
|
||||
previous_discovery_loop_asked_domain_family: "counterparty_value",
|
||||
previous_discovery_loop_asked_action_family: "turnover",
|
||||
previous_discovery_loop_unsupported_family: "counterparty_value_or_turnover",
|
||||
previous_filters: {
|
||||
organization: orgName
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.adapter_status).toBe("ready");
|
||||
expect(result.should_run_discovery).toBe(true);
|
||||
expect(result.reason_codes).toContain("mcp_discovery_all_time_scope_signal_detected");
|
||||
expect(result.reason_codes).toContain("mcp_discovery_period_clarification_followup_from_followup_context");
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_not_applicable_for_supported_exact_turn");
|
||||
expect(result.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "turnover",
|
||||
explicit_organization_scope: orgName,
|
||||
unsupported_but_understood_family: "counterparty_value_or_turnover",
|
||||
stale_replay_forbidden: true
|
||||
});
|
||||
expect(result.turn_meaning_ref?.explicit_date_scope).toBeUndefined();
|
||||
expect(result.data_need_graph?.clarification_gaps).toEqual([]);
|
||||
expect(result.data_need_graph?.time_scope_need).toBe("all_time_scope");
|
||||
expect(result.data_need_graph?.reason_codes).toContain("data_need_graph_open_scope_total_without_subject");
|
||||
expect(result.data_need_graph?.reason_codes).toContain("data_need_graph_all_time_scope_hint");
|
||||
});
|
||||
|
||||
it("resumes an open-scope ranking from follow-up context when the user clarifies only the organization", () => {
|
||||
const orgName = "ООО Альтернатива Плюс";
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
|
|
|
|||
Loading…
Reference in New Issue