ARCH: добавить двусторонний MCP discovery для нетто-потока
This commit is contained in:
parent
52da709671
commit
99a568241d
|
|
@ -1212,6 +1212,55 @@ Module progress:
|
|||
|
||||
- Big Block 5 MCP Semantic Data Agent: `98%`.
|
||||
|
||||
## Progress Update - 2026-04-20 MCP Discovery Bidirectional Net Value-Flow Composition
|
||||
|
||||
The eighteenth implementation slice of Big Block 5 adds the first composed MCP discovery value-flow answer.
|
||||
|
||||
Before this slice the assistant could recover two separate guarded paths:
|
||||
|
||||
- incoming customer revenue/value-flow through `customer_revenue_and_payments`;
|
||||
- outgoing supplier payments/write-offs through `supplier_payouts_profile`.
|
||||
|
||||
The new slice lets one current user question ask for both sides and the net result without adding a one-off hardcoded route.
|
||||
|
||||
New behavior:
|
||||
|
||||
- net/bidirectional wording such as `нетто`, `сальдо`, `взаиморасчет`, `получили и заплатили`, `incoming and outgoing`, and `received and paid` is recognized as `asked_action_family=net_value_flow`;
|
||||
- the pilot scope `counterparty_bidirectional_value_flow_query_movements_v1` executes both reviewed movement profiles through the same guarded `query_movements` primitive family;
|
||||
- runtime derives `derived_bidirectional_value_flow` with incoming amount, outgoing amount, net amount, side-specific row counts, first/latest dates, and side-specific probe-limit coverage;
|
||||
- the user-facing answer says what was received, what was paid, and what net value can be inferred from found rows;
|
||||
- if either side reaches the probe row limit, the answer explicitly says the full requested period is not proven and the net value is only a found-row calculation;
|
||||
- the final response candidate still filters internal primitive/query/runtime/planner/catalog/pilot mechanics.
|
||||
|
||||
Replay result:
|
||||
|
||||
- `address_truth_harness_phase19_mcp_discovery_response_gate_live_rerun10` passed 7/7 after adding the new net-flow step;
|
||||
- `address_truth_harness_phase19_mcp_discovery_response_gate_live_rerun11` passed 7/7 after answer punctuation cleanup;
|
||||
- final status: `accepted`;
|
||||
- the net-flow step answered through `mcp_discovery_response_candidate_guarded`;
|
||||
- the bidirectional pilot scope was `counterparty_bidirectional_value_flow_query_movements_v1`;
|
||||
- confirmed incoming found-row amount for `Группа СВК` in `2020`: `47 628 853,03 руб.`;
|
||||
- confirmed outgoing found-row amount for `Группа СВК` in `2020`: `19 568 878,06 руб.`;
|
||||
- found-row net in our favor: `28 059 974,97 руб.`;
|
||||
- incoming coverage: `44 из 44` rows, first date `2020-01-09`, latest `2020-12-30`;
|
||||
- outgoing coverage: `100 из 100` rows, first date `2020-01-09`, latest `2020-03-16`;
|
||||
- because the outgoing side reached the row limit, the answer marks full-period coverage as unproven;
|
||||
- user-facing text has no `query_documents`, `query_movements`, `runtime_`, `planner_`, `catalog_`, `primitive`, or `pilot_` leak.
|
||||
|
||||
Validation:
|
||||
|
||||
- `npm test -- assistantMcpDiscoveryTurnInputAdapter.test.ts assistantMcpDiscoveryPlanner.test.ts assistantMcpDiscoveryPilotExecutor.test.ts assistantMcpDiscoveryAnswerAdapter.test.ts assistantMcpDiscoveryResponseCandidate.test.ts assistantMcpDiscoveryResponsePolicy.test.ts assistantMcpDiscoveryRuntimeEntryPoint.test.ts assistantMcpDiscoveryRuntimeBridge.test.ts assistantAddressLaneResponseRuntimeAdapter.test.ts assistantDeepTurnResponseRuntimeAdapter.test.ts assistantLivingChatRuntimeAdapter.test.ts assistantAddressOrchestrationRuntimeAdapter.test.ts assistantDebugPayloadAssembler.test.ts` passed 85/85;
|
||||
- `npm run build` passed;
|
||||
- `python scripts/domain_truth_harness.py run-live --spec docs/orchestration/address_truth_harness_phase19_mcp_discovery_response_gate.json --output-dir artifacts/domain_runs/address_truth_harness_phase19_mcp_discovery_response_gate_live_rerun11 --timeout-seconds 180` passed 7/7, final status `accepted`.
|
||||
|
||||
Known remaining boundary:
|
||||
|
||||
- this is still profile-composed discovery, not arbitrary self-navigation through every 1C register. Richer coverage proofs, follow-up drilldown over discovered rows, and Qwen-planned multi-axis exploration remain future work.
|
||||
|
||||
Module progress:
|
||||
|
||||
- Big Block 5 MCP Semantic Data Agent: `99%`.
|
||||
|
||||
## Execution Rule
|
||||
|
||||
Do not implement this plan as:
|
||||
|
|
|
|||
|
|
@ -141,7 +141,39 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"step_id": "step_06_off_domain_living_chat_not_hijacked",
|
||||
"step_id": "step_06_counterparty_bidirectional_net_flow_uses_guarded_discovery",
|
||||
"title": "Unsupported-but-understood counterparty net cash-flow question composes incoming and outgoing discovery",
|
||||
"question": "какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?",
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)свк",
|
||||
"(?i)1с|найден|строк|проверен",
|
||||
"(?i)получил|входящ|поступ",
|
||||
"(?i)заплат|исходящ|списан|плат[её]ж",
|
||||
"(?i)нетто|сальдо|разниц",
|
||||
"(?i)сумм|руб",
|
||||
"(?i)2020|период",
|
||||
"(?i)не подтвержд|проверенн|найденн"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)точный маршрут.*не подключ",
|
||||
"(?i)не буду подставлять",
|
||||
"(?i)query_documents",
|
||||
"(?i)query_movements",
|
||||
"(?i)runtime_",
|
||||
"(?i)planner_",
|
||||
"(?i)catalog_",
|
||||
"(?i)primitive",
|
||||
"(?i)pilot_"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": [
|
||||
"mcp_discovery_bidirectional_value_flow",
|
||||
"counterparty_net_cash_flow",
|
||||
"unsupported_current_turn_meaning_boundary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step_id": "step_07_off_domain_living_chat_not_hijacked",
|
||||
"title": "Off-domain living chat remains human and is not hijacked by discovery carryover",
|
||||
"question": "а чем капибара отличается от утки?",
|
||||
"required_answer_patterns_any": [
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ exports.buildInventoryRootFrameFromAddressDebug = buildInventoryRootFrameFromAdd
|
|||
exports.isGroundedAddressDebug = isGroundedAddressDebug;
|
||||
exports.resolveAssistantContinuitySnapshot = resolveAssistantContinuitySnapshot;
|
||||
exports.resolveAssistantOrganizationAuthority = resolveAssistantOrganizationAuthority;
|
||||
exports.resolveOrganizationClarificationContinuation = resolveOrganizationClarificationContinuation;
|
||||
const assistantOrganizationMatcher_1 = require("./assistantOrganizationMatcher");
|
||||
function fallbackToNonEmptyString(value) {
|
||||
if (value === null || value === undefined) {
|
||||
|
|
@ -685,3 +686,36 @@ function resolveAssistantOrganizationAuthority(input) {
|
|||
organizationClarificationSelectionFromScope
|
||||
};
|
||||
}
|
||||
function resolveOrganizationClarificationContinuation(input) {
|
||||
const toNonEmptyString = input.toNonEmptyString ?? fallbackToNonEmptyString;
|
||||
const normalizeOrganizationScopeValue = input.normalizeOrganizationScopeValue ?? normalizeOrganizationScopeDefault;
|
||||
const candidates = Array.isArray(input.organizationClarificationCandidates)
|
||||
? input.organizationClarificationCandidates
|
||||
: [];
|
||||
const resolveOrganizationSelectionFromMessage = input.resolveOrganizationSelectionFromMessage;
|
||||
const messages = Array.isArray(input.rawMessages) ? input.rawMessages : [];
|
||||
let explicitSelection = null;
|
||||
if (typeof resolveOrganizationSelectionFromMessage === "function") {
|
||||
for (const message of messages) {
|
||||
const normalizedMessage = toNonEmptyString(message);
|
||||
if (!normalizedMessage) {
|
||||
continue;
|
||||
}
|
||||
explicitSelection = resolveOrganizationSelectionFromMessage(normalizedMessage, candidates);
|
||||
if (explicitSelection) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const normalizedScopeSelection = normalizeOrganizationScopeValue(input.organizationClarificationSelectionFromScope);
|
||||
const selection = explicitSelection ??
|
||||
(normalizedScopeSelection &&
|
||||
candidates.some((candidate) => normalizeOrganizationScopeValue(candidate) === normalizedScopeSelection)
|
||||
? normalizedScopeSelection
|
||||
: null);
|
||||
return {
|
||||
explicitSelection,
|
||||
selection,
|
||||
hasContinuation: Boolean(input.lastOrganizationClarificationDebug && selection)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,9 +61,13 @@ function modeFor(pilot) {
|
|||
}
|
||||
function isValueFlowPilot(pilot) {
|
||||
return (pilot.pilot_scope === "counterparty_value_flow_query_movements_v1" ||
|
||||
pilot.pilot_scope === "counterparty_supplier_payout_query_movements_v1");
|
||||
pilot.pilot_scope === "counterparty_supplier_payout_query_movements_v1" ||
|
||||
pilot.pilot_scope === "counterparty_bidirectional_value_flow_query_movements_v1");
|
||||
}
|
||||
function headlineFor(mode, pilot) {
|
||||
if (pilot.derived_bidirectional_value_flow && mode === "confirmed_with_bounded_inference") {
|
||||
return "По данным 1С найдены строки входящих и исходящих денежных движений; нетто можно называть только как расчет по найденным строкам и проверенному периоду.";
|
||||
}
|
||||
if (pilot.derived_value_flow && mode === "confirmed_with_bounded_inference") {
|
||||
if (pilot.derived_value_flow.value_flow_direction === "outgoing_supplier_payout") {
|
||||
return "По данным 1С найдены строки исходящих платежей/списаний; сумму можно называть только в рамках проверенного периода и найденных строк.";
|
||||
|
|
@ -147,6 +151,40 @@ function derivedValueFlowConfirmedLine(pilot) {
|
|||
: "";
|
||||
return `По найденным строкам ${movementLabel} в 1С${counterparty}${period} ${totalLabel} ${flow.total_amount_human_ru} Учтено строк с суммой: ${flow.rows_with_amount} из ${flow.rows_matched}.${dates}${limitCaveat} ${caveat}`;
|
||||
}
|
||||
function sideDateRange(first, latest) {
|
||||
if (first && latest) {
|
||||
return ` первая дата ${first}, последняя ${latest}`;
|
||||
}
|
||||
return " даты движения не выделены";
|
||||
}
|
||||
function derivedBidirectionalValueFlowConfirmedLine(pilot) {
|
||||
const flow = pilot.derived_bidirectional_value_flow;
|
||||
if (!flow) {
|
||||
return null;
|
||||
}
|
||||
const counterparty = flow.counterparty ? ` по контрагенту ${flow.counterparty}` : "";
|
||||
const period = flow.period_scope ? ` за период ${flow.period_scope}` : " в проверенном окне";
|
||||
const incoming = flow.incoming_customer_revenue;
|
||||
const outgoing = flow.outgoing_supplier_payout;
|
||||
const netLabel = flow.net_direction === "net_incoming"
|
||||
? "нетто в нашу сторону"
|
||||
: flow.net_direction === "net_outgoing"
|
||||
? "нетто исходящий"
|
||||
: "нетто нулевое";
|
||||
const limitCaveat = flow.coverage_limited_by_probe_limit
|
||||
? " Лимит строк проверки достигнут хотя бы по одной стороне; полный запрошенный период может быть покрыт не полностью."
|
||||
: "";
|
||||
return [
|
||||
`По найденным строкам 1С${counterparty}${period}: получили ${incoming.total_amount_human_ru} по входящим движениям, заплатили ${outgoing.total_amount_human_ru} по исходящим платежам/списаниям.`,
|
||||
`Расчетное ${netLabel}: ${flow.net_amount_human_ru}`,
|
||||
`Входящие строки с суммой: ${incoming.rows_with_amount} из ${incoming.rows_matched};${sideDateRange(incoming.first_movement_date, incoming.latest_movement_date)}.`,
|
||||
`Исходящие строки с суммой: ${outgoing.rows_with_amount} из ${outgoing.rows_matched};${sideDateRange(outgoing.first_movement_date, outgoing.latest_movement_date)}.`,
|
||||
`${limitCaveat} Это расчет по найденным строкам 1С, а не подтверждение полного сальдо вне проверенного окна.`
|
||||
]
|
||||
.join(" ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
|
||||
const mode = modeFor(pilot);
|
||||
const reasonCodes = [...pilot.reason_codes, ...pilot.evidence.reason_codes];
|
||||
|
|
@ -161,7 +199,7 @@ function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
|
|||
const inferenceLines = derivedInferenceLine
|
||||
? [derivedInferenceLine]
|
||||
: pilot.evidence.inferred_facts;
|
||||
const derivedValueLine = derivedValueFlowConfirmedLine(pilot);
|
||||
const derivedValueLine = derivedBidirectionalValueFlowConfirmedLine(pilot) ?? derivedValueFlowConfirmedLine(pilot);
|
||||
const confirmedLines = derivedValueLine
|
||||
? [...pilot.evidence.confirmed_facts, derivedValueLine]
|
||||
: pilot.evidence.confirmed_facts;
|
||||
|
|
|
|||
|
|
@ -120,6 +120,16 @@ function valueFlowPilotProfile(planner) {
|
|||
const action = String(meaning?.asked_action_family ?? "").toLowerCase();
|
||||
const unsupported = String(meaning?.unsupported_but_understood_family ?? "").toLowerCase();
|
||||
const combined = `${action} ${unsupported}`;
|
||||
if (combined.includes("net_value_flow") ||
|
||||
combined.includes("bidirectional") ||
|
||||
combined.includes("netting") ||
|
||||
combined.includes("net")) {
|
||||
return {
|
||||
scope: "counterparty_bidirectional_value_flow_query_movements_v1",
|
||||
recipe_intent: null,
|
||||
direction: "bidirectional_net_value_flow"
|
||||
};
|
||||
}
|
||||
if (combined.includes("payout") ||
|
||||
combined.includes("outflow") ||
|
||||
combined.includes("supplier") ||
|
||||
|
|
@ -314,6 +324,74 @@ function deriveValueFlow(result, counterparty, periodScope, direction, probeLimi
|
|||
inference_basis: "sum_of_confirmed_1c_value_flow_rows"
|
||||
};
|
||||
}
|
||||
function deriveValueFlowSideSummary(result, probeLimit) {
|
||||
if (!result || result.error || result.matched_rows <= 0) {
|
||||
return {
|
||||
rows_matched: 0,
|
||||
rows_with_amount: 0,
|
||||
total_amount: 0,
|
||||
total_amount_human_ru: formatAmountHumanRu(0),
|
||||
first_movement_date: null,
|
||||
latest_movement_date: null,
|
||||
coverage_limited_by_probe_limit: false
|
||||
};
|
||||
}
|
||||
let totalAmount = 0;
|
||||
let rowsWithAmount = 0;
|
||||
for (const row of result.rows) {
|
||||
const amount = rowAmountValue(row);
|
||||
if (amount !== null) {
|
||||
totalAmount += amount;
|
||||
rowsWithAmount += 1;
|
||||
}
|
||||
}
|
||||
const dates = result.rows
|
||||
.map((row) => rowDateValue(row))
|
||||
.filter((value) => Boolean(value))
|
||||
.sort();
|
||||
return {
|
||||
rows_matched: result.matched_rows,
|
||||
rows_with_amount: rowsWithAmount,
|
||||
total_amount: totalAmount,
|
||||
total_amount_human_ru: formatAmountHumanRu(totalAmount),
|
||||
first_movement_date: dates[0] ?? null,
|
||||
latest_movement_date: dates[dates.length - 1] ?? null,
|
||||
coverage_limited_by_probe_limit: result.matched_rows >= probeLimit
|
||||
};
|
||||
}
|
||||
function deriveBidirectionalValueFlow(input) {
|
||||
const incoming = deriveValueFlowSideSummary(input.incomingResult, input.probeLimit);
|
||||
const outgoing = deriveValueFlowSideSummary(input.outgoingResult, input.probeLimit);
|
||||
if (incoming.rows_with_amount <= 0 && outgoing.rows_with_amount <= 0) {
|
||||
return null;
|
||||
}
|
||||
const netAmount = incoming.total_amount - outgoing.total_amount;
|
||||
return {
|
||||
counterparty: input.counterparty,
|
||||
period_scope: input.periodScope,
|
||||
incoming_customer_revenue: incoming,
|
||||
outgoing_supplier_payout: outgoing,
|
||||
net_amount: netAmount,
|
||||
net_amount_human_ru: formatAmountHumanRu(Math.abs(netAmount)),
|
||||
net_direction: netAmount > 0 ? "net_incoming" : netAmount < 0 ? "net_outgoing" : "balanced",
|
||||
coverage_limited_by_probe_limit: incoming.coverage_limited_by_probe_limit || outgoing.coverage_limited_by_probe_limit,
|
||||
inference_basis: "incoming_minus_outgoing_confirmed_1c_value_flow_rows"
|
||||
};
|
||||
}
|
||||
function summarizeBidirectionalValueFlowRows(input) {
|
||||
const incoming = input.incomingResult;
|
||||
const outgoing = input.outgoingResult;
|
||||
if (!incoming && !outgoing) {
|
||||
return null;
|
||||
}
|
||||
const incomingSummary = incoming?.error
|
||||
? "incoming value-flow query failed"
|
||||
: `${incoming?.fetched_rows ?? 0} incoming value-flow rows fetched, ${incoming?.matched_rows ?? 0} matched`;
|
||||
const outgoingSummary = outgoing?.error
|
||||
? "outgoing supplier-payout query failed"
|
||||
: `${outgoing?.fetched_rows ?? 0} outgoing supplier-payout rows fetched, ${outgoing?.matched_rows ?? 0} matched`;
|
||||
return `${incomingSummary}; ${outgoingSummary}`;
|
||||
}
|
||||
function buildLifecycleConfirmedFacts(result, counterparty) {
|
||||
if (result.error || result.matched_rows <= 0) {
|
||||
return [];
|
||||
|
|
@ -341,6 +419,21 @@ function buildValueFlowConfirmedFacts(result, counterparty, direction) {
|
|||
: "1C value-flow rows were found for the requested counterparty scope"
|
||||
];
|
||||
}
|
||||
function buildBidirectionalValueFlowConfirmedFacts(derived) {
|
||||
if (!derived) {
|
||||
return [];
|
||||
}
|
||||
const hasIncoming = derived.incoming_customer_revenue.rows_matched > 0;
|
||||
const hasOutgoing = derived.outgoing_supplier_payout.rows_matched > 0;
|
||||
if (derived.counterparty) {
|
||||
return [
|
||||
`1C bidirectional value-flow rows were checked for counterparty ${derived.counterparty}: incoming=${hasIncoming ? "found" : "not_found"}, outgoing=${hasOutgoing ? "found" : "not_found"}`
|
||||
];
|
||||
}
|
||||
return [
|
||||
`1C bidirectional value-flow rows were checked for the requested counterparty scope: incoming=${hasIncoming ? "found" : "not_found"}, outgoing=${hasOutgoing ? "found" : "not_found"}`
|
||||
];
|
||||
}
|
||||
function buildLifecycleInferredFacts(result) {
|
||||
if (result.error || result.fetched_rows <= 0) {
|
||||
return [];
|
||||
|
|
@ -356,6 +449,12 @@ function buildValueFlowInferredFacts(derived) {
|
|||
}
|
||||
return ["Counterparty value-flow total was calculated from confirmed 1C movement rows"];
|
||||
}
|
||||
function buildBidirectionalValueFlowInferredFacts(derived) {
|
||||
if (!derived) {
|
||||
return [];
|
||||
}
|
||||
return ["Counterparty net value-flow was calculated as incoming confirmed 1C rows minus outgoing confirmed 1C rows"];
|
||||
}
|
||||
function buildLifecycleUnknownFacts() {
|
||||
return ["Legal registration date is not proven by this MCP discovery pilot"];
|
||||
}
|
||||
|
|
@ -375,6 +474,16 @@ function buildValueFlowUnknownFacts(periodScope, direction, derived) {
|
|||
: "Full all-time turnover is not proven without an explicit checked period");
|
||||
return unknownFacts;
|
||||
}
|
||||
function buildBidirectionalValueFlowUnknownFacts(periodScope, derived) {
|
||||
const unknownFacts = [];
|
||||
if (derived?.coverage_limited_by_probe_limit) {
|
||||
unknownFacts.push("Complete requested-period coverage for bidirectional value-flow is not proven because at least one MCP discovery probe row limit was reached");
|
||||
}
|
||||
unknownFacts.push(periodScope
|
||||
? "Full bidirectional value-flow outside the checked period is not proven by this MCP discovery pilot"
|
||||
: "Full all-time bidirectional value-flow is not proven without an explicit checked period");
|
||||
return unknownFacts;
|
||||
}
|
||||
function buildEmptyEvidence(planner, dryRun, probeResults, reason) {
|
||||
return (0, assistantMcpDiscoveryPolicy_1.resolveAssistantMcpDiscoveryEvidence)({
|
||||
plan: planner.discovery_plan,
|
||||
|
|
@ -408,6 +517,7 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
|
|||
source_rows_summary: null,
|
||||
derived_activity_period: null,
|
||||
derived_value_flow: null,
|
||||
derived_bidirectional_value_flow: null,
|
||||
query_limitations: ["MCP discovery pilot was blocked before execution"],
|
||||
reason_codes: reasonCodes
|
||||
};
|
||||
|
|
@ -429,6 +539,7 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
|
|||
source_rows_summary: null,
|
||||
derived_activity_period: null,
|
||||
derived_value_flow: null,
|
||||
derived_bidirectional_value_flow: null,
|
||||
query_limitations: ["MCP discovery pilot needs more scope before execution"],
|
||||
reason_codes: reasonCodes
|
||||
};
|
||||
|
|
@ -456,6 +567,7 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
|
|||
source_rows_summary: null,
|
||||
derived_activity_period: null,
|
||||
derived_value_flow: null,
|
||||
derived_bidirectional_value_flow: null,
|
||||
query_limitations: ["MCP discovery pilot scope is not implemented yet"],
|
||||
reason_codes: reasonCodes
|
||||
};
|
||||
|
|
@ -466,7 +578,109 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
|
|||
let queryResult = null;
|
||||
const filters = buildValueFlowFilters(planner);
|
||||
const valueFlowProfile = valueFlowPilotProfile(planner);
|
||||
const selection = (0, addressRecipeCatalog_1.selectAddressRecipe)(valueFlowProfile.recipe_intent, filters);
|
||||
if (valueFlowProfile.direction === "bidirectional_net_value_flow") {
|
||||
let incomingResult = null;
|
||||
let outgoingResult = null;
|
||||
const incomingSelection = (0, addressRecipeCatalog_1.selectAddressRecipe)("customer_revenue_and_payments", filters);
|
||||
const outgoingSelection = (0, addressRecipeCatalog_1.selectAddressRecipe)("supplier_payouts_profile", filters);
|
||||
if (!incomingSelection.selected_recipe || !outgoingSelection.selected_recipe) {
|
||||
pushReason(reasonCodes, "pilot_bidirectional_value_flow_recipe_not_available");
|
||||
const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "Bidirectional value-flow recipes are not available");
|
||||
return {
|
||||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||||
pilot_status: "unsupported",
|
||||
pilot_scope: valueFlowProfile.scope,
|
||||
dry_run: dryRun,
|
||||
mcp_execution_performed: false,
|
||||
executed_primitives: executedPrimitives,
|
||||
skipped_primitives: skippedPrimitives,
|
||||
probe_results: probeResults,
|
||||
evidence,
|
||||
source_rows_summary: null,
|
||||
derived_activity_period: null,
|
||||
derived_value_flow: null,
|
||||
derived_bidirectional_value_flow: null,
|
||||
query_limitations: ["Bidirectional value-flow recipes are not available"],
|
||||
reason_codes: reasonCodes
|
||||
};
|
||||
}
|
||||
pushReason(reasonCodes, "pilot_bidirectional_value_flow_recipes_selected");
|
||||
const incomingRecipePlan = (0, addressRecipeCatalog_1.buildAddressRecipePlan)(incomingSelection.selected_recipe, filters);
|
||||
const outgoingRecipePlan = (0, addressRecipeCatalog_1.buildAddressRecipePlan)(outgoingSelection.selected_recipe, filters);
|
||||
for (const step of dryRun.execution_steps) {
|
||||
if (step.primitive_id !== "query_movements") {
|
||||
skippedPrimitives.push(step.primitive_id);
|
||||
probeResults.push(skippedProbeResult(step, "pilot_bidirectional_value_flow_uses_two_query_movements_and_derives_net"));
|
||||
continue;
|
||||
}
|
||||
incomingResult = await deps.executeAddressMcpQuery({
|
||||
query: incomingRecipePlan.query,
|
||||
limit: incomingRecipePlan.limit,
|
||||
account_scope: incomingRecipePlan.account_scope
|
||||
});
|
||||
outgoingResult = await deps.executeAddressMcpQuery({
|
||||
query: outgoingRecipePlan.query,
|
||||
limit: outgoingRecipePlan.limit,
|
||||
account_scope: outgoingRecipePlan.account_scope
|
||||
});
|
||||
pushUnique(executedPrimitives, step.primitive_id);
|
||||
probeResults.push(queryResultToProbeResult(step.primitive_id, incomingResult));
|
||||
probeResults.push(queryResultToProbeResult(step.primitive_id, outgoingResult));
|
||||
if (incomingResult.error) {
|
||||
pushUnique(queryLimitations, incomingResult.error);
|
||||
pushReason(reasonCodes, "pilot_bidirectional_incoming_query_movements_mcp_error");
|
||||
}
|
||||
if (outgoingResult.error) {
|
||||
pushUnique(queryLimitations, outgoingResult.error);
|
||||
pushReason(reasonCodes, "pilot_bidirectional_outgoing_query_movements_mcp_error");
|
||||
}
|
||||
if (!incomingResult.error || !outgoingResult.error) {
|
||||
pushReason(reasonCodes, "pilot_bidirectional_query_movements_mcp_executed");
|
||||
}
|
||||
}
|
||||
const sourceRowsSummary = summarizeBidirectionalValueFlowRows({ incomingResult, outgoingResult });
|
||||
const derivedBidirectionalValueFlow = deriveBidirectionalValueFlow({
|
||||
incomingResult,
|
||||
outgoingResult,
|
||||
counterparty,
|
||||
periodScope: dateScope,
|
||||
probeLimit: planner.discovery_plan.execution_budget.max_rows_per_probe
|
||||
});
|
||||
if (derivedBidirectionalValueFlow) {
|
||||
pushReason(reasonCodes, "pilot_derived_bidirectional_value_flow_from_confirmed_rows");
|
||||
}
|
||||
const evidence = (0, assistantMcpDiscoveryPolicy_1.resolveAssistantMcpDiscoveryEvidence)({
|
||||
plan: planner.discovery_plan,
|
||||
probeResults,
|
||||
confirmedFacts: buildBidirectionalValueFlowConfirmedFacts(derivedBidirectionalValueFlow),
|
||||
inferredFacts: buildBidirectionalValueFlowInferredFacts(derivedBidirectionalValueFlow),
|
||||
unknownFacts: buildBidirectionalValueFlowUnknownFacts(dateScope, derivedBidirectionalValueFlow),
|
||||
sourceRowsSummary,
|
||||
queryLimitations,
|
||||
recommendedNextProbe: "explain_evidence_basis"
|
||||
});
|
||||
return {
|
||||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||||
pilot_status: "executed",
|
||||
pilot_scope: valueFlowProfile.scope,
|
||||
dry_run: dryRun,
|
||||
mcp_execution_performed: executedPrimitives.length > 0,
|
||||
executed_primitives: executedPrimitives,
|
||||
skipped_primitives: skippedPrimitives,
|
||||
probe_results: probeResults,
|
||||
evidence,
|
||||
source_rows_summary: sourceRowsSummary,
|
||||
derived_activity_period: null,
|
||||
derived_value_flow: null,
|
||||
derived_bidirectional_value_flow: derivedBidirectionalValueFlow,
|
||||
query_limitations: queryLimitations,
|
||||
reason_codes: reasonCodes
|
||||
};
|
||||
}
|
||||
const recipeIntent = valueFlowProfile.recipe_intent;
|
||||
const selection = recipeIntent ? (0, addressRecipeCatalog_1.selectAddressRecipe)(recipeIntent, filters) : { selected_recipe: null };
|
||||
if (!selection.selected_recipe) {
|
||||
pushReason(reasonCodes, "pilot_value_flow_recipe_not_available");
|
||||
const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "Value-flow recipe is not available");
|
||||
|
|
@ -484,6 +698,7 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
|
|||
source_rows_summary: null,
|
||||
derived_activity_period: null,
|
||||
derived_value_flow: null,
|
||||
derived_bidirectional_value_flow: null,
|
||||
query_limitations: ["Value-flow recipe is not available"],
|
||||
reason_codes: reasonCodes
|
||||
};
|
||||
|
|
@ -542,6 +757,7 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
|
|||
source_rows_summary: sourceRowsSummary,
|
||||
derived_activity_period: null,
|
||||
derived_value_flow: derivedValueFlow,
|
||||
derived_bidirectional_value_flow: null,
|
||||
query_limitations: queryLimitations,
|
||||
reason_codes: reasonCodes
|
||||
};
|
||||
|
|
@ -566,6 +782,7 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
|
|||
source_rows_summary: null,
|
||||
derived_activity_period: null,
|
||||
derived_value_flow: null,
|
||||
derived_bidirectional_value_flow: null,
|
||||
query_limitations: ["Lifecycle recipe is not available"],
|
||||
reason_codes: reasonCodes
|
||||
};
|
||||
|
|
@ -621,6 +838,7 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
|
|||
source_rows_summary: sourceRowsSummary,
|
||||
derived_activity_period: derivedActivityPeriod,
|
||||
derived_value_flow: null,
|
||||
derived_bidirectional_value_flow: null,
|
||||
query_limitations: queryLimitations,
|
||||
reason_codes: reasonCodes
|
||||
};
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ function recipeFor(input) {
|
|||
const combined = `${domain} ${action} ${unsupported}`.trim();
|
||||
const axes = [];
|
||||
addScopeAxes(axes, meaning);
|
||||
if (includesAny(combined, ["turnover", "revenue", "payment", "payout", "value"])) {
|
||||
if (includesAny(combined, ["turnover", "revenue", "payment", "payout", "value", "net", "netting", "balance", "cashflow"])) {
|
||||
pushUnique(axes, "aggregate_axis");
|
||||
pushUnique(axes, "amount");
|
||||
pushUnique(axes, "coverage_target");
|
||||
|
|
|
|||
|
|
@ -82,6 +82,18 @@ function localizeLine(value) {
|
|||
if (/^1C supplier-payout rows were found for the requested counterparty scope$/i.test(value)) {
|
||||
return "В 1С найдены строки исходящих платежей/списаний по запрошенному контрагентскому контуру.";
|
||||
}
|
||||
const bidirectionalMatch = value.match(/^1C bidirectional value-flow rows were checked for counterparty\s+(.+): incoming=(found|not_found), outgoing=(found|not_found)$/i);
|
||||
if (bidirectionalMatch) {
|
||||
const incoming = bidirectionalMatch[2] === "found" ? "входящие строки найдены" : "входящие строки не найдены";
|
||||
const outgoing = bidirectionalMatch[3] === "found" ? "исходящие строки найдены" : "исходящие строки не найдены";
|
||||
return `В 1С проверены входящие и исходящие денежные строки по контрагенту ${bidirectionalMatch[1]}: ${incoming}, ${outgoing}.`;
|
||||
}
|
||||
const bidirectionalScopeMatch = value.match(/^1C bidirectional value-flow rows were checked for the requested counterparty scope: incoming=(found|not_found), outgoing=(found|not_found)$/i);
|
||||
if (bidirectionalScopeMatch) {
|
||||
const incoming = bidirectionalScopeMatch[1] === "found" ? "входящие строки найдены" : "входящие строки не найдены";
|
||||
const outgoing = bidirectionalScopeMatch[2] === "found" ? "исходящие строки найдены" : "исходящие строки не найдены";
|
||||
return `В 1С проверены входящие и исходящие денежные строки по запрошенному контрагентскому контуру: ${incoming}, ${outgoing}.`;
|
||||
}
|
||||
if (/^Business activity duration may be inferred from first and latest confirmed 1C activity rows$/i.test(value)) {
|
||||
return "Длительность деловой активности можно оценивать только как вывод по первой и последней подтвержденной строке активности в 1С.";
|
||||
}
|
||||
|
|
@ -91,12 +103,18 @@ function localizeLine(value) {
|
|||
if (/^Counterparty supplier-payout total was calculated from confirmed 1C outgoing payment rows$/i.test(value)) {
|
||||
return "Сумма исходящих платежей рассчитана только по подтвержденным строкам списаний в 1С.";
|
||||
}
|
||||
if (/^Counterparty net value-flow was calculated as incoming confirmed 1C rows minus outgoing confirmed 1C rows$/i.test(value)) {
|
||||
return "Нетто денежного потока рассчитано только как входящие подтвержденные строки 1С минус исходящие подтвержденные строки 1С.";
|
||||
}
|
||||
if (/^Legal registration date is not proven by this MCP discovery pilot$/i.test(value)) {
|
||||
return "Юридическая дата регистрации этим поиском не подтверждена.";
|
||||
}
|
||||
if (/^Complete requested-period coverage is not proven because the MCP discovery probe row limit was reached$/i.test(value)) {
|
||||
return "Полное покрытие запрошенного периода не подтверждено: проверка достигла лимита найденных строк.";
|
||||
}
|
||||
if (/^Complete requested-period coverage for bidirectional value-flow is not proven because at least one MCP discovery probe row limit was reached$/i.test(value)) {
|
||||
return "Полное покрытие запрошенного периода по двустороннему денежному потоку не подтверждено: хотя бы одна сторона проверки достигла лимита найденных строк.";
|
||||
}
|
||||
if (/^Full turnover outside the checked period is not proven by this MCP discovery pilot$/i.test(value)) {
|
||||
return "Полный оборот вне проверенного периода этим поиском не подтвержден.";
|
||||
}
|
||||
|
|
@ -109,6 +127,12 @@ function localizeLine(value) {
|
|||
if (/^Full all-time supplier-payout amount is not proven without an explicit checked period$/i.test(value)) {
|
||||
return "Полный объем исходящих платежей за все время без явно проверенного периода не подтвержден.";
|
||||
}
|
||||
if (/^Full bidirectional value-flow outside the checked period is not proven by this MCP discovery pilot$/i.test(value)) {
|
||||
return "Полный двусторонний денежный поток вне проверенного периода этим поиском не подтвержден.";
|
||||
}
|
||||
if (/^Full all-time bidirectional value-flow is not proven without an explicit checked period$/i.test(value)) {
|
||||
return "Полный двусторонний денежный поток за все время без явно проверенного периода не подтвержден.";
|
||||
}
|
||||
return value;
|
||||
}
|
||||
function section(title, lines) {
|
||||
|
|
|
|||
|
|
@ -101,12 +101,15 @@ function hasValueFlowSignal(text) {
|
|||
function hasPayoutSignal(text) {
|
||||
return /(?:\bмы\s+(?:за)?плат|(?:за)?платил|оплатил|перечисл|списан|расход|поставщик|исходящ|supplier|payout|outflow|paid\s+to|payment\s+to)/iu.test(text);
|
||||
}
|
||||
function hasBidirectionalValueFlowSignal(text) {
|
||||
return /(?:нетто|сальдо|баланс\s+(?:плат|денег|денеж)|взаиморасч[её]т|получил[иа]?.*(?:за)?платил|(?:за)?платил[иа]?.*получил|входящ.*исходящ|исходящ.*входящ|дебет.*кредит|кредит.*дебет|net\s+(?:flow|cash|payment)|cash\s+net|incoming\s+and\s+outgoing|received\s+and\s+paid|paid\s+and\s+received)/iu.test(text);
|
||||
}
|
||||
function semanticNeedFor(input) {
|
||||
const combined = compactLower(`${input.domain ?? ""} ${input.action ?? ""} ${input.unsupported ?? ""}`);
|
||||
if (input.lifecycleSignal || /(?:lifecycle|activity|duration|age)/iu.test(combined)) {
|
||||
return "counterparty lifecycle evidence";
|
||||
}
|
||||
if (input.valueFlowSignal || /(?:turnover|revenue|payment|payout|value)/iu.test(combined)) {
|
||||
if (input.valueFlowSignal || /(?:turnover|revenue|payment|payout|value|net|netting|balance|cashflow)/iu.test(combined)) {
|
||||
return "counterparty value-flow evidence";
|
||||
}
|
||||
if (/(?:document|documents|list_documents)/iu.test(combined)) {
|
||||
|
|
@ -136,8 +139,9 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
const reasonCodes = [];
|
||||
const rawText = compactLower(`${input.userMessage ?? ""} ${input.effectiveMessage ?? ""}`);
|
||||
const lifecycleSignal = hasLifecycleSignal(rawText);
|
||||
const valueFlowSignal = !lifecycleSignal && hasValueFlowSignal(rawText);
|
||||
const payoutSignal = valueFlowSignal && hasPayoutSignal(rawText);
|
||||
const bidirectionalValueFlowSignal = !lifecycleSignal && hasBidirectionalValueFlowSignal(rawText);
|
||||
const valueFlowSignal = !lifecycleSignal && (hasValueFlowSignal(rawText) || bidirectionalValueFlowSignal);
|
||||
const payoutSignal = valueFlowSignal && !bidirectionalValueFlowSignal && hasPayoutSignal(rawText);
|
||||
const rawDomain = toNonEmptyString(assistantTurnMeaning?.asked_domain_family);
|
||||
const rawAction = toNonEmptyString(assistantTurnMeaning?.asked_action_family);
|
||||
const unsupported = toNonEmptyString(assistantTurnMeaning?.unsupported_but_understood_family);
|
||||
|
|
@ -157,11 +161,28 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
const explicitOrganizationScope = valueFlowSignal && !predecomposeEntities.counterparty ? null : predecomposeEntities.organization;
|
||||
const turnMeaning = {
|
||||
asked_domain_family: lifecycleSignal ? "counterparty_lifecycle" : valueFlowSignal ? "counterparty_value" : rawDomain,
|
||||
asked_action_family: lifecycleSignal ? "activity_duration" : valueFlowSignal ? (payoutSignal ? "payout" : "turnover") : rawAction,
|
||||
asked_action_family: lifecycleSignal
|
||||
? "activity_duration"
|
||||
: valueFlowSignal
|
||||
? bidirectionalValueFlowSignal
|
||||
? "net_value_flow"
|
||||
: payoutSignal
|
||||
? "payout"
|
||||
: "turnover"
|
||||
: rawAction,
|
||||
explicit_entity_candidates: entityCandidates,
|
||||
explicit_organization_scope: explicitOrganizationScope,
|
||||
explicit_date_scope: collectDateScope(predecomposeContract),
|
||||
unsupported_but_understood_family: unsupported ?? (lifecycleSignal ? "counterparty_lifecycle" : valueFlowSignal ? (payoutSignal ? "counterparty_payouts_or_outflow" : "counterparty_value_or_turnover") : null),
|
||||
unsupported_but_understood_family: unsupported ??
|
||||
(lifecycleSignal
|
||||
? "counterparty_lifecycle"
|
||||
: valueFlowSignal
|
||||
? bidirectionalValueFlowSignal
|
||||
? "counterparty_bidirectional_value_flow_or_netting"
|
||||
: payoutSignal
|
||||
? "counterparty_payouts_or_outflow"
|
||||
: "counterparty_value_or_turnover"
|
||||
: null),
|
||||
stale_replay_forbidden: Boolean(assistantTurnMeaning?.stale_replay_forbidden || unsupported || lifecycleSignal || valueFlowSignal)
|
||||
};
|
||||
const cleanTurnMeaning = {};
|
||||
|
|
@ -212,6 +233,9 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
if (payoutSignal) {
|
||||
pushReason(reasonCodes, "mcp_discovery_payout_signal_detected");
|
||||
}
|
||||
if (bidirectionalValueFlowSignal) {
|
||||
pushReason(reasonCodes, "mcp_discovery_bidirectional_value_flow_signal_detected");
|
||||
}
|
||||
if (unsupported) {
|
||||
pushReason(reasonCodes, "mcp_discovery_unsupported_but_understood_turn");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -328,17 +328,22 @@ function createAssistantRoutePolicy(deps) {
|
|||
continuitySnapshot.lastGroundedAddressDebug;
|
||||
const lastOrganizationClarificationDebug = findLastOrganizationClarificationAddressDebug(sessionItems);
|
||||
const organizationClarificationCandidates = organizationAuthority.organizationClarificationCandidates;
|
||||
const organizationClarificationSelectionFromScope = organizationAuthority.organizationClarificationSelectionFromScope;
|
||||
const explicitOrganizationClarificationSelection = resolveOrganizationSelectionFromMessage(rawUserMessage, organizationClarificationCandidates) ??
|
||||
resolveOrganizationSelectionFromMessage(repairedRawUserMessage, organizationClarificationCandidates) ??
|
||||
resolveOrganizationSelectionFromMessage(effectiveAddressUserMessage, organizationClarificationCandidates) ??
|
||||
resolveOrganizationSelectionFromMessage(repairedEffectiveAddressUserMessage, organizationClarificationCandidates) ??
|
||||
null;
|
||||
const organizationClarificationSelection = explicitOrganizationClarificationSelection ??
|
||||
(organizationClarificationSelectionFromScope &&
|
||||
organizationClarificationCandidates.some((candidate) => normalizeOrganizationScopeValue(candidate) === organizationClarificationSelectionFromScope)
|
||||
? organizationClarificationSelectionFromScope
|
||||
: null);
|
||||
const organizationClarificationContinuation = (0, assistantContinuityPolicy_1.resolveOrganizationClarificationContinuation)({
|
||||
rawMessages: [
|
||||
rawUserMessage,
|
||||
repairedRawUserMessage,
|
||||
effectiveAddressUserMessage,
|
||||
repairedEffectiveAddressUserMessage
|
||||
],
|
||||
organizationClarificationCandidates,
|
||||
organizationClarificationSelectionFromScope: organizationAuthority.organizationClarificationSelectionFromScope,
|
||||
lastOrganizationClarificationDebug,
|
||||
resolveOrganizationSelectionFromMessage,
|
||||
toNonEmptyString,
|
||||
normalizeOrganizationScopeValue
|
||||
});
|
||||
const explicitOrganizationClarificationSelection = organizationClarificationContinuation.explicitSelection;
|
||||
const organizationClarificationSelection = organizationClarificationContinuation.selection;
|
||||
const metaSignals = resolveMetaSignalSet({
|
||||
rawUserMessage,
|
||||
repairedRawUserMessage,
|
||||
|
|
|
|||
|
|
@ -316,13 +316,18 @@ function createAssistantTransitionPolicy(deps) {
|
|||
const organizationClarificationCandidates = Array.isArray(organizationAuthority.organizationClarificationCandidates)
|
||||
? organizationAuthority.organizationClarificationCandidates
|
||||
: [];
|
||||
const explicitOrganizationClarificationSelection = deps.resolveOrganizationSelectionFromMessage(userMessage, organizationClarificationCandidates) ??
|
||||
(deps.toNonEmptyString(alternateMessage)
|
||||
? deps.resolveOrganizationSelectionFromMessage(String(alternateMessage ?? ""), organizationClarificationCandidates)
|
||||
: null);
|
||||
const organizationClarificationSelection = explicitOrganizationClarificationSelection ??
|
||||
deps.normalizeOrganizationScopeValue(organizationAuthority.organizationClarificationSelectionFromScope);
|
||||
const hasOrganizationClarificationContinuation = Boolean(lastOrganizationClarificationDebug && organizationClarificationSelection);
|
||||
const organizationClarificationContinuation = (0, assistantContinuityPolicy_1.resolveOrganizationClarificationContinuation)({
|
||||
rawMessages: [userMessage, alternateMessage],
|
||||
organizationClarificationCandidates,
|
||||
organizationClarificationSelectionFromScope: organizationAuthority.organizationClarificationSelectionFromScope,
|
||||
lastOrganizationClarificationDebug,
|
||||
resolveOrganizationSelectionFromMessage: deps.resolveOrganizationSelectionFromMessage,
|
||||
toNonEmptyString: deps.toNonEmptyString,
|
||||
normalizeOrganizationScopeValue: deps.normalizeOrganizationScopeValue
|
||||
});
|
||||
const explicitOrganizationClarificationSelection = organizationClarificationContinuation.explicitSelection;
|
||||
const organizationClarificationSelection = organizationClarificationContinuation.selection;
|
||||
const hasOrganizationClarificationContinuation = organizationClarificationContinuation.hasContinuation;
|
||||
const carryoverSourceDebug = previousAddressDebug ??
|
||||
(hasOrganizationClarificationContinuation ? lastOrganizationClarificationDebug : null);
|
||||
const followupOffer = carryoverSourceDebug ? deps.buildAddressFollowupOffer(carryoverSourceDebug) : null;
|
||||
|
|
|
|||
|
|
@ -86,6 +86,12 @@ export interface AssistantOrganizationAuthority {
|
|||
organizationClarificationSelectionFromScope: string | null;
|
||||
}
|
||||
|
||||
export interface AssistantOrganizationClarificationContinuation {
|
||||
explicitSelection: string | null;
|
||||
selection: string | null;
|
||||
hasContinuation: boolean;
|
||||
}
|
||||
|
||||
function fallbackToNonEmptyString(value: unknown): string | null {
|
||||
if (value === null || value === undefined) {
|
||||
return null;
|
||||
|
|
@ -1077,3 +1083,50 @@ export function resolveAssistantOrganizationAuthority(
|
|||
organizationClarificationSelectionFromScope
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveOrganizationClarificationContinuation(input: {
|
||||
rawMessages: unknown[];
|
||||
organizationClarificationCandidates?: unknown[];
|
||||
organizationClarificationSelectionFromScope?: unknown;
|
||||
lastOrganizationClarificationDebug?: Record<string, unknown> | null;
|
||||
resolveOrganizationSelectionFromMessage?: (message: string, candidates: unknown[]) => string | null;
|
||||
toNonEmptyString?: (value: unknown) => string | null;
|
||||
normalizeOrganizationScopeValue?: (value: unknown) => string | null;
|
||||
}): AssistantOrganizationClarificationContinuation {
|
||||
const toNonEmptyString = input.toNonEmptyString ?? fallbackToNonEmptyString;
|
||||
const normalizeOrganizationScopeValue =
|
||||
input.normalizeOrganizationScopeValue ?? normalizeOrganizationScopeDefault;
|
||||
const candidates = Array.isArray(input.organizationClarificationCandidates)
|
||||
? input.organizationClarificationCandidates
|
||||
: [];
|
||||
const resolveOrganizationSelectionFromMessage = input.resolveOrganizationSelectionFromMessage;
|
||||
const messages = Array.isArray(input.rawMessages) ? input.rawMessages : [];
|
||||
let explicitSelection: string | null = null;
|
||||
|
||||
if (typeof resolveOrganizationSelectionFromMessage === "function") {
|
||||
for (const message of messages) {
|
||||
const normalizedMessage = toNonEmptyString(message);
|
||||
if (!normalizedMessage) {
|
||||
continue;
|
||||
}
|
||||
explicitSelection = resolveOrganizationSelectionFromMessage(normalizedMessage, candidates);
|
||||
if (explicitSelection) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const normalizedScopeSelection = normalizeOrganizationScopeValue(input.organizationClarificationSelectionFromScope);
|
||||
const selection =
|
||||
explicitSelection ??
|
||||
(normalizedScopeSelection &&
|
||||
candidates.some((candidate) => normalizeOrganizationScopeValue(candidate) === normalizedScopeSelection)
|
||||
? normalizedScopeSelection
|
||||
: null);
|
||||
|
||||
return {
|
||||
explicitSelection,
|
||||
selection,
|
||||
hasContinuation: Boolean(input.lastOrganizationClarificationDebug && selection)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,11 +92,15 @@ function modeFor(pilot: AssistantMcpDiscoveryPilotExecutionContract): AssistantM
|
|||
function isValueFlowPilot(pilot: AssistantMcpDiscoveryPilotExecutionContract): boolean {
|
||||
return (
|
||||
pilot.pilot_scope === "counterparty_value_flow_query_movements_v1" ||
|
||||
pilot.pilot_scope === "counterparty_supplier_payout_query_movements_v1"
|
||||
pilot.pilot_scope === "counterparty_supplier_payout_query_movements_v1" ||
|
||||
pilot.pilot_scope === "counterparty_bidirectional_value_flow_query_movements_v1"
|
||||
);
|
||||
}
|
||||
|
||||
function headlineFor(mode: AssistantMcpDiscoveryAnswerMode, pilot: AssistantMcpDiscoveryPilotExecutionContract): string {
|
||||
if (pilot.derived_bidirectional_value_flow && mode === "confirmed_with_bounded_inference") {
|
||||
return "По данным 1С найдены строки входящих и исходящих денежных движений; нетто можно называть только как расчет по найденным строкам и проверенному периоду.";
|
||||
}
|
||||
if (pilot.derived_value_flow && mode === "confirmed_with_bounded_inference") {
|
||||
if (pilot.derived_value_flow.value_flow_direction === "outgoing_supplier_payout") {
|
||||
return "По данным 1С найдены строки исходящих платежей/списаний; сумму можно называть только в рамках проверенного периода и найденных строк.";
|
||||
|
|
@ -189,6 +193,43 @@ function derivedValueFlowConfirmedLine(pilot: AssistantMcpDiscoveryPilotExecutio
|
|||
return `По найденным строкам ${movementLabel} в 1С${counterparty}${period} ${totalLabel} ${flow.total_amount_human_ru} Учтено строк с суммой: ${flow.rows_with_amount} из ${flow.rows_matched}.${dates}${limitCaveat} ${caveat}`;
|
||||
}
|
||||
|
||||
function sideDateRange(first: string | null, latest: string | null): string {
|
||||
if (first && latest) {
|
||||
return ` первая дата ${first}, последняя ${latest}`;
|
||||
}
|
||||
return " даты движения не выделены";
|
||||
}
|
||||
|
||||
function derivedBidirectionalValueFlowConfirmedLine(pilot: AssistantMcpDiscoveryPilotExecutionContract): string | null {
|
||||
const flow = pilot.derived_bidirectional_value_flow;
|
||||
if (!flow) {
|
||||
return null;
|
||||
}
|
||||
const counterparty = flow.counterparty ? ` по контрагенту ${flow.counterparty}` : "";
|
||||
const period = flow.period_scope ? ` за период ${flow.period_scope}` : " в проверенном окне";
|
||||
const incoming = flow.incoming_customer_revenue;
|
||||
const outgoing = flow.outgoing_supplier_payout;
|
||||
const netLabel =
|
||||
flow.net_direction === "net_incoming"
|
||||
? "нетто в нашу сторону"
|
||||
: flow.net_direction === "net_outgoing"
|
||||
? "нетто исходящий"
|
||||
: "нетто нулевое";
|
||||
const limitCaveat = flow.coverage_limited_by_probe_limit
|
||||
? " Лимит строк проверки достигнут хотя бы по одной стороне; полный запрошенный период может быть покрыт не полностью."
|
||||
: "";
|
||||
return [
|
||||
`По найденным строкам 1С${counterparty}${period}: получили ${incoming.total_amount_human_ru} по входящим движениям, заплатили ${outgoing.total_amount_human_ru} по исходящим платежам/списаниям.`,
|
||||
`Расчетное ${netLabel}: ${flow.net_amount_human_ru}`,
|
||||
`Входящие строки с суммой: ${incoming.rows_with_amount} из ${incoming.rows_matched};${sideDateRange(incoming.first_movement_date, incoming.latest_movement_date)}.`,
|
||||
`Исходящие строки с суммой: ${outgoing.rows_with_amount} из ${outgoing.rows_matched};${sideDateRange(outgoing.first_movement_date, outgoing.latest_movement_date)}.`,
|
||||
`${limitCaveat} Это расчет по найденным строкам 1С, а не подтверждение полного сальдо вне проверенного окна.`
|
||||
]
|
||||
.join(" ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
export function buildAssistantMcpDiscoveryAnswerDraft(
|
||||
pilot: AssistantMcpDiscoveryPilotExecutionContract
|
||||
): AssistantMcpDiscoveryAnswerDraftContract {
|
||||
|
|
@ -205,7 +246,7 @@ export function buildAssistantMcpDiscoveryAnswerDraft(
|
|||
const inferenceLines = derivedInferenceLine
|
||||
? [derivedInferenceLine]
|
||||
: pilot.evidence.inferred_facts;
|
||||
const derivedValueLine = derivedValueFlowConfirmedLine(pilot);
|
||||
const derivedValueLine = derivedBidirectionalValueFlowConfirmedLine(pilot) ?? derivedValueFlowConfirmedLine(pilot);
|
||||
const confirmedLines = derivedValueLine
|
||||
? [...pilot.evidence.confirmed_facts, derivedValueLine]
|
||||
: pilot.evidence.confirmed_facts;
|
||||
|
|
|
|||
|
|
@ -54,10 +54,33 @@ export interface AssistantMcpDiscoveryDerivedValueFlow {
|
|||
inference_basis: "sum_of_confirmed_1c_value_flow_rows";
|
||||
}
|
||||
|
||||
export interface AssistantMcpDiscoveryValueFlowSideSummary {
|
||||
rows_matched: number;
|
||||
rows_with_amount: number;
|
||||
total_amount: number;
|
||||
total_amount_human_ru: string;
|
||||
first_movement_date: string | null;
|
||||
latest_movement_date: string | null;
|
||||
coverage_limited_by_probe_limit: boolean;
|
||||
}
|
||||
|
||||
export interface AssistantMcpDiscoveryDerivedBidirectionalValueFlow {
|
||||
counterparty: string | null;
|
||||
period_scope: string | null;
|
||||
incoming_customer_revenue: AssistantMcpDiscoveryValueFlowSideSummary;
|
||||
outgoing_supplier_payout: AssistantMcpDiscoveryValueFlowSideSummary;
|
||||
net_amount: number;
|
||||
net_amount_human_ru: string;
|
||||
net_direction: "net_incoming" | "net_outgoing" | "balanced";
|
||||
coverage_limited_by_probe_limit: boolean;
|
||||
inference_basis: "incoming_minus_outgoing_confirmed_1c_value_flow_rows";
|
||||
}
|
||||
|
||||
export type AssistantMcpDiscoveryPilotScope =
|
||||
| "counterparty_lifecycle_query_documents_v1"
|
||||
| "counterparty_value_flow_query_movements_v1"
|
||||
| "counterparty_supplier_payout_query_movements_v1";
|
||||
| "counterparty_supplier_payout_query_movements_v1"
|
||||
| "counterparty_bidirectional_value_flow_query_movements_v1";
|
||||
|
||||
export interface AssistantMcpDiscoveryPilotExecutionContract {
|
||||
schema_version: typeof ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION;
|
||||
|
|
@ -73,6 +96,7 @@ export interface AssistantMcpDiscoveryPilotExecutionContract {
|
|||
source_rows_summary: string | null;
|
||||
derived_activity_period: AssistantMcpDiscoveryDerivedActivityPeriod | null;
|
||||
derived_value_flow: AssistantMcpDiscoveryDerivedValueFlow | null;
|
||||
derived_bidirectional_value_flow: AssistantMcpDiscoveryDerivedBidirectionalValueFlow | null;
|
||||
query_limitations: string[];
|
||||
reason_codes: string[];
|
||||
}
|
||||
|
|
@ -205,10 +229,12 @@ function isValueFlowPilotEligible(planner: AssistantMcpDiscoveryPlannerContract)
|
|||
interface ValueFlowPilotProfile {
|
||||
scope: Extract<
|
||||
AssistantMcpDiscoveryPilotScope,
|
||||
"counterparty_value_flow_query_movements_v1" | "counterparty_supplier_payout_query_movements_v1"
|
||||
| "counterparty_value_flow_query_movements_v1"
|
||||
| "counterparty_supplier_payout_query_movements_v1"
|
||||
| "counterparty_bidirectional_value_flow_query_movements_v1"
|
||||
>;
|
||||
recipe_intent: Extract<AddressIntent, "customer_revenue_and_payments" | "supplier_payouts_profile">;
|
||||
direction: AssistantMcpDiscoveryDerivedValueFlow["value_flow_direction"];
|
||||
recipe_intent: Extract<AddressIntent, "customer_revenue_and_payments" | "supplier_payouts_profile"> | null;
|
||||
direction: AssistantMcpDiscoveryDerivedValueFlow["value_flow_direction"] | "bidirectional_net_value_flow";
|
||||
}
|
||||
|
||||
function valueFlowPilotProfile(planner: AssistantMcpDiscoveryPlannerContract): ValueFlowPilotProfile {
|
||||
|
|
@ -216,6 +242,18 @@ function valueFlowPilotProfile(planner: AssistantMcpDiscoveryPlannerContract): V
|
|||
const action = String(meaning?.asked_action_family ?? "").toLowerCase();
|
||||
const unsupported = String(meaning?.unsupported_but_understood_family ?? "").toLowerCase();
|
||||
const combined = `${action} ${unsupported}`;
|
||||
if (
|
||||
combined.includes("net_value_flow") ||
|
||||
combined.includes("bidirectional") ||
|
||||
combined.includes("netting") ||
|
||||
combined.includes("net")
|
||||
) {
|
||||
return {
|
||||
scope: "counterparty_bidirectional_value_flow_query_movements_v1",
|
||||
recipe_intent: null,
|
||||
direction: "bidirectional_net_value_flow"
|
||||
};
|
||||
}
|
||||
if (
|
||||
combined.includes("payout") ||
|
||||
combined.includes("outflow") ||
|
||||
|
|
@ -435,6 +473,91 @@ function deriveValueFlow(
|
|||
};
|
||||
}
|
||||
|
||||
function deriveValueFlowSideSummary(
|
||||
result: AddressMcpQueryExecutorResult | null,
|
||||
probeLimit: number
|
||||
): AssistantMcpDiscoveryValueFlowSideSummary {
|
||||
if (!result || result.error || result.matched_rows <= 0) {
|
||||
return {
|
||||
rows_matched: 0,
|
||||
rows_with_amount: 0,
|
||||
total_amount: 0,
|
||||
total_amount_human_ru: formatAmountHumanRu(0),
|
||||
first_movement_date: null,
|
||||
latest_movement_date: null,
|
||||
coverage_limited_by_probe_limit: false
|
||||
};
|
||||
}
|
||||
|
||||
let totalAmount = 0;
|
||||
let rowsWithAmount = 0;
|
||||
for (const row of result.rows) {
|
||||
const amount = rowAmountValue(row);
|
||||
if (amount !== null) {
|
||||
totalAmount += amount;
|
||||
rowsWithAmount += 1;
|
||||
}
|
||||
}
|
||||
const dates = result.rows
|
||||
.map((row) => rowDateValue(row))
|
||||
.filter((value): value is string => Boolean(value))
|
||||
.sort();
|
||||
return {
|
||||
rows_matched: result.matched_rows,
|
||||
rows_with_amount: rowsWithAmount,
|
||||
total_amount: totalAmount,
|
||||
total_amount_human_ru: formatAmountHumanRu(totalAmount),
|
||||
first_movement_date: dates[0] ?? null,
|
||||
latest_movement_date: dates[dates.length - 1] ?? null,
|
||||
coverage_limited_by_probe_limit: result.matched_rows >= probeLimit
|
||||
};
|
||||
}
|
||||
|
||||
function deriveBidirectionalValueFlow(input: {
|
||||
incomingResult: AddressMcpQueryExecutorResult | null;
|
||||
outgoingResult: AddressMcpQueryExecutorResult | null;
|
||||
counterparty: string | null;
|
||||
periodScope: string | null;
|
||||
probeLimit: number;
|
||||
}): AssistantMcpDiscoveryDerivedBidirectionalValueFlow | null {
|
||||
const incoming = deriveValueFlowSideSummary(input.incomingResult, input.probeLimit);
|
||||
const outgoing = deriveValueFlowSideSummary(input.outgoingResult, input.probeLimit);
|
||||
if (incoming.rows_with_amount <= 0 && outgoing.rows_with_amount <= 0) {
|
||||
return null;
|
||||
}
|
||||
const netAmount = incoming.total_amount - outgoing.total_amount;
|
||||
return {
|
||||
counterparty: input.counterparty,
|
||||
period_scope: input.periodScope,
|
||||
incoming_customer_revenue: incoming,
|
||||
outgoing_supplier_payout: outgoing,
|
||||
net_amount: netAmount,
|
||||
net_amount_human_ru: formatAmountHumanRu(Math.abs(netAmount)),
|
||||
net_direction: netAmount > 0 ? "net_incoming" : netAmount < 0 ? "net_outgoing" : "balanced",
|
||||
coverage_limited_by_probe_limit:
|
||||
incoming.coverage_limited_by_probe_limit || outgoing.coverage_limited_by_probe_limit,
|
||||
inference_basis: "incoming_minus_outgoing_confirmed_1c_value_flow_rows"
|
||||
};
|
||||
}
|
||||
|
||||
function summarizeBidirectionalValueFlowRows(input: {
|
||||
incomingResult: AddressMcpQueryExecutorResult | null;
|
||||
outgoingResult: AddressMcpQueryExecutorResult | null;
|
||||
}): string | null {
|
||||
const incoming = input.incomingResult;
|
||||
const outgoing = input.outgoingResult;
|
||||
if (!incoming && !outgoing) {
|
||||
return null;
|
||||
}
|
||||
const incomingSummary = incoming?.error
|
||||
? "incoming value-flow query failed"
|
||||
: `${incoming?.fetched_rows ?? 0} incoming value-flow rows fetched, ${incoming?.matched_rows ?? 0} matched`;
|
||||
const outgoingSummary = outgoing?.error
|
||||
? "outgoing supplier-payout query failed"
|
||||
: `${outgoing?.fetched_rows ?? 0} outgoing supplier-payout rows fetched, ${outgoing?.matched_rows ?? 0} matched`;
|
||||
return `${incomingSummary}; ${outgoingSummary}`;
|
||||
}
|
||||
|
||||
function buildLifecycleConfirmedFacts(result: AddressMcpQueryExecutorResult, counterparty: string | null): string[] {
|
||||
if (result.error || result.matched_rows <= 0) {
|
||||
return [];
|
||||
|
|
@ -468,6 +591,24 @@ function buildValueFlowConfirmedFacts(
|
|||
];
|
||||
}
|
||||
|
||||
function buildBidirectionalValueFlowConfirmedFacts(
|
||||
derived: AssistantMcpDiscoveryDerivedBidirectionalValueFlow | null
|
||||
): string[] {
|
||||
if (!derived) {
|
||||
return [];
|
||||
}
|
||||
const hasIncoming = derived.incoming_customer_revenue.rows_matched > 0;
|
||||
const hasOutgoing = derived.outgoing_supplier_payout.rows_matched > 0;
|
||||
if (derived.counterparty) {
|
||||
return [
|
||||
`1C bidirectional value-flow rows were checked for counterparty ${derived.counterparty}: incoming=${hasIncoming ? "found" : "not_found"}, outgoing=${hasOutgoing ? "found" : "not_found"}`
|
||||
];
|
||||
}
|
||||
return [
|
||||
`1C bidirectional value-flow rows were checked for the requested counterparty scope: incoming=${hasIncoming ? "found" : "not_found"}, outgoing=${hasOutgoing ? "found" : "not_found"}`
|
||||
];
|
||||
}
|
||||
|
||||
function buildLifecycleInferredFacts(result: AddressMcpQueryExecutorResult): string[] {
|
||||
if (result.error || result.fetched_rows <= 0) {
|
||||
return [];
|
||||
|
|
@ -485,6 +626,15 @@ function buildValueFlowInferredFacts(derived: AssistantMcpDiscoveryDerivedValueF
|
|||
return ["Counterparty value-flow total was calculated from confirmed 1C movement rows"];
|
||||
}
|
||||
|
||||
function buildBidirectionalValueFlowInferredFacts(
|
||||
derived: AssistantMcpDiscoveryDerivedBidirectionalValueFlow | null
|
||||
): string[] {
|
||||
if (!derived) {
|
||||
return [];
|
||||
}
|
||||
return ["Counterparty net value-flow was calculated as incoming confirmed 1C rows minus outgoing confirmed 1C rows"];
|
||||
}
|
||||
|
||||
function buildLifecycleUnknownFacts(): string[] {
|
||||
return ["Legal registration date is not proven by this MCP discovery pilot"];
|
||||
}
|
||||
|
|
@ -514,6 +664,24 @@ function buildValueFlowUnknownFacts(
|
|||
return unknownFacts;
|
||||
}
|
||||
|
||||
function buildBidirectionalValueFlowUnknownFacts(
|
||||
periodScope: string | null,
|
||||
derived: AssistantMcpDiscoveryDerivedBidirectionalValueFlow | null
|
||||
): string[] {
|
||||
const unknownFacts: string[] = [];
|
||||
if (derived?.coverage_limited_by_probe_limit) {
|
||||
unknownFacts.push(
|
||||
"Complete requested-period coverage for bidirectional value-flow is not proven because at least one MCP discovery probe row limit was reached"
|
||||
);
|
||||
}
|
||||
unknownFacts.push(
|
||||
periodScope
|
||||
? "Full bidirectional value-flow outside the checked period is not proven by this MCP discovery pilot"
|
||||
: "Full all-time bidirectional value-flow is not proven without an explicit checked period"
|
||||
);
|
||||
return unknownFacts;
|
||||
}
|
||||
|
||||
function buildEmptyEvidence(
|
||||
planner: AssistantMcpDiscoveryPlannerContract,
|
||||
dryRun: AssistantMcpDiscoveryRuntimeDryRunContract,
|
||||
|
|
@ -557,6 +725,7 @@ export async function executeAssistantMcpDiscoveryPilot(
|
|||
source_rows_summary: null,
|
||||
derived_activity_period: null,
|
||||
derived_value_flow: null,
|
||||
derived_bidirectional_value_flow: null,
|
||||
query_limitations: ["MCP discovery pilot was blocked before execution"],
|
||||
reason_codes: reasonCodes
|
||||
};
|
||||
|
|
@ -579,6 +748,7 @@ export async function executeAssistantMcpDiscoveryPilot(
|
|||
source_rows_summary: null,
|
||||
derived_activity_period: null,
|
||||
derived_value_flow: null,
|
||||
derived_bidirectional_value_flow: null,
|
||||
query_limitations: ["MCP discovery pilot needs more scope before execution"],
|
||||
reason_codes: reasonCodes
|
||||
};
|
||||
|
|
@ -608,6 +778,7 @@ export async function executeAssistantMcpDiscoveryPilot(
|
|||
source_rows_summary: null,
|
||||
derived_activity_period: null,
|
||||
derived_value_flow: null,
|
||||
derived_bidirectional_value_flow: null,
|
||||
query_limitations: ["MCP discovery pilot scope is not implemented yet"],
|
||||
reason_codes: reasonCodes
|
||||
};
|
||||
|
|
@ -620,7 +791,117 @@ export async function executeAssistantMcpDiscoveryPilot(
|
|||
let queryResult: AddressMcpQueryExecutorResult | null = null;
|
||||
const filters = buildValueFlowFilters(planner);
|
||||
const valueFlowProfile = valueFlowPilotProfile(planner);
|
||||
const selection = selectAddressRecipe(valueFlowProfile.recipe_intent, filters);
|
||||
if (valueFlowProfile.direction === "bidirectional_net_value_flow") {
|
||||
let incomingResult: AddressMcpQueryExecutorResult | null = null;
|
||||
let outgoingResult: AddressMcpQueryExecutorResult | null = null;
|
||||
const incomingSelection = selectAddressRecipe("customer_revenue_and_payments", filters);
|
||||
const outgoingSelection = selectAddressRecipe("supplier_payouts_profile", filters);
|
||||
if (!incomingSelection.selected_recipe || !outgoingSelection.selected_recipe) {
|
||||
pushReason(reasonCodes, "pilot_bidirectional_value_flow_recipe_not_available");
|
||||
const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "Bidirectional value-flow recipes are not available");
|
||||
return {
|
||||
schema_version: ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||||
pilot_status: "unsupported",
|
||||
pilot_scope: valueFlowProfile.scope,
|
||||
dry_run: dryRun,
|
||||
mcp_execution_performed: false,
|
||||
executed_primitives: executedPrimitives,
|
||||
skipped_primitives: skippedPrimitives,
|
||||
probe_results: probeResults,
|
||||
evidence,
|
||||
source_rows_summary: null,
|
||||
derived_activity_period: null,
|
||||
derived_value_flow: null,
|
||||
derived_bidirectional_value_flow: null,
|
||||
query_limitations: ["Bidirectional value-flow recipes are not available"],
|
||||
reason_codes: reasonCodes
|
||||
};
|
||||
}
|
||||
|
||||
pushReason(reasonCodes, "pilot_bidirectional_value_flow_recipes_selected");
|
||||
const incomingRecipePlan = buildAddressRecipePlan(incomingSelection.selected_recipe, filters);
|
||||
const outgoingRecipePlan = buildAddressRecipePlan(outgoingSelection.selected_recipe, filters);
|
||||
|
||||
for (const step of dryRun.execution_steps) {
|
||||
if (step.primitive_id !== "query_movements") {
|
||||
skippedPrimitives.push(step.primitive_id);
|
||||
probeResults.push(
|
||||
skippedProbeResult(step, "pilot_bidirectional_value_flow_uses_two_query_movements_and_derives_net")
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
incomingResult = await deps.executeAddressMcpQuery({
|
||||
query: incomingRecipePlan.query,
|
||||
limit: incomingRecipePlan.limit,
|
||||
account_scope: incomingRecipePlan.account_scope
|
||||
});
|
||||
outgoingResult = await deps.executeAddressMcpQuery({
|
||||
query: outgoingRecipePlan.query,
|
||||
limit: outgoingRecipePlan.limit,
|
||||
account_scope: outgoingRecipePlan.account_scope
|
||||
});
|
||||
pushUnique(executedPrimitives, step.primitive_id);
|
||||
probeResults.push(queryResultToProbeResult(step.primitive_id, incomingResult));
|
||||
probeResults.push(queryResultToProbeResult(step.primitive_id, outgoingResult));
|
||||
if (incomingResult.error) {
|
||||
pushUnique(queryLimitations, incomingResult.error);
|
||||
pushReason(reasonCodes, "pilot_bidirectional_incoming_query_movements_mcp_error");
|
||||
}
|
||||
if (outgoingResult.error) {
|
||||
pushUnique(queryLimitations, outgoingResult.error);
|
||||
pushReason(reasonCodes, "pilot_bidirectional_outgoing_query_movements_mcp_error");
|
||||
}
|
||||
if (!incomingResult.error || !outgoingResult.error) {
|
||||
pushReason(reasonCodes, "pilot_bidirectional_query_movements_mcp_executed");
|
||||
}
|
||||
}
|
||||
|
||||
const sourceRowsSummary = summarizeBidirectionalValueFlowRows({ incomingResult, outgoingResult });
|
||||
const derivedBidirectionalValueFlow = deriveBidirectionalValueFlow({
|
||||
incomingResult,
|
||||
outgoingResult,
|
||||
counterparty,
|
||||
periodScope: dateScope,
|
||||
probeLimit: planner.discovery_plan.execution_budget.max_rows_per_probe
|
||||
});
|
||||
if (derivedBidirectionalValueFlow) {
|
||||
pushReason(reasonCodes, "pilot_derived_bidirectional_value_flow_from_confirmed_rows");
|
||||
}
|
||||
const evidence = resolveAssistantMcpDiscoveryEvidence({
|
||||
plan: planner.discovery_plan,
|
||||
probeResults,
|
||||
confirmedFacts: buildBidirectionalValueFlowConfirmedFacts(derivedBidirectionalValueFlow),
|
||||
inferredFacts: buildBidirectionalValueFlowInferredFacts(derivedBidirectionalValueFlow),
|
||||
unknownFacts: buildBidirectionalValueFlowUnknownFacts(dateScope, derivedBidirectionalValueFlow),
|
||||
sourceRowsSummary,
|
||||
queryLimitations,
|
||||
recommendedNextProbe: "explain_evidence_basis"
|
||||
});
|
||||
|
||||
return {
|
||||
schema_version: ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||||
pilot_status: "executed",
|
||||
pilot_scope: valueFlowProfile.scope,
|
||||
dry_run: dryRun,
|
||||
mcp_execution_performed: executedPrimitives.length > 0,
|
||||
executed_primitives: executedPrimitives,
|
||||
skipped_primitives: skippedPrimitives,
|
||||
probe_results: probeResults,
|
||||
evidence,
|
||||
source_rows_summary: sourceRowsSummary,
|
||||
derived_activity_period: null,
|
||||
derived_value_flow: null,
|
||||
derived_bidirectional_value_flow: derivedBidirectionalValueFlow,
|
||||
query_limitations: queryLimitations,
|
||||
reason_codes: reasonCodes
|
||||
};
|
||||
}
|
||||
|
||||
const recipeIntent = valueFlowProfile.recipe_intent;
|
||||
const selection = recipeIntent ? selectAddressRecipe(recipeIntent, filters) : { selected_recipe: null };
|
||||
if (!selection.selected_recipe) {
|
||||
pushReason(reasonCodes, "pilot_value_flow_recipe_not_available");
|
||||
const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "Value-flow recipe is not available");
|
||||
|
|
@ -638,6 +919,7 @@ export async function executeAssistantMcpDiscoveryPilot(
|
|||
source_rows_summary: null,
|
||||
derived_activity_period: null,
|
||||
derived_value_flow: null,
|
||||
derived_bidirectional_value_flow: null,
|
||||
query_limitations: ["Value-flow recipe is not available"],
|
||||
reason_codes: reasonCodes
|
||||
};
|
||||
|
|
@ -707,6 +989,7 @@ export async function executeAssistantMcpDiscoveryPilot(
|
|||
source_rows_summary: sourceRowsSummary,
|
||||
derived_activity_period: null,
|
||||
derived_value_flow: derivedValueFlow,
|
||||
derived_bidirectional_value_flow: null,
|
||||
query_limitations: queryLimitations,
|
||||
reason_codes: reasonCodes
|
||||
};
|
||||
|
|
@ -732,6 +1015,7 @@ export async function executeAssistantMcpDiscoveryPilot(
|
|||
source_rows_summary: null,
|
||||
derived_activity_period: null,
|
||||
derived_value_flow: null,
|
||||
derived_bidirectional_value_flow: null,
|
||||
query_limitations: ["Lifecycle recipe is not available"],
|
||||
reason_codes: reasonCodes
|
||||
};
|
||||
|
|
@ -789,6 +1073,7 @@ export async function executeAssistantMcpDiscoveryPilot(
|
|||
source_rows_summary: sourceRowsSummary,
|
||||
derived_activity_period: derivedActivityPeriod,
|
||||
derived_value_flow: null,
|
||||
derived_bidirectional_value_flow: null,
|
||||
query_limitations: queryLimitations,
|
||||
reason_codes: reasonCodes
|
||||
};
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ function recipeFor(input: AssistantMcpDiscoveryPlannerInput): PlannerRecipe {
|
|||
const axes: string[] = [];
|
||||
addScopeAxes(axes, meaning);
|
||||
|
||||
if (includesAny(combined, ["turnover", "revenue", "payment", "payout", "value"])) {
|
||||
if (includesAny(combined, ["turnover", "revenue", "payment", "payout", "value", "net", "netting", "balance", "cashflow"])) {
|
||||
pushUnique(axes, "aggregate_axis");
|
||||
pushUnique(axes, "amount");
|
||||
pushUnique(axes, "coverage_target");
|
||||
|
|
|
|||
|
|
@ -112,6 +112,22 @@ function localizeLine(value: string): string {
|
|||
if (/^1C supplier-payout rows were found for the requested counterparty scope$/i.test(value)) {
|
||||
return "В 1С найдены строки исходящих платежей/списаний по запрошенному контрагентскому контуру.";
|
||||
}
|
||||
const bidirectionalMatch = value.match(
|
||||
/^1C bidirectional value-flow rows were checked for counterparty\s+(.+): incoming=(found|not_found), outgoing=(found|not_found)$/i
|
||||
);
|
||||
if (bidirectionalMatch) {
|
||||
const incoming = bidirectionalMatch[2] === "found" ? "входящие строки найдены" : "входящие строки не найдены";
|
||||
const outgoing = bidirectionalMatch[3] === "found" ? "исходящие строки найдены" : "исходящие строки не найдены";
|
||||
return `В 1С проверены входящие и исходящие денежные строки по контрагенту ${bidirectionalMatch[1]}: ${incoming}, ${outgoing}.`;
|
||||
}
|
||||
const bidirectionalScopeMatch = value.match(
|
||||
/^1C bidirectional value-flow rows were checked for the requested counterparty scope: incoming=(found|not_found), outgoing=(found|not_found)$/i
|
||||
);
|
||||
if (bidirectionalScopeMatch) {
|
||||
const incoming = bidirectionalScopeMatch[1] === "found" ? "входящие строки найдены" : "входящие строки не найдены";
|
||||
const outgoing = bidirectionalScopeMatch[2] === "found" ? "исходящие строки найдены" : "исходящие строки не найдены";
|
||||
return `В 1С проверены входящие и исходящие денежные строки по запрошенному контрагентскому контуру: ${incoming}, ${outgoing}.`;
|
||||
}
|
||||
if (/^Business activity duration may be inferred from first and latest confirmed 1C activity rows$/i.test(value)) {
|
||||
return "Длительность деловой активности можно оценивать только как вывод по первой и последней подтвержденной строке активности в 1С.";
|
||||
}
|
||||
|
|
@ -121,12 +137,18 @@ function localizeLine(value: string): string {
|
|||
if (/^Counterparty supplier-payout total was calculated from confirmed 1C outgoing payment rows$/i.test(value)) {
|
||||
return "Сумма исходящих платежей рассчитана только по подтвержденным строкам списаний в 1С.";
|
||||
}
|
||||
if (/^Counterparty net value-flow was calculated as incoming confirmed 1C rows minus outgoing confirmed 1C rows$/i.test(value)) {
|
||||
return "Нетто денежного потока рассчитано только как входящие подтвержденные строки 1С минус исходящие подтвержденные строки 1С.";
|
||||
}
|
||||
if (/^Legal registration date is not proven by this MCP discovery pilot$/i.test(value)) {
|
||||
return "Юридическая дата регистрации этим поиском не подтверждена.";
|
||||
}
|
||||
if (/^Complete requested-period coverage is not proven because the MCP discovery probe row limit was reached$/i.test(value)) {
|
||||
return "Полное покрытие запрошенного периода не подтверждено: проверка достигла лимита найденных строк.";
|
||||
}
|
||||
if (/^Complete requested-period coverage for bidirectional value-flow is not proven because at least one MCP discovery probe row limit was reached$/i.test(value)) {
|
||||
return "Полное покрытие запрошенного периода по двустороннему денежному потоку не подтверждено: хотя бы одна сторона проверки достигла лимита найденных строк.";
|
||||
}
|
||||
if (/^Full turnover outside the checked period is not proven by this MCP discovery pilot$/i.test(value)) {
|
||||
return "Полный оборот вне проверенного периода этим поиском не подтвержден.";
|
||||
}
|
||||
|
|
@ -139,6 +161,12 @@ function localizeLine(value: string): string {
|
|||
if (/^Full all-time supplier-payout amount is not proven without an explicit checked period$/i.test(value)) {
|
||||
return "Полный объем исходящих платежей за все время без явно проверенного периода не подтвержден.";
|
||||
}
|
||||
if (/^Full bidirectional value-flow outside the checked period is not proven by this MCP discovery pilot$/i.test(value)) {
|
||||
return "Полный двусторонний денежный поток вне проверенного периода этим поиском не подтвержден.";
|
||||
}
|
||||
if (/^Full all-time bidirectional value-flow is not proven without an explicit checked period$/i.test(value)) {
|
||||
return "Полный двусторонний денежный поток за все время без явно проверенного периода не подтвержден.";
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -150,6 +150,12 @@ function hasPayoutSignal(text: string): boolean {
|
|||
);
|
||||
}
|
||||
|
||||
function hasBidirectionalValueFlowSignal(text: string): boolean {
|
||||
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 semanticNeedFor(input: {
|
||||
domain: string | null;
|
||||
action: string | null;
|
||||
|
|
@ -161,7 +167,7 @@ function semanticNeedFor(input: {
|
|||
if (input.lifecycleSignal || /(?:lifecycle|activity|duration|age)/iu.test(combined)) {
|
||||
return "counterparty lifecycle evidence";
|
||||
}
|
||||
if (input.valueFlowSignal || /(?:turnover|revenue|payment|payout|value)/iu.test(combined)) {
|
||||
if (input.valueFlowSignal || /(?:turnover|revenue|payment|payout|value|net|netting|balance|cashflow)/iu.test(combined)) {
|
||||
return "counterparty value-flow evidence";
|
||||
}
|
||||
if (/(?:document|documents|list_documents)/iu.test(combined)) {
|
||||
|
|
@ -201,8 +207,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
const reasonCodes: string[] = [];
|
||||
const rawText = compactLower(`${input.userMessage ?? ""} ${input.effectiveMessage ?? ""}`);
|
||||
const lifecycleSignal = hasLifecycleSignal(rawText);
|
||||
const valueFlowSignal = !lifecycleSignal && hasValueFlowSignal(rawText);
|
||||
const payoutSignal = valueFlowSignal && hasPayoutSignal(rawText);
|
||||
const bidirectionalValueFlowSignal = !lifecycleSignal && hasBidirectionalValueFlowSignal(rawText);
|
||||
const valueFlowSignal = !lifecycleSignal && (hasValueFlowSignal(rawText) || bidirectionalValueFlowSignal);
|
||||
const payoutSignal = valueFlowSignal && !bidirectionalValueFlowSignal && hasPayoutSignal(rawText);
|
||||
|
||||
const rawDomain = toNonEmptyString(assistantTurnMeaning?.asked_domain_family);
|
||||
const rawAction = toNonEmptyString(assistantTurnMeaning?.asked_action_family);
|
||||
|
|
@ -225,12 +232,29 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
|
||||
const turnMeaning: AssistantMcpDiscoveryTurnMeaningRef = {
|
||||
asked_domain_family: lifecycleSignal ? "counterparty_lifecycle" : valueFlowSignal ? "counterparty_value" : rawDomain,
|
||||
asked_action_family: lifecycleSignal ? "activity_duration" : valueFlowSignal ? (payoutSignal ? "payout" : "turnover") : rawAction,
|
||||
asked_action_family: lifecycleSignal
|
||||
? "activity_duration"
|
||||
: valueFlowSignal
|
||||
? bidirectionalValueFlowSignal
|
||||
? "net_value_flow"
|
||||
: payoutSignal
|
||||
? "payout"
|
||||
: "turnover"
|
||||
: rawAction,
|
||||
explicit_entity_candidates: entityCandidates,
|
||||
explicit_organization_scope: explicitOrganizationScope,
|
||||
explicit_date_scope: collectDateScope(predecomposeContract),
|
||||
unsupported_but_understood_family:
|
||||
unsupported ?? (lifecycleSignal ? "counterparty_lifecycle" : valueFlowSignal ? (payoutSignal ? "counterparty_payouts_or_outflow" : "counterparty_value_or_turnover") : null),
|
||||
unsupported ??
|
||||
(lifecycleSignal
|
||||
? "counterparty_lifecycle"
|
||||
: valueFlowSignal
|
||||
? bidirectionalValueFlowSignal
|
||||
? "counterparty_bidirectional_value_flow_or_netting"
|
||||
: payoutSignal
|
||||
? "counterparty_payouts_or_outflow"
|
||||
: "counterparty_value_or_turnover"
|
||||
: null),
|
||||
stale_replay_forbidden: Boolean(assistantTurnMeaning?.stale_replay_forbidden || unsupported || lifecycleSignal || valueFlowSignal)
|
||||
};
|
||||
|
||||
|
|
@ -284,6 +308,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
if (payoutSignal) {
|
||||
pushReason(reasonCodes, "mcp_discovery_payout_signal_detected");
|
||||
}
|
||||
if (bidirectionalValueFlowSignal) {
|
||||
pushReason(reasonCodes, "mcp_discovery_bidirectional_value_flow_signal_detected");
|
||||
}
|
||||
if (unsupported) {
|
||||
pushReason(reasonCodes, "mcp_discovery_unsupported_but_understood_turn");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// @ts-nocheck
|
||||
import {
|
||||
resolveAssistantOrganizationAuthority
|
||||
resolveAssistantOrganizationAuthority,
|
||||
resolveOrganizationClarificationContinuation
|
||||
} from "./assistantContinuityPolicy";
|
||||
|
||||
const ADDRESS_INTENTS_KEEP_ADDRESS_LANE = new Set([
|
||||
|
|
@ -409,17 +410,22 @@ export function createAssistantRoutePolicy(deps) {
|
|||
continuitySnapshot.lastGroundedAddressDebug;
|
||||
const lastOrganizationClarificationDebug = findLastOrganizationClarificationAddressDebug(sessionItems);
|
||||
const organizationClarificationCandidates = organizationAuthority.organizationClarificationCandidates;
|
||||
const organizationClarificationSelectionFromScope = organizationAuthority.organizationClarificationSelectionFromScope;
|
||||
const explicitOrganizationClarificationSelection = resolveOrganizationSelectionFromMessage(rawUserMessage, organizationClarificationCandidates) ??
|
||||
resolveOrganizationSelectionFromMessage(repairedRawUserMessage, organizationClarificationCandidates) ??
|
||||
resolveOrganizationSelectionFromMessage(effectiveAddressUserMessage, organizationClarificationCandidates) ??
|
||||
resolveOrganizationSelectionFromMessage(repairedEffectiveAddressUserMessage, organizationClarificationCandidates) ??
|
||||
null;
|
||||
const organizationClarificationSelection = explicitOrganizationClarificationSelection ??
|
||||
(organizationClarificationSelectionFromScope &&
|
||||
organizationClarificationCandidates.some((candidate) => normalizeOrganizationScopeValue(candidate) === organizationClarificationSelectionFromScope)
|
||||
? organizationClarificationSelectionFromScope
|
||||
: null);
|
||||
const organizationClarificationContinuation = resolveOrganizationClarificationContinuation({
|
||||
rawMessages: [
|
||||
rawUserMessage,
|
||||
repairedRawUserMessage,
|
||||
effectiveAddressUserMessage,
|
||||
repairedEffectiveAddressUserMessage
|
||||
],
|
||||
organizationClarificationCandidates,
|
||||
organizationClarificationSelectionFromScope: organizationAuthority.organizationClarificationSelectionFromScope,
|
||||
lastOrganizationClarificationDebug,
|
||||
resolveOrganizationSelectionFromMessage,
|
||||
toNonEmptyString,
|
||||
normalizeOrganizationScopeValue
|
||||
});
|
||||
const explicitOrganizationClarificationSelection = organizationClarificationContinuation.explicitSelection;
|
||||
const organizationClarificationSelection = organizationClarificationContinuation.selection;
|
||||
const metaSignals = resolveMetaSignalSet({
|
||||
rawUserMessage,
|
||||
repairedRawUserMessage,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
readAddressDebugFilters,
|
||||
readAddressDebugItem,
|
||||
readAddressDebugTemporalScope,
|
||||
resolveOrganizationClarificationContinuation,
|
||||
resolveNavigationSessionContextState,
|
||||
resolveAddressDebugCarryoverFilters,
|
||||
resolveAddressDebugAnchorContext,
|
||||
|
|
@ -414,17 +415,18 @@ export function createAssistantTransitionPolicy(deps) {
|
|||
const organizationClarificationCandidates = Array.isArray(organizationAuthority.organizationClarificationCandidates)
|
||||
? organizationAuthority.organizationClarificationCandidates
|
||||
: [];
|
||||
const explicitOrganizationClarificationSelection =
|
||||
deps.resolveOrganizationSelectionFromMessage(userMessage, organizationClarificationCandidates) ??
|
||||
(deps.toNonEmptyString(alternateMessage)
|
||||
? deps.resolveOrganizationSelectionFromMessage(String(alternateMessage ?? ""), organizationClarificationCandidates)
|
||||
: null);
|
||||
const organizationClarificationSelection =
|
||||
explicitOrganizationClarificationSelection ??
|
||||
deps.normalizeOrganizationScopeValue(organizationAuthority.organizationClarificationSelectionFromScope);
|
||||
const hasOrganizationClarificationContinuation = Boolean(
|
||||
lastOrganizationClarificationDebug && organizationClarificationSelection
|
||||
);
|
||||
const organizationClarificationContinuation = resolveOrganizationClarificationContinuation({
|
||||
rawMessages: [userMessage, alternateMessage],
|
||||
organizationClarificationCandidates,
|
||||
organizationClarificationSelectionFromScope: organizationAuthority.organizationClarificationSelectionFromScope,
|
||||
lastOrganizationClarificationDebug,
|
||||
resolveOrganizationSelectionFromMessage: deps.resolveOrganizationSelectionFromMessage,
|
||||
toNonEmptyString: deps.toNonEmptyString,
|
||||
normalizeOrganizationScopeValue: deps.normalizeOrganizationScopeValue
|
||||
});
|
||||
const explicitOrganizationClarificationSelection = organizationClarificationContinuation.explicitSelection;
|
||||
const organizationClarificationSelection = organizationClarificationContinuation.selection;
|
||||
const hasOrganizationClarificationContinuation = organizationClarificationContinuation.hasContinuation;
|
||||
const carryoverSourceDebug =
|
||||
previousAddressDebug ??
|
||||
(hasOrganizationClarificationContinuation ? lastOrganizationClarificationDebug : null);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
resolveAddressDebugCarryoverFilters,
|
||||
resolveAddressDebugContextFacts,
|
||||
resolveAddressDebugAnchorContext,
|
||||
resolveOrganizationClarificationContinuation,
|
||||
resolveDisplayedEntityFollowupRetarget,
|
||||
resolveFollowupTargetIntent,
|
||||
resolveInventoryFollowupPivotFlags,
|
||||
|
|
@ -146,6 +147,27 @@ describe("assistantContinuityPolicy organization authority", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("resolves organization clarification continuation through one shared helper", () => {
|
||||
const continuation = resolveOrganizationClarificationContinuation({
|
||||
rawMessages: ["Альтернатива Плюс", null, " "],
|
||||
organizationClarificationCandidates: ["ООО Альтернатива Плюс", "РАЙМ"],
|
||||
organizationClarificationSelectionFromScope: "РАЙМ",
|
||||
lastOrganizationClarificationDebug: {
|
||||
organization_candidates: ["ООО Альтернатива Плюс", "РАЙМ"]
|
||||
},
|
||||
resolveOrganizationSelectionFromMessage: (message, candidates) =>
|
||||
/альтернатива/i.test(String(message))
|
||||
? String(candidates[0] ?? "")
|
||||
: null
|
||||
});
|
||||
|
||||
expect(continuation).toEqual({
|
||||
explicitSelection: "ООО Альтернатива Плюс",
|
||||
selection: "ООО Альтернатива Плюс",
|
||||
hasContinuation: true
|
||||
});
|
||||
});
|
||||
|
||||
it("resolves carryover temporal scope and inferred anchor from debug filters when explicit anchor fields are absent", () => {
|
||||
const debug = {
|
||||
extracted_filters: {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,22 @@ function buildDeps(rows: Array<Record<string, unknown>>, error: string | null =
|
|||
};
|
||||
}
|
||||
|
||||
function buildSequentialDeps(results: Array<{ rows: Array<Record<string, unknown>>; error?: string | null }>) {
|
||||
const executeAddressMcpQuery = vi.fn(async () => {
|
||||
const next = results.shift() ?? { rows: [] };
|
||||
const rows = next.rows;
|
||||
const error = next.error ?? null;
|
||||
return {
|
||||
fetched_rows: rows.length,
|
||||
matched_rows: error ? 0 : rows.length,
|
||||
raw_rows: rows,
|
||||
rows: error ? [] : rows,
|
||||
error
|
||||
};
|
||||
});
|
||||
return { executeAddressMcpQuery };
|
||||
}
|
||||
|
||||
describe("assistant MCP discovery answer adapter", () => {
|
||||
it("turns confirmed lifecycle evidence into a human-safe bounded answer draft", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
|
|
@ -139,6 +155,42 @@ describe("assistant MCP discovery answer adapter", () => {
|
|||
expect(draft.unknown_lines).toContain("Full supplier-payout amount outside the checked period is not proven by this MCP discovery pilot");
|
||||
});
|
||||
|
||||
it("turns bidirectional value-flow evidence into a bounded net cash answer draft", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
turnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "net_value_flow",
|
||||
explicit_entity_candidates: ["SVK"],
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "counterparty_bidirectional_value_flow_or_netting"
|
||||
}
|
||||
});
|
||||
const pilot = await executeAssistantMcpDiscoveryPilot(
|
||||
planner,
|
||||
buildSequentialDeps([
|
||||
{
|
||||
rows: [
|
||||
{ Period: "2020-01-15T00:00:00", Amount: 10000, Counterparty: "SVK" },
|
||||
{ Period: "2020-02-20T00:00:00", Amount: "2 500,50", Counterparty: "SVK" }
|
||||
]
|
||||
},
|
||||
{ rows: [{ Period: "2020-03-10T00:00:00", Amount: 4000, Counterparty: "SVK" }] }
|
||||
])
|
||||
);
|
||||
|
||||
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
|
||||
const confirmedText = draft.confirmed_lines.join("\n");
|
||||
|
||||
expect(draft.answer_mode).toBe("confirmed_with_bounded_inference");
|
||||
expect(draft.headline).toContain("входящих и исходящих денежных движений");
|
||||
expect(confirmedText).toContain("получили 12 500,50 руб.");
|
||||
expect(confirmedText).toContain("заплатили 4 000 руб.");
|
||||
expect(confirmedText).toContain("нетто в нашу сторону: 8 500,50 руб.");
|
||||
expect(draft.inference_lines.join("\n")).toContain("net value-flow");
|
||||
expect(draft.unknown_lines).toContain("Full bidirectional value-flow outside the checked period is not proven by this MCP discovery pilot");
|
||||
expect(draft.must_not_claim).toContain("Do not present a derived sum as a legal/accounting final total outside the checked 1C rows.");
|
||||
});
|
||||
|
||||
it("does not leak primitive names or query text into user-facing lines", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
turnMeaning: {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,22 @@ function buildDeps(rows: Array<Record<string, unknown>>, error: string | null =
|
|||
};
|
||||
}
|
||||
|
||||
function buildSequentialDeps(results: Array<{ rows: Array<Record<string, unknown>>; error?: string | null }>) {
|
||||
const executeAddressMcpQuery = vi.fn(async () => {
|
||||
const next = results.shift() ?? { rows: [] };
|
||||
const rows = next.rows;
|
||||
const error = next.error ?? null;
|
||||
return {
|
||||
fetched_rows: rows.length,
|
||||
matched_rows: error ? 0 : rows.length,
|
||||
raw_rows: rows,
|
||||
rows: error ? [] : rows,
|
||||
error
|
||||
};
|
||||
});
|
||||
return { executeAddressMcpQuery };
|
||||
}
|
||||
|
||||
describe("assistant MCP discovery pilot executor", () => {
|
||||
it("executes only the lifecycle query_documents primitive through injected MCP deps", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
|
|
@ -183,6 +199,67 @@ describe("assistant MCP discovery pilot executor", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("executes bidirectional value-flow queries and derives guarded net cash flow", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
turnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "net_value_flow",
|
||||
explicit_entity_candidates: ["SVK"],
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "counterparty_bidirectional_value_flow_or_netting"
|
||||
}
|
||||
});
|
||||
const deps = buildSequentialDeps([
|
||||
{
|
||||
rows: [
|
||||
{ Period: "2020-01-15T00:00:00", Amount: 10000, Counterparty: "SVK" },
|
||||
{ Period: "2020-02-20T00:00:00", Amount: "2 500,50", Counterparty: "SVK" }
|
||||
]
|
||||
},
|
||||
{
|
||||
rows: [{ Period: "2020-03-10T00:00:00", Amount: 4000, Counterparty: "SVK" }]
|
||||
}
|
||||
]);
|
||||
|
||||
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
|
||||
|
||||
expect(result.pilot_status).toBe("executed");
|
||||
expect(result.pilot_scope).toBe("counterparty_bidirectional_value_flow_query_movements_v1");
|
||||
expect(result.mcp_execution_performed).toBe(true);
|
||||
expect(result.executed_primitives).toEqual(["query_movements"]);
|
||||
expect(result.derived_value_flow).toBeNull();
|
||||
expect(result.derived_bidirectional_value_flow).toMatchObject({
|
||||
counterparty: "SVK",
|
||||
period_scope: "2020",
|
||||
net_amount: 8500.5,
|
||||
net_amount_human_ru: "8 500,50 руб.",
|
||||
net_direction: "net_incoming",
|
||||
coverage_limited_by_probe_limit: false,
|
||||
incoming_customer_revenue: {
|
||||
rows_matched: 2,
|
||||
rows_with_amount: 2,
|
||||
total_amount: 12500.5,
|
||||
first_movement_date: "2020-01-15",
|
||||
latest_movement_date: "2020-02-20"
|
||||
},
|
||||
outgoing_supplier_payout: {
|
||||
rows_matched: 1,
|
||||
rows_with_amount: 1,
|
||||
total_amount: 4000,
|
||||
first_movement_date: "2020-03-10",
|
||||
latest_movement_date: "2020-03-10"
|
||||
}
|
||||
});
|
||||
expect(result.evidence.confirmed_facts[0]).toContain("bidirectional value-flow rows");
|
||||
expect(result.evidence.inferred_facts[0]).toContain("net value-flow");
|
||||
expect(result.evidence.unknown_facts).toContain(
|
||||
"Full bidirectional value-flow outside the checked period is not proven by this MCP discovery pilot"
|
||||
);
|
||||
expect(result.reason_codes).toContain("pilot_bidirectional_value_flow_recipes_selected");
|
||||
expect(result.reason_codes).toContain("pilot_derived_bidirectional_value_flow_from_confirmed_rows");
|
||||
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("keeps non-lifecycle ready plans unsupported until a dedicated pilot exists", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
turnMeaning: {
|
||||
|
|
|
|||
|
|
@ -112,6 +112,45 @@ describe("assistant MCP discovery response candidate", () => {
|
|||
expect(candidate.reply_text).not.toContain("query_movements");
|
||||
});
|
||||
|
||||
it("localizes bidirectional net value-flow evidence without leaking pilot mechanics", () => {
|
||||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||||
entryPoint({
|
||||
bridge: {
|
||||
bridge_status: "answer_draft_ready",
|
||||
user_facing_response_allowed: true,
|
||||
business_fact_answer_allowed: true,
|
||||
requires_user_clarification: false,
|
||||
answer_draft: {
|
||||
answer_mode: "confirmed_with_bounded_inference",
|
||||
headline: "По данным 1С найдены строки входящих и исходящих денежных движений; нетто можно называть только как расчет по найденным строкам и проверенному периоду.",
|
||||
confirmed_lines: [
|
||||
"1C bidirectional value-flow rows were checked for counterparty SVK: incoming=found, outgoing=found",
|
||||
"По найденным строкам 1С по контрагенту SVK за период 2020: получили 12 500,50 руб. по входящим движениям, заплатили 4 000 руб. по исходящим платежам/списаниям. Расчетное нетто в нашу сторону: 8 500,50 руб."
|
||||
],
|
||||
inference_lines: [
|
||||
"Counterparty net value-flow was calculated as incoming confirmed 1C rows minus outgoing confirmed 1C rows"
|
||||
],
|
||||
unknown_lines: [
|
||||
"Complete requested-period coverage for bidirectional value-flow is not proven because at least one MCP discovery probe row limit was reached",
|
||||
"Full bidirectional value-flow outside the checked period is not proven by this MCP discovery pilot"
|
||||
],
|
||||
limitation_lines: ["pilot_bidirectional_value_flow_uses_two_query_movements_and_derives_net"],
|
||||
next_step_line: null
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
expect(candidate.candidate_status).toBe("ready_for_guarded_use");
|
||||
expect(candidate.reply_text).toContain("В 1С проверены входящие и исходящие денежные строки по контрагенту SVK");
|
||||
expect(candidate.reply_text).toContain("получили 12 500,50 руб.");
|
||||
expect(candidate.reply_text).toContain("заплатили 4 000 руб.");
|
||||
expect(candidate.reply_text).toContain("нетто в нашу сторону: 8 500,50 руб.");
|
||||
expect(candidate.reply_text).toContain("Полное покрытие запрошенного периода по двустороннему денежному потоку не подтверждено");
|
||||
expect(candidate.reply_text).not.toContain("pilot_");
|
||||
expect(candidate.reply_text).not.toContain("query_movements");
|
||||
});
|
||||
|
||||
it("returns not applicable when discovery was skipped for an exact supported route", () => {
|
||||
const candidate = buildAssistantMcpDiscoveryResponseCandidate({
|
||||
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||
|
|
|
|||
|
|
@ -108,6 +108,29 @@ describe("assistant MCP discovery turn input adapter", () => {
|
|||
expect(result.reason_codes).toContain("mcp_discovery_payout_signal_detected");
|
||||
});
|
||||
|
||||
it("keeps net cash wording as bidirectional value-flow discovery", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage: "какое нетто по деньгам с Группа СВК за 2020: сколько получили и сколько заплатили?",
|
||||
predecomposeContract: {
|
||||
entities: { counterparty: "Группа СВК" },
|
||||
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.adapter_status).toBe("ready");
|
||||
expect(result.semantic_data_need).toBe("counterparty value-flow evidence");
|
||||
expect(result.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "net_value_flow",
|
||||
explicit_entity_candidates: ["Группа СВК"],
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "counterparty_bidirectional_value_flow_or_netting",
|
||||
stale_replay_forbidden: true
|
||||
});
|
||||
expect(result.reason_codes).toContain("mcp_discovery_bidirectional_value_flow_signal_detected");
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_payout_signal_detected");
|
||||
});
|
||||
|
||||
it("does not activate discovery for supported exact current-turn intent", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
assistantTurnMeaning: {
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,254 @@
|
|||
{
|
||||
"suite_id": "assistant_autogen_gen-mo6rqcw8-kuswg6b",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_autogen_suite_v0_1",
|
||||
"generated_at": "2026-04-20T05:42:42.633Z",
|
||||
"generation_id": "gen-mo6rqcw8-kuswg6b",
|
||||
"mode": "qwen_seed",
|
||||
"domain": null,
|
||||
"scenario_count": 15,
|
||||
"case_ids": [
|
||||
"AUTO-001",
|
||||
"AUTO-002",
|
||||
"AUTO-003",
|
||||
"AUTO-004",
|
||||
"AUTO-005",
|
||||
"AUTO-006",
|
||||
"AUTO-007",
|
||||
"AUTO-008",
|
||||
"AUTO-009",
|
||||
"AUTO-010",
|
||||
"AUTO-011",
|
||||
"AUTO-012",
|
||||
"AUTO-013",
|
||||
"AUTO-014",
|
||||
"AUTO-015"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "AUTO-001",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "По каким контрагентам у нас висит сальдо, которое уже больше месяца не менялось и выглядит как застывший долг?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-002",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Где у нас поставщики, которые давно отгружали, но оплаты так и нет — можно ли их считать просроченными или это просто задержка в документах?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-003",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Покажи всех клиентов, по которым есть отгрузки, но денег с них не поступало уже больше 60 дней — какие из них вообще активны?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-004",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "По каким контрагентам у нас есть оплаты, но нет закрытия счетов, и это выглядит как архивный кэш?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-005",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Кто из заказчиков давно не платил, но при этом по ним висят открытые договора — можно ли считать их активными или уже нужно смотреть на отключение?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-006",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Где у нас авансы, которые уже прошли 3 месяца после получения, но не закрыты ни документами, ни сверками?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-007",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Покажи все контрагенты, по которым в 1С указано «договор активен», а оплаты и отгрузки — ноль за последние полгода"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-008",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "По каким поставщикам у нас есть счета-фактуры, но документы об оплате не приходили?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-009",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "А может быть, они вообще висят?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-010",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Какие клиенты уже давно получали товары, но по ним нет актов сверки и счёт-фактур — возможно, это ошибка или просто пропущенные действия?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-011",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Где у нас есть документы по отгрузке, но оплаты не видно — может, деньги пришли, но не привязались?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-012",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Покажи всех контрагентов с незакрытыми договорами и остатками в долгах свыше 100 тыс. — кто из них реально проблемный?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-013",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Какие поставщики у нас на текущий момент имеют несогласованные остатки, и где это может повлиять на отчётность?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-014",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Где у нас зависшие взаиморасчёты, когда документы есть, но оплаты нет — можно ли считать это технической ошибкой или уже ручная проверка нужна?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-015",
|
||||
"scenario_tag": "qwen_seed_general",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "По каким контрагентам в 1С отображается активность, но при этом у них ноль поставок и оплат за год?"
|
||||
}
|
||||
],
|
||||
"expected_hints": {
|
||||
"expected_reply_type": null,
|
||||
"expected_degraded_to": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
{
|
||||
"suite_id": "assistant_autogen_runtime_job-EYyK1lOCNB",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_autogen_runtime_v0_1",
|
||||
"scenario_count": 15,
|
||||
"case_ids": [
|
||||
"AUTO-001",
|
||||
"AUTO-002",
|
||||
"AUTO-003",
|
||||
"AUTO-004",
|
||||
"AUTO-005",
|
||||
"AUTO-006",
|
||||
"AUTO-007",
|
||||
"AUTO-008",
|
||||
"AUTO-009",
|
||||
"AUTO-010",
|
||||
"AUTO-011",
|
||||
"AUTO-012",
|
||||
"AUTO-013",
|
||||
"AUTO-014",
|
||||
"AUTO-015"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "AUTO-001",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "По каким контрагентам у нас висит сальдо, которое уже больше месяца не менялось и выглядит как застывший долг?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-002",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Где у нас поставщики, которые давно отгружали, но оплаты так и нет — можно ли их считать просроченными или это просто задержка в документах?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-003",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Покажи всех клиентов, по которым есть отгрузки, но денег с них не поступало уже больше 60 дней — какие из них вообще активны?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-004",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "По каким контрагентам у нас есть оплаты, но нет закрытия счетов, и это выглядит как архивный кэш?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-005",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Кто из заказчиков давно не платил, но при этом по ним висят открытые договора — можно ли считать их активными или уже нужно смотреть на отключение?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-006",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Где у нас авансы, которые уже прошли 3 месяца после получения, но не закрыты ни документами, ни сверками?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-007",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Покажи все контрагенты, по которым в 1С указано «договор активен», а оплаты и отгрузки — ноль за последние полгода"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-008",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "По каким поставщикам у нас есть счета-фактуры, но документы об оплате не приходили?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-009",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "А может быть, они вообще висят?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-010",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Какие клиенты уже давно получали товары, но по ним нет актов сверки и счёт-фактур — возможно, это ошибка или просто пропущенные действия?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-011",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Где у нас есть документы по отгрузке, но оплаты не видно — может, деньги пришли, но не привязались?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-012",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Покажи всех контрагентов с незакрытыми договорами и остатками в долгах свыше 100 тыс. — кто из них реально проблемный?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-013",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Какие поставщики у нас на текущий момент имеют несогласованные остатки, и где это может повлиять на отчётность?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-014",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Где у нас зависшие взаиморасчёты, когда документы есть, но оплаты нет — можно ли считать это технической ошибкой или уже ручная проверка нужна?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-015",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "По каким контрагентам в 1С отображается активность, но при этом у них ноль поставок и оплат за год?"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
{
|
||||
"suite_id": "assistant_autogen_runtime_job-l0db74IDVl",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_autogen_runtime_v0_1",
|
||||
"scenario_count": 15,
|
||||
"case_ids": [
|
||||
"AUTO-001",
|
||||
"AUTO-002",
|
||||
"AUTO-003",
|
||||
"AUTO-004",
|
||||
"AUTO-005",
|
||||
"AUTO-006",
|
||||
"AUTO-007",
|
||||
"AUTO-008",
|
||||
"AUTO-009",
|
||||
"AUTO-010",
|
||||
"AUTO-011",
|
||||
"AUTO-012",
|
||||
"AUTO-013",
|
||||
"AUTO-014",
|
||||
"AUTO-015"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "AUTO-001",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "По каким контрагентам у нас висит сальдо, которое уже больше месяца не менялось и выглядит как застывший долг?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-002",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Где у нас поставщики, которые давно отгружали, но оплаты так и нет — можно ли их считать просроченными или это просто задержка в документах?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-003",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Покажи всех клиентов, по которым есть отгрузки, но денег с них не поступало уже больше 60 дней — какие из них вообще активны?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-004",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "По каким контрагентам у нас есть оплаты, но нет закрытия счетов, и это выглядит как архивный кэш?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-005",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Кто из заказчиков давно не платил, но при этом по ним висят открытые договора — можно ли считать их активными или уже нужно смотреть на отключение?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-006",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Где у нас авансы, которые уже прошли 3 месяца после получения, но не закрыты ни документами, ни сверками?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-007",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Покажи все контрагенты, по которым в 1С указано «договор активен», а оплаты и отгрузки — ноль за последние полгода"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-008",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "По каким поставщикам у нас есть счета-фактуры, но документы об оплате не приходили?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-009",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "А может быть, они вообще висят?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-010",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Какие клиенты уже давно получали товары, но по ним нет актов сверки и счёт-фактур — возможно, это ошибка или просто пропущенные действия?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-011",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Где у нас есть документы по отгрузке, но оплаты не видно — может, деньги пришли, но не привязались?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-012",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Покажи всех контрагентов с незакрытыми договорами и остатками в долгах свыше 100 тыс. — кто из них реально проблемный?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-013",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Какие поставщики у нас на текущий момент имеют несогласованные остатки, и где это может повлиять на отчётность?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-014",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Где у нас зависшие взаиморасчёты, когда документы есть, но оплаты нет — можно ли считать это технической ошибкой или уже ручная проверка нужна?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-015",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "По каким контрагентам в 1С отображается активность, но при этом у них ноль поставок и оплат за год?"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
{
|
||||
"suite_id": "assistant_autogen_runtime_job-vLP4TFSoym",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_autogen_runtime_v0_1",
|
||||
"scenario_count": 15,
|
||||
"case_ids": [
|
||||
"AUTO-001",
|
||||
"AUTO-002",
|
||||
"AUTO-003",
|
||||
"AUTO-004",
|
||||
"AUTO-005",
|
||||
"AUTO-006",
|
||||
"AUTO-007",
|
||||
"AUTO-008",
|
||||
"AUTO-009",
|
||||
"AUTO-010",
|
||||
"AUTO-011",
|
||||
"AUTO-012",
|
||||
"AUTO-013",
|
||||
"AUTO-014",
|
||||
"AUTO-015"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "AUTO-001",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "По каким контрагентам у нас висит сальдо, которое уже больше месяца не менялось и выглядит как застывший долг?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-002",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Где у нас поставщики, которые давно отгружали, но оплаты так и нет — можно ли их считать просроченными или это просто задержка в документах?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-003",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Покажи всех клиентов, по которым есть отгрузки, но денег с них не поступало уже больше 60 дней — какие из них вообще активны?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-004",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "По каким контрагентам у нас есть оплаты, но нет закрытия счетов, и это выглядит как архивный кэш?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-005",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Кто из заказчиков давно не платил, но при этом по ним висят открытые договора — можно ли считать их активными или уже нужно смотреть на отключение?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-006",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Где у нас авансы, которые уже прошли 3 месяца после получения, но не закрыты ни документами, ни сверками?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-007",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Покажи все контрагенты, по которым в 1С указано «договор активен», а оплаты и отгрузки — ноль за последние полгода"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-008",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "По каким поставщикам у нас есть счета-фактуры, но документы об оплате не приходили?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-009",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "А может быть, они вообще висят?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-010",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Какие клиенты уже давно получали товары, но по ним нет актов сверки и счёт-фактур — возможно, это ошибка или просто пропущенные действия?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-011",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Где у нас есть документы по отгрузке, но оплаты не видно — может, деньги пришли, но не привязались?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-012",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Покажи всех контрагентов с незакрытыми договорами и остатками в долгах свыше 100 тыс. — кто из них реально проблемный?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-013",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Какие поставщики у нас на текущий момент имеют несогласованные остатки, и где это может повлиять на отчётность?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-014",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Где у нас зависшие взаиморасчёты, когда документы есть, но оплаты нет — можно ли считать это технической ошибкой или уже ручная проверка нужна?"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "AUTO-015",
|
||||
"scenario_tag": "autogen_runtime",
|
||||
"question_type": "direct",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "По каким контрагентам в 1С отображается активность, но при этом у них ноль поставок и оплат за год?"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"suite_id": "assistant_saved_session_gen-mo5zy5vo-z9klj34",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
||||
"generated_at": "2026-04-19T16:44:57.540Z",
|
||||
"generation_id": "gen-mo5zy5vo-z9klj34",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "Ручная сессия 19.04.2026, 18:58:20",
|
||||
"scenario_count": 1,
|
||||
"case_ids": [
|
||||
"SAVED-001"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "SAVED-001",
|
||||
"scenario_tag": "saved_user_sessions",
|
||||
"title": "Ручная сессия 19.04.2026, 18:58:20",
|
||||
"question_type": "followup",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "привет как дела"
|
||||
},
|
||||
{
|
||||
"user_message": "кто намс должен денег на сегодня"
|
||||
},
|
||||
{
|
||||
"user_message": "что ты можешь"
|
||||
},
|
||||
{
|
||||
"user_message": "кто нам должен денег на сегодня"
|
||||
},
|
||||
{
|
||||
"user_message": "а мы кому"
|
||||
},
|
||||
{
|
||||
"user_message": "какиек остатки на складе на сегодня"
|
||||
},
|
||||
{
|
||||
"user_message": "альтернатива"
|
||||
},
|
||||
{
|
||||
"user_message": "покажи документы по чепурнову"
|
||||
},
|
||||
{
|
||||
"user_message": "какой оборот был свк"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"suite_id": "assistant_saved_session_runtime_job-2uOHnkz2a2",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_runtime_v0_1",
|
||||
"title": "AGENT | Phase 18 semantic dialog authority replay",
|
||||
"scenario_count": 1,
|
||||
"case_ids": [
|
||||
"SAVED-001"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "SAVED-001",
|
||||
"scenario_tag": "saved_user_sessions_runtime",
|
||||
"title": "AGENT | Phase 18 semantic dialog authority replay",
|
||||
"question_type": "followup",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "приветик - че как там дела"
|
||||
},
|
||||
{
|
||||
"user_message": "расскажи что можешь интересного"
|
||||
},
|
||||
{
|
||||
"user_message": "по какой компании мы сейчас работаем?"
|
||||
},
|
||||
{
|
||||
"user_message": "Альтернатива Плюс"
|
||||
},
|
||||
{
|
||||
"user_message": "по чепурнову покажи все доки"
|
||||
},
|
||||
{
|
||||
"user_message": "какой оборот был свк"
|
||||
},
|
||||
{
|
||||
"user_message": "а чем капибара отличается от утки?"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
{
|
||||
"suite_id": "assistant_saved_session_runtime_job-KK_Wm-w0cW",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_runtime_v0_1",
|
||||
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
|
||||
"scenario_count": 1,
|
||||
"case_ids": [
|
||||
"SAVED-001"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "SAVED-001",
|
||||
"scenario_tag": "saved_user_sessions_runtime",
|
||||
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
|
||||
"question_type": "followup",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "приветик - че как там дела"
|
||||
},
|
||||
{
|
||||
"user_message": "расскажи что можешь интересного"
|
||||
},
|
||||
{
|
||||
"user_message": "кайф - что там на складе по остаткам?"
|
||||
},
|
||||
{
|
||||
"user_message": "АЛЬТЕРНАТИВА"
|
||||
},
|
||||
{
|
||||
"user_message": "а исторические остатки на другие даты умеешь?"
|
||||
},
|
||||
{
|
||||
"user_message": "давай на июль 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "март 2016"
|
||||
},
|
||||
{
|
||||
"user_message": "По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?"
|
||||
},
|
||||
{
|
||||
"user_message": "а кому продали?"
|
||||
},
|
||||
{
|
||||
"user_message": "у тебя написано кто контрагент: рабочая станция - это ошибка?"
|
||||
},
|
||||
{
|
||||
"user_message": "ндс можешь прикинуть на дату покупки рабочей станции?"
|
||||
},
|
||||
{
|
||||
"user_message": "а какой ндс мы должны сгрузить на март 2020?"
|
||||
},
|
||||
{
|
||||
"user_message": "прикинь какой ндс нам надо заплатить на февраль 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "кто у нас самый доходный клиент за все время"
|
||||
},
|
||||
{
|
||||
"user_message": "кто нам должен денег на май 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "а какой ндс мы должны примерно заплатить за этот период?"
|
||||
},
|
||||
{
|
||||
"user_message": "мы должны комуто денег на сегодня?"
|
||||
},
|
||||
{
|
||||
"user_message": "а нам?"
|
||||
},
|
||||
{
|
||||
"user_message": "какой у нас самый доходный год"
|
||||
},
|
||||
{
|
||||
"user_message": "а за 2017 мы скок заработали?"
|
||||
},
|
||||
{
|
||||
"user_message": "сколько вообще денег мы заработали за все время?"
|
||||
},
|
||||
{
|
||||
"user_message": "ты умеешь считать дельту по договорам?"
|
||||
},
|
||||
{
|
||||
"user_message": "по чепурнову покажи все доки"
|
||||
},
|
||||
{
|
||||
"user_message": "а по свк"
|
||||
},
|
||||
{
|
||||
"user_message": "а сейчас у нас есть что на складе?"
|
||||
},
|
||||
{
|
||||
"user_message": "что нам отгружал чепурнов? какой товар или услугу?"
|
||||
},
|
||||
{
|
||||
"user_message": "какие остатки на складе на сегодня"
|
||||
},
|
||||
{
|
||||
"user_message": "остатки на март 2016"
|
||||
},
|
||||
{
|
||||
"user_message": "хвосты покажи по счету 60 на август 2022"
|
||||
},
|
||||
{
|
||||
"user_message": "Есть ли остатки товара, которые закупались очень давно"
|
||||
},
|
||||
{
|
||||
"user_message": "Какие конкретно номенклатуры формируют остаток по складу на май 2020"
|
||||
},
|
||||
{
|
||||
"user_message": "а по Альтернативе Плюс сколько лет активности в базе 1С?"
|
||||
},
|
||||
{
|
||||
"user_message": "Как ты оценишь деятельность компании?"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
{
|
||||
"suite_id": "assistant_saved_session_runtime_job-U2K9_kawA3",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_runtime_v0_1",
|
||||
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
|
||||
"scenario_count": 1,
|
||||
"case_ids": [
|
||||
"SAVED-001"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "SAVED-001",
|
||||
"scenario_tag": "saved_user_sessions_runtime",
|
||||
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
|
||||
"question_type": "followup",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "приветик - че как там дела"
|
||||
},
|
||||
{
|
||||
"user_message": "расскажи что можешь интересного"
|
||||
},
|
||||
{
|
||||
"user_message": "кайф - что там на складе по остаткам?"
|
||||
},
|
||||
{
|
||||
"user_message": "АЛЬТЕРНАТИВА"
|
||||
},
|
||||
{
|
||||
"user_message": "а исторические остатки на другие даты умеешь?"
|
||||
},
|
||||
{
|
||||
"user_message": "давай на июль 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "март 2016"
|
||||
},
|
||||
{
|
||||
"user_message": "По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?"
|
||||
},
|
||||
{
|
||||
"user_message": "а кому продали?"
|
||||
},
|
||||
{
|
||||
"user_message": "у тебя написано кто контрагент: рабочая станция - это ошибка?"
|
||||
},
|
||||
{
|
||||
"user_message": "ндс можешь прикинуть на дату покупки рабочей станции?"
|
||||
},
|
||||
{
|
||||
"user_message": "а какой ндс мы должны сгрузить на март 2020?"
|
||||
},
|
||||
{
|
||||
"user_message": "прикинь какой ндс нам надо заплатить на февраль 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "кто у нас самый доходный клиент за все время"
|
||||
},
|
||||
{
|
||||
"user_message": "кто нам должен денег на май 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "а какой ндс мы должны примерно заплатить за этот период?"
|
||||
},
|
||||
{
|
||||
"user_message": "мы должны комуто денег на сегодня?"
|
||||
},
|
||||
{
|
||||
"user_message": "а нам?"
|
||||
},
|
||||
{
|
||||
"user_message": "какой у нас самый доходный год"
|
||||
},
|
||||
{
|
||||
"user_message": "а за 2017 мы скок заработали?"
|
||||
},
|
||||
{
|
||||
"user_message": "сколько вообще денег мы заработали за все время?"
|
||||
},
|
||||
{
|
||||
"user_message": "ты умеешь считать дельту по договорам?"
|
||||
},
|
||||
{
|
||||
"user_message": "по чепурнову покажи все доки"
|
||||
},
|
||||
{
|
||||
"user_message": "а по свк"
|
||||
},
|
||||
{
|
||||
"user_message": "а сейчас у нас есть что на складе?"
|
||||
},
|
||||
{
|
||||
"user_message": "что нам отгружал чепурнов? какой товар или услугу?"
|
||||
},
|
||||
{
|
||||
"user_message": "какие остатки на складе на сегодня"
|
||||
},
|
||||
{
|
||||
"user_message": "остатки на март 2016"
|
||||
},
|
||||
{
|
||||
"user_message": "хвосты покажи по счету 60 на август 2022"
|
||||
},
|
||||
{
|
||||
"user_message": "Есть ли остатки товара, которые закупались очень давно"
|
||||
},
|
||||
{
|
||||
"user_message": "Какие конкретно номенклатуры формируют остаток по складу на май 2020"
|
||||
},
|
||||
{
|
||||
"user_message": "а по Альтернативе Плюс сколько лет активности в базе 1С?"
|
||||
},
|
||||
{
|
||||
"user_message": "Как ты оценишь деятельность компании?"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"suite_id": "assistant_saved_session_runtime_job-UerO64gB9r",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_runtime_v0_1",
|
||||
"title": "Ручная сессия 19.04.2026, 18:58:20",
|
||||
"scenario_count": 1,
|
||||
"case_ids": [
|
||||
"SAVED-001"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "SAVED-001",
|
||||
"scenario_tag": "saved_user_sessions_runtime",
|
||||
"title": "Ручная сессия 19.04.2026, 18:58:20",
|
||||
"question_type": "followup",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "привет как дела"
|
||||
},
|
||||
{
|
||||
"user_message": "кто намс должен денег на сегодня"
|
||||
},
|
||||
{
|
||||
"user_message": "что ты можешь"
|
||||
},
|
||||
{
|
||||
"user_message": "кто нам должен денег на сегодня"
|
||||
},
|
||||
{
|
||||
"user_message": "а мы кому"
|
||||
},
|
||||
{
|
||||
"user_message": "какиек остатки на складе на сегодня"
|
||||
},
|
||||
{
|
||||
"user_message": "альтернатива"
|
||||
},
|
||||
{
|
||||
"user_message": "покажи документы по чепурнову"
|
||||
},
|
||||
{
|
||||
"user_message": "какой оборот был свк"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
{
|
||||
"suite_id": "assistant_saved_session_runtime_job-Z2Yw7g_j-N",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_runtime_v0_1",
|
||||
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
|
||||
"scenario_count": 1,
|
||||
"case_ids": [
|
||||
"SAVED-001"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "SAVED-001",
|
||||
"scenario_tag": "saved_user_sessions_runtime",
|
||||
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
|
||||
"question_type": "followup",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "приветик - че как там дела"
|
||||
},
|
||||
{
|
||||
"user_message": "расскажи что можешь интересного"
|
||||
},
|
||||
{
|
||||
"user_message": "кайф - что там на складе по остаткам?"
|
||||
},
|
||||
{
|
||||
"user_message": "АЛЬТЕРНАТИВА"
|
||||
},
|
||||
{
|
||||
"user_message": "а исторические остатки на другие даты умеешь?"
|
||||
},
|
||||
{
|
||||
"user_message": "давай на июль 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "март 2016"
|
||||
},
|
||||
{
|
||||
"user_message": "По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?"
|
||||
},
|
||||
{
|
||||
"user_message": "а кому продали?"
|
||||
},
|
||||
{
|
||||
"user_message": "у тебя написано кто контрагент: рабочая станция - это ошибка?"
|
||||
},
|
||||
{
|
||||
"user_message": "ндс можешь прикинуть на дату покупки рабочей станции?"
|
||||
},
|
||||
{
|
||||
"user_message": "а какой ндс мы должны сгрузить на март 2020?"
|
||||
},
|
||||
{
|
||||
"user_message": "прикинь какой ндс нам надо заплатить на февраль 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "кто у нас самый доходный клиент за все время"
|
||||
},
|
||||
{
|
||||
"user_message": "кто нам должен денег на май 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "а какой ндс мы должны примерно заплатить за этот период?"
|
||||
},
|
||||
{
|
||||
"user_message": "мы должны комуто денег на сегодня?"
|
||||
},
|
||||
{
|
||||
"user_message": "а нам?"
|
||||
},
|
||||
{
|
||||
"user_message": "какой у нас самый доходный год"
|
||||
},
|
||||
{
|
||||
"user_message": "а за 2017 мы скок заработали?"
|
||||
},
|
||||
{
|
||||
"user_message": "сколько вообще денег мы заработали за все время?"
|
||||
},
|
||||
{
|
||||
"user_message": "ты умеешь считать дельту по договорам?"
|
||||
},
|
||||
{
|
||||
"user_message": "по чепурнову покажи все доки"
|
||||
},
|
||||
{
|
||||
"user_message": "а по свк"
|
||||
},
|
||||
{
|
||||
"user_message": "а сейчас у нас есть что на складе?"
|
||||
},
|
||||
{
|
||||
"user_message": "что нам отгружал чепурнов? какой товар или услугу?"
|
||||
},
|
||||
{
|
||||
"user_message": "какие остатки на складе на сегодня"
|
||||
},
|
||||
{
|
||||
"user_message": "остатки на март 2016"
|
||||
},
|
||||
{
|
||||
"user_message": "хвосты покажи по счету 60 на август 2022"
|
||||
},
|
||||
{
|
||||
"user_message": "Есть ли остатки товара, которые закупались очень давно"
|
||||
},
|
||||
{
|
||||
"user_message": "Какие конкретно номенклатуры формируют остаток по складу на май 2020"
|
||||
},
|
||||
{
|
||||
"user_message": "а по Альтернативе Плюс сколько лет активности в базе 1С?"
|
||||
},
|
||||
{
|
||||
"user_message": "Как ты оценишь деятельность компании?"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"suite_id": "assistant_saved_session_runtime_job-_OYN2F_Y52",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_runtime_v0_1",
|
||||
"title": "AGENT | Phase 18 semantic dialog authority replay",
|
||||
"scenario_count": 1,
|
||||
"case_ids": [
|
||||
"SAVED-001"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "SAVED-001",
|
||||
"scenario_tag": "saved_user_sessions_runtime",
|
||||
"title": "AGENT | Phase 18 semantic dialog authority replay",
|
||||
"question_type": "followup",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "приветик - че как там дела"
|
||||
},
|
||||
{
|
||||
"user_message": "расскажи что можешь интересного"
|
||||
},
|
||||
{
|
||||
"user_message": "по какой компании мы сейчас работаем?"
|
||||
},
|
||||
{
|
||||
"user_message": "Альтернатива Плюс"
|
||||
},
|
||||
{
|
||||
"user_message": "по чепурнову покажи все доки"
|
||||
},
|
||||
{
|
||||
"user_message": "какой оборот был свк"
|
||||
},
|
||||
{
|
||||
"user_message": "а чем капибара отличается от утки?"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"suite_id": "assistant_saved_session_runtime_job-pP3lffpL3V",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_runtime_v0_1",
|
||||
"title": "Ручная сессия 19.04.2026, 18:58:20",
|
||||
"scenario_count": 1,
|
||||
"case_ids": [
|
||||
"SAVED-001"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "SAVED-001",
|
||||
"scenario_tag": "saved_user_sessions_runtime",
|
||||
"title": "Ручная сессия 19.04.2026, 18:58:20",
|
||||
"question_type": "followup",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "привет как дела"
|
||||
},
|
||||
{
|
||||
"user_message": "кто намс должен денег на сегодня"
|
||||
},
|
||||
{
|
||||
"user_message": "что ты можешь"
|
||||
},
|
||||
{
|
||||
"user_message": "кто нам должен денег на сегодня"
|
||||
},
|
||||
{
|
||||
"user_message": "а мы кому"
|
||||
},
|
||||
{
|
||||
"user_message": "какиек остатки на складе на сегодня"
|
||||
},
|
||||
{
|
||||
"user_message": "альтернатива"
|
||||
},
|
||||
{
|
||||
"user_message": "покажи документы по чепурнову"
|
||||
},
|
||||
{
|
||||
"user_message": "какой оборот был свк"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"suite_id": "assistant_saved_session_runtime_job-q4K6EkY6xR",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_runtime_v0_1",
|
||||
"title": "Ручная сессия 19.04.2026, 18:58:20",
|
||||
"scenario_count": 1,
|
||||
"case_ids": [
|
||||
"SAVED-001"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "SAVED-001",
|
||||
"scenario_tag": "saved_user_sessions_runtime",
|
||||
"title": "Ручная сессия 19.04.2026, 18:58:20",
|
||||
"question_type": "followup",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "привет как дела"
|
||||
},
|
||||
{
|
||||
"user_message": "кто намс должен денег на сегодня"
|
||||
},
|
||||
{
|
||||
"user_message": "что ты можешь"
|
||||
},
|
||||
{
|
||||
"user_message": "кто нам должен денег на сегодня"
|
||||
},
|
||||
{
|
||||
"user_message": "а мы кому"
|
||||
},
|
||||
{
|
||||
"user_message": "какиек остатки на складе на сегодня"
|
||||
},
|
||||
{
|
||||
"user_message": "альтернатива"
|
||||
},
|
||||
{
|
||||
"user_message": "покажи документы по чепурнову"
|
||||
},
|
||||
{
|
||||
"user_message": "какой оборот был свк"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
{
|
||||
"suite_id": "assistant_saved_session_runtime_job-vkFyPa1gBh",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_runtime_v0_1",
|
||||
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
|
||||
"scenario_count": 1,
|
||||
"case_ids": [
|
||||
"SAVED-001"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "SAVED-001",
|
||||
"scenario_tag": "saved_user_sessions_runtime",
|
||||
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
|
||||
"question_type": "followup",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "приветик - че как там дела"
|
||||
},
|
||||
{
|
||||
"user_message": "расскажи что можешь интересного"
|
||||
},
|
||||
{
|
||||
"user_message": "кайф - что там на складе по остаткам?"
|
||||
},
|
||||
{
|
||||
"user_message": "АЛЬТЕРНАТИВА"
|
||||
},
|
||||
{
|
||||
"user_message": "а исторические остатки на другие даты умеешь?"
|
||||
},
|
||||
{
|
||||
"user_message": "давай на июль 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "март 2016"
|
||||
},
|
||||
{
|
||||
"user_message": "По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?"
|
||||
},
|
||||
{
|
||||
"user_message": "а кому продали?"
|
||||
},
|
||||
{
|
||||
"user_message": "у тебя написано кто контрагент: рабочая станция - это ошибка?"
|
||||
},
|
||||
{
|
||||
"user_message": "ндс можешь прикинуть на дату покупки рабочей станции?"
|
||||
},
|
||||
{
|
||||
"user_message": "а какой ндс мы должны сгрузить на март 2020?"
|
||||
},
|
||||
{
|
||||
"user_message": "прикинь какой ндс нам надо заплатить на февраль 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "кто у нас самый доходный клиент за все время"
|
||||
},
|
||||
{
|
||||
"user_message": "кто нам должен денег на май 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "а какой ндс мы должны примерно заплатить за этот период?"
|
||||
},
|
||||
{
|
||||
"user_message": "мы должны комуто денег на сегодня?"
|
||||
},
|
||||
{
|
||||
"user_message": "а нам?"
|
||||
},
|
||||
{
|
||||
"user_message": "какой у нас самый доходный год"
|
||||
},
|
||||
{
|
||||
"user_message": "а за 2017 мы скок заработали?"
|
||||
},
|
||||
{
|
||||
"user_message": "сколько вообще денег мы заработали за все время?"
|
||||
},
|
||||
{
|
||||
"user_message": "ты умеешь считать дельту по договорам?"
|
||||
},
|
||||
{
|
||||
"user_message": "по чепурнову покажи все доки"
|
||||
},
|
||||
{
|
||||
"user_message": "а по свк"
|
||||
},
|
||||
{
|
||||
"user_message": "а сейчас у нас есть что на складе?"
|
||||
},
|
||||
{
|
||||
"user_message": "что нам отгружал чепурнов? какой товар или услугу?"
|
||||
},
|
||||
{
|
||||
"user_message": "какие остатки на складе на сегодня"
|
||||
},
|
||||
{
|
||||
"user_message": "остатки на март 2016"
|
||||
},
|
||||
{
|
||||
"user_message": "хвосты покажи по счету 60 на август 2022"
|
||||
},
|
||||
{
|
||||
"user_message": "Есть ли остатки товара, которые закупались очень давно"
|
||||
},
|
||||
{
|
||||
"user_message": "Какие конкретно номенклатуры формируют остаток по складу на май 2020"
|
||||
},
|
||||
{
|
||||
"user_message": "а по Альтернативе Плюс сколько лет активности в базе 1С?"
|
||||
},
|
||||
{
|
||||
"user_message": "Как ты оценишь деятельность компании?"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue