Удержать контекст СВК в follow-up ассистента
This commit is contained in:
parent
7e70ec1d46
commit
b3f2e405bb
|
|
@ -127,6 +127,48 @@ function readNavigationDiscoveryCounterparty(debug) {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
function readDiscoveryPilot(debug) {
|
||||
const entry = toObject(debug.assistant_mcp_discovery_entry_point_v1);
|
||||
const bridge = toObject(entry?.bridge);
|
||||
return toObject(bridge?.pilot);
|
||||
}
|
||||
function readCurrentDiscoveryCounterparty(debug) {
|
||||
const pilot = readDiscoveryPilot(debug);
|
||||
const entityResolution = toObject(pilot?.derived_entity_resolution);
|
||||
const bidirectionalValueFlow = toObject(pilot?.derived_bidirectional_value_flow);
|
||||
const valueFlow = toObject(pilot?.derived_value_flow);
|
||||
return (toNonEmptyString(entityResolution?.resolved_entity) ??
|
||||
toNonEmptyString(bidirectionalValueFlow?.counterparty) ??
|
||||
toNonEmptyString(valueFlow?.counterparty) ??
|
||||
(0, assistantContinuityPolicy_1.readAddressDebugCounterparty)(debug, toNonEmptyString) ??
|
||||
readNavigationDiscoveryCounterparty(debug));
|
||||
}
|
||||
function isCounterpartyDiscoveryChain(value) {
|
||||
return (value === "entity_resolution" ||
|
||||
value === "value_flow" ||
|
||||
value === "value_flow_comparison" ||
|
||||
value === "document_evidence" ||
|
||||
value === "movement_evidence" ||
|
||||
value === "lifecycle");
|
||||
}
|
||||
function intentFromDiscoveryChain(value) {
|
||||
if (value === "business_overview") {
|
||||
return "business_overview";
|
||||
}
|
||||
if (value === "value_flow" || value === "value_flow_comparison") {
|
||||
return "customer_revenue_and_payments";
|
||||
}
|
||||
if (value === "document_evidence") {
|
||||
return "list_documents_by_counterparty";
|
||||
}
|
||||
if (value === "movement_evidence") {
|
||||
return "bank_operations_by_counterparty";
|
||||
}
|
||||
if (value === "lifecycle") {
|
||||
return "counterparty_activity_lifecycle";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
function toAddressFocusObjectType(value) {
|
||||
const normalized = toNonEmptyString(value);
|
||||
if (!normalized) {
|
||||
|
|
@ -364,8 +406,7 @@ function readBusinessOverviewComparisonProofBundles(debug) {
|
|||
const entryPoint = toObject(debug.assistant_mcp_discovery_entry_point_v1);
|
||||
const turnInput = toObject(entryPoint?.turn_input);
|
||||
const turnMeaning = toObject(turnInput?.turn_meaning_ref);
|
||||
const bridge = toObject(entryPoint?.bridge);
|
||||
const pilot = toObject(bridge?.pilot);
|
||||
const pilot = readDiscoveryPilot(debug);
|
||||
const counterpartyValueFlowBundle = cloneRecord(turnMeaning?.previous_counterparty_value_flow_bundle) ??
|
||||
cloneRecord(pilot?.derived_bidirectional_value_flow);
|
||||
const counterpartyDocumentBundle = cloneRecord(turnMeaning?.previous_counterparty_document_bundle);
|
||||
|
|
@ -383,6 +424,18 @@ function readBusinessOverviewComparisonProofBundles(debug) {
|
|||
counterparty_document_bundle: counterpartyDocumentBundle
|
||||
};
|
||||
}
|
||||
function readCounterpartyValueFlowProofBundles(debug) {
|
||||
const pilot = readDiscoveryPilot(debug);
|
||||
const counterpartyValueFlowBundle = cloneRecord(pilot?.derived_bidirectional_value_flow);
|
||||
const counterparty = toNonEmptyString(counterpartyValueFlowBundle?.counterparty);
|
||||
if (!counterparty || !counterpartyValueFlowBundle) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
counterparty_value_flow_bundle: counterpartyValueFlowBundle,
|
||||
counterparty_document_bundle: null
|
||||
};
|
||||
}
|
||||
function buildFocusObjectFromDebug(debug, resultSetId, createdAt) {
|
||||
const extractedFilters = toObject(debug.extracted_filters) ?? {};
|
||||
const selectedDiscoveryChain = toNonEmptyString(debug.mcp_discovery_selected_chain_id);
|
||||
|
|
@ -396,8 +449,8 @@ function buildFocusObjectFromDebug(debug, resultSetId, createdAt) {
|
|||
return buildFocusObject("organization", organization, resultSetId, createdAt);
|
||||
}
|
||||
}
|
||||
if (selectedDiscoveryChain === "value_flow_comparison" && debug.mcp_discovery_response_applied === true) {
|
||||
const counterparty = (0, assistantContinuityPolicy_1.readAddressDebugCounterparty)(debug, toNonEmptyString) ?? readNavigationDiscoveryCounterparty(debug);
|
||||
if (isCounterpartyDiscoveryChain(selectedDiscoveryChain) && debug.mcp_discovery_response_applied === true) {
|
||||
const counterparty = readCurrentDiscoveryCounterparty(debug);
|
||||
if (counterparty) {
|
||||
return buildFocusObject("counterparty", counterparty, resultSetId, createdAt);
|
||||
}
|
||||
|
|
@ -587,14 +640,13 @@ function evolveAddressNavigationStateWithAssistantItem(state, item, turnIndex) {
|
|||
}
|
||||
const debug = item.debug;
|
||||
const selectedDiscoveryChain = toNonEmptyString(debug.mcp_discovery_selected_chain_id);
|
||||
const discoveryIntent = selectedDiscoveryChain === "business_overview"
|
||||
? "business_overview"
|
||||
: selectedDiscoveryChain === "value_flow_comparison"
|
||||
? "customer_revenue_and_payments"
|
||||
: "unknown";
|
||||
const detectedIntent = toNonEmptyString(debug.detected_intent);
|
||||
const intent = toAddressIntent(detectedIntent && detectedIntent !== "unknown" ? detectedIntent : discoveryIntent);
|
||||
const discoveryIntent = intentFromDiscoveryChain(selectedDiscoveryChain);
|
||||
const trackableDiscoveryTurn = debug.mcp_discovery_response_applied === true && Boolean(selectedDiscoveryChain);
|
||||
const shouldPreferDiscoveryIntent = trackableDiscoveryTurn &&
|
||||
discoveryIntent !== "unknown" &&
|
||||
selectedDiscoveryChain !== "business_overview";
|
||||
const intent = toAddressIntent(shouldPreferDiscoveryIntent ? discoveryIntent : detectedIntent && detectedIntent !== "unknown" ? detectedIntent : discoveryIntent);
|
||||
if (intent === "unknown" && !trackableDiscoveryTurn) {
|
||||
return state;
|
||||
}
|
||||
|
|
@ -603,8 +655,8 @@ function evolveAddressNavigationStateWithAssistantItem(state, item, turnIndex) {
|
|||
const routeId = toNonEmptyString(debug.selected_recipe) ?? selectedDiscoveryChain;
|
||||
const filters = sanitizeBusinessOverviewNavigationFilters(normalizeFilters(debug.extracted_filters), debug);
|
||||
const derivedOrganizationScope = resolveDerivedOrganizationScope(debug, filters, item.text) ?? (0, assistantContinuityPolicy_1.readAddressDebugOrganization)(debug, toNonEmptyString);
|
||||
const derivedCounterpartyScope = selectedDiscoveryChain === "value_flow_comparison" && debug.mcp_discovery_response_applied === true
|
||||
? (0, assistantContinuityPolicy_1.readAddressDebugCounterparty)(debug, toNonEmptyString) ?? readNavigationDiscoveryCounterparty(debug)
|
||||
const derivedCounterpartyScope = isCounterpartyDiscoveryChain(selectedDiscoveryChain) && debug.mcp_discovery_response_applied === true
|
||||
? readCurrentDiscoveryCounterparty(debug)
|
||||
: null;
|
||||
const filtersWithDerivedScope = derivedOrganizationScope && !toNonEmptyString(filters.organization)
|
||||
? {
|
||||
|
|
@ -637,8 +689,12 @@ function evolveAddressNavigationStateWithAssistantItem(state, item, turnIndex) {
|
|||
const debugFocusObject = buildFocusObjectFromDebug(debug, resultSetId, createdAt);
|
||||
const primaryEntityFocusObject = buildFocusObjectFromPrimaryEntityRef(resultSet, createdAt);
|
||||
const focusObject = resolveFocusObjectForNavigation(state, intent, resultSet, debugFocusObject, primaryEntityFocusObject);
|
||||
const rawComparisonCounterparty = selectedDiscoveryChain === "business_overview" && debug.mcp_discovery_response_applied === true
|
||||
? readNavigationDiscoveryCounterparty(debug)
|
||||
const rawComparisonCounterparty = debug.mcp_discovery_response_applied === true
|
||||
? selectedDiscoveryChain === "business_overview"
|
||||
? readNavigationDiscoveryCounterparty(debug)
|
||||
: isCounterpartyDiscoveryChain(selectedDiscoveryChain)
|
||||
? readCurrentDiscoveryCounterparty(debug)
|
||||
: null
|
||||
: null;
|
||||
const comparisonCounterparty = rawComparisonCounterparty && !shouldSuppressBusinessOverviewCounterpartyFocus(debug, rawComparisonCounterparty)
|
||||
? rawComparisonCounterparty
|
||||
|
|
@ -648,7 +704,9 @@ function evolveAddressNavigationStateWithAssistantItem(state, item, turnIndex) {
|
|||
: null;
|
||||
const currentComparisonProofBundles = selectedDiscoveryChain === "business_overview" && debug.mcp_discovery_response_applied === true
|
||||
? readBusinessOverviewComparisonProofBundles(debug)
|
||||
: null;
|
||||
: selectedDiscoveryChain === "value_flow_comparison" && debug.mcp_discovery_response_applied === true
|
||||
? readCounterpartyValueFlowProofBundles(debug)
|
||||
: null;
|
||||
const inheritedComparisonScope = state.session_context.comparison_scope;
|
||||
const inheritedComparisonProofBundles = comparisonCounterparty &&
|
||||
sameBusinessLabel(inheritedComparisonScope?.counterparty?.label, comparisonCounterparty)
|
||||
|
|
|
|||
|
|
@ -1313,6 +1313,15 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
|
|||
intent === "vat_payable_confirmed_as_of_date";
|
||||
const currentHasPeriod = hasExplicitPeriodWindow(merged);
|
||||
const previousHasPeriod = hasExplicitPeriodWindow(previous);
|
||||
const currentPeriodFromForCarryover = toNonEmptyString(merged.period_from);
|
||||
const currentPeriodToForCarryover = toNonEmptyString(merged.period_to);
|
||||
const todayIsoForPeriodCarryover = new Date().toISOString().slice(0, 10);
|
||||
const currentValueFlowPeriodDefaultsToToday = isValueCounterpartyIntent(intent) &&
|
||||
hasFollowupSignal &&
|
||||
!hasExplicitPeriodInMessage &&
|
||||
!hasExplicitCurrentDateInMessage &&
|
||||
!currentPeriodFromForCarryover &&
|
||||
currentPeriodToForCarryover === todayIsoForPeriodCarryover;
|
||||
const currentCounterpartyExplicit = toNonEmptyString(merged.counterparty);
|
||||
const currentContractExplicit = toNonEmptyString(merged.contract);
|
||||
const currentItemExplicit = toNonEmptyString(merged.item);
|
||||
|
|
@ -1387,7 +1396,7 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
|
|||
}
|
||||
reasons.push("period_from_followup_context");
|
||||
}
|
||||
if (!currentHasPeriod &&
|
||||
if ((!currentHasPeriod || currentValueFlowPeriodDefaultsToToday) &&
|
||||
previousHasPeriod &&
|
||||
hasFollowupSignal &&
|
||||
!hasExplicitPeriodInMessage &&
|
||||
|
|
|
|||
|
|
@ -778,7 +778,11 @@ function resolveFollowupTargetIntent(inventoryPurchaseDateVatBridge, selectedObj
|
|||
}
|
||||
function shouldUseNavigationTemporalCarryover(sourceIntentHint) {
|
||||
const normalizedIntent = fallbackToNonEmptyString(sourceIntentHint);
|
||||
return (normalizedIntent === "inventory_on_hand_as_of_date" ||
|
||||
return (normalizedIntent === "customer_revenue_and_payments" ||
|
||||
normalizedIntent === "supplier_payouts_profile" ||
|
||||
normalizedIntent === "contract_usage_and_value" ||
|
||||
normalizedIntent === "counterparty_activity_lifecycle" ||
|
||||
normalizedIntent === "inventory_on_hand_as_of_date" ||
|
||||
normalizedIntent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
normalizedIntent === "inventory_purchase_provenance_for_item" ||
|
||||
normalizedIntent === "inventory_purchase_documents_for_item" ||
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ function valueFlowDirectionLabelRu(pilot) {
|
|||
? "исходящих платежей/списаний"
|
||||
: "входящих денежных поступлений";
|
||||
}
|
||||
function valueFlowZeroResultConfirmedLine(pilot) {
|
||||
function valueFlowZeroResultInsufficiencyLine(pilot) {
|
||||
if (!hasExecutedZeroValueFlowRows(pilot)) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -224,7 +224,22 @@ function valueFlowZeroResultConfirmedLine(pilot) {
|
|||
const period = explicitDateScope(pilot);
|
||||
const organizationPart = organization ? ` по организации ${organization}` : "";
|
||||
const periodPart = period ? ` за период ${period}` : " в проверенном окне";
|
||||
return `В проверенном срезе 1С по контрагенту ${counterparty}${organizationPart}${periodPart}: 0 руб.; ${valueFlowDirectionLabelRu(pilot)} не найдено.`;
|
||||
const direction = valueFlowDirectionLabelRu(pilot);
|
||||
return `Точную сумму ${direction} по контрагенту ${counterparty}${organizationPart}${periodPart} не подтверждаю: в выполненной проверке 1С строки ${direction} не найдены; это не доказывает отсутствие операций вне доступного банковского контура.`;
|
||||
}
|
||||
function valueFlowZeroResultCheckedLine(pilot) {
|
||||
if (!hasExecutedZeroValueFlowRows(pilot)) {
|
||||
return null;
|
||||
}
|
||||
const counterparty = firstEntityCandidate(pilot);
|
||||
if (!counterparty) {
|
||||
return null;
|
||||
}
|
||||
const organization = explicitOrganizationScope(pilot);
|
||||
const period = explicitDateScope(pilot);
|
||||
const organizationPart = organization ? ` по организации ${organization}` : "";
|
||||
const periodPart = period ? ` за период ${period}` : " в проверенном окне";
|
||||
return `В проверенном срезе 1С по контрагенту ${counterparty}${organizationPart}${periodPart} строки ${valueFlowDirectionLabelRu(pilot)} не найдено.`;
|
||||
}
|
||||
function valueFlowZeroResultUnknownLine(pilot) {
|
||||
if (!hasExecutedZeroValueFlowRows(pilot)) {
|
||||
|
|
@ -239,11 +254,11 @@ function valueFlowZeroResultUnknownLine(pilot) {
|
|||
return `Это не доказывает отсутствие операций с контрагентом ${counterparty}${periodPart} или вне доступного банковского контура.`;
|
||||
}
|
||||
function valueFlowZeroResultHeadline(pilot) {
|
||||
const confirmedLine = valueFlowZeroResultConfirmedLine(pilot);
|
||||
if (!confirmedLine) {
|
||||
const insufficiencyLine = valueFlowZeroResultInsufficiencyLine(pilot);
|
||||
if (!insufficiencyLine) {
|
||||
return null;
|
||||
}
|
||||
return confirmedLine;
|
||||
return insufficiencyLine;
|
||||
}
|
||||
function hasAllTimeScope(pilot) {
|
||||
return (dryRunHasAxis(pilot, "all_time_scope") ||
|
||||
|
|
@ -1846,20 +1861,20 @@ function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
|
|||
? [derivedValueLine]
|
||||
: derivedValueLine
|
||||
? [derivedValueLine, ...monthlyConfirmedLines]
|
||||
: valueFlowZeroResultConfirmedLine(pilot)
|
||||
? [valueFlowZeroResultConfirmedLine(pilot)]
|
||||
: derivedEntityResolutionLine
|
||||
? [...pilot.evidence.confirmed_facts, derivedEntityResolutionLine]
|
||||
: derivedMetadataLine
|
||||
? [derivedMetadataLine]
|
||||
: pilot.evidence.confirmed_facts;
|
||||
: derivedEntityResolutionLine
|
||||
? [...pilot.evidence.confirmed_facts, derivedEntityResolutionLine]
|
||||
: derivedMetadataLine
|
||||
? [derivedMetadataLine]
|
||||
: pilot.evidence.confirmed_facts;
|
||||
const unknownLines = pilot.derived_business_overview
|
||||
? businessOverviewUnknownLines(pilot)
|
||||
: pilot.derived_metadata_surface
|
||||
? pilot.derived_metadata_surface.available_fields.length > 0
|
||||
? userFacingUnknowns(pilot.evidence.unknown_facts)
|
||||
: ["Детальный список полей этих объектов этим шагом не получен."]
|
||||
: appendValueFlowZeroResultUnknown(rankedValueFlowUnknownLines(pilot), pilot);
|
||||
: appendValueFlowZeroResultUnknown(valueFlowZeroResultCheckedLine(pilot)
|
||||
? [valueFlowZeroResultCheckedLine(pilot), ...rankedValueFlowUnknownLines(pilot)]
|
||||
: rankedValueFlowUnknownLines(pilot), pilot);
|
||||
return {
|
||||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_ANSWER_DRAFT_SCHEMA_VERSION,
|
||||
policy_owner: "assistantMcpDiscoveryAnswerAdapter",
|
||||
|
|
|
|||
|
|
@ -2209,9 +2209,28 @@ function deriveBusinessOverviewContractUsageProfile(result, periodScope) {
|
|||
};
|
||||
}
|
||||
function deriveValueFlow(result, counterparty, periodScope, direction, aggregationAxis) {
|
||||
if (!result || result.error || result.matched_rows <= 0) {
|
||||
if (!result || result.error) {
|
||||
return null;
|
||||
}
|
||||
if (result.matched_rows <= 0) {
|
||||
return {
|
||||
value_flow_direction: direction,
|
||||
counterparty,
|
||||
period_scope: periodScope,
|
||||
aggregation_axis: aggregationAxis,
|
||||
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: result.coverage_limited_by_probe_limit,
|
||||
coverage_recovered_by_period_chunking: result.coverage_recovered_by_period_chunking,
|
||||
period_chunking_granularity: result.period_chunking_granularity,
|
||||
monthly_breakdown: [],
|
||||
inference_basis: "sum_of_confirmed_1c_value_flow_rows"
|
||||
};
|
||||
}
|
||||
let totalAmount = 0;
|
||||
let rowsWithAmount = 0;
|
||||
for (const row of result.rows) {
|
||||
|
|
@ -3926,16 +3945,30 @@ function buildMovementConfirmedFacts(result, counterparty, periodScope) {
|
|||
return [`В 1С найдены строки движений${checkedCounterpartySuffixRu(counterparty)}${checkedPeriodSuffixRu(periodScope)}.`];
|
||||
}
|
||||
function buildValueFlowConfirmedFacts(result, counterparty, direction) {
|
||||
if (result.error || result.matched_rows <= 0) {
|
||||
if (result.error) {
|
||||
return [];
|
||||
}
|
||||
if (direction === "outgoing_supplier_payout") {
|
||||
if (result.matched_rows <= 0) {
|
||||
return [
|
||||
counterparty
|
||||
? `1C supplier-payout rows were checked for counterparty ${counterparty}: not_found`
|
||||
: "1C supplier-payout rows were checked for the requested counterparty scope: not_found"
|
||||
];
|
||||
}
|
||||
return [
|
||||
counterparty
|
||||
? `1C supplier-payout rows were found for counterparty ${counterparty}`
|
||||
: "1C supplier-payout rows were found for the requested counterparty scope"
|
||||
];
|
||||
}
|
||||
if (result.matched_rows <= 0) {
|
||||
return [
|
||||
counterparty
|
||||
? `1C value-flow rows were checked for counterparty ${counterparty}: not_found`
|
||||
: "1C value-flow rows were checked for the requested counterparty scope: not_found"
|
||||
];
|
||||
}
|
||||
return [
|
||||
counterparty
|
||||
? `1C value-flow rows were found for counterparty ${counterparty}`
|
||||
|
|
@ -4115,6 +4148,12 @@ function buildValueFlowInferredFacts(derived) {
|
|||
return [];
|
||||
}
|
||||
const facts = [];
|
||||
if (derived.rows_matched <= 0) {
|
||||
facts.push(derived.value_flow_direction === "outgoing_supplier_payout"
|
||||
? "Counterparty supplier-payout not_found result was treated as zero only inside the scoped checked 1C result set"
|
||||
: "Counterparty incoming value-flow not_found result was treated as zero only inside the scoped checked 1C result set");
|
||||
return facts;
|
||||
}
|
||||
if (derived.value_flow_direction === "outgoing_supplier_payout") {
|
||||
facts.push("Counterparty supplier-payout total was calculated from confirmed 1C outgoing payment rows");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -261,13 +261,14 @@ function resolveAssistantMcpDiscoveryEvidence(input) {
|
|||
const rowsMatched = probeRowsMatched(probeResults);
|
||||
const rowsReceived = probeRowsReceived(probeResults);
|
||||
const bypassDetected = hasProbeBypass(input.plan, probeResults);
|
||||
const confirmedNoMatchFact = confirmedFacts.some((fact) => /\bnot_found\b/iu.test(String(fact ?? "")));
|
||||
if (bypassDetected) {
|
||||
pushReason(reasonCodes, "probe_result_used_primitive_outside_runtime_plan");
|
||||
}
|
||||
if (input.plan.plan_status !== "allowed") {
|
||||
pushReason(reasonCodes, "plan_not_allowed_by_runtime");
|
||||
}
|
||||
if (confirmedFacts.length > 0 && rowsMatched <= 0) {
|
||||
if (confirmedFacts.length > 0 && rowsMatched <= 0 && !confirmedNoMatchFact) {
|
||||
pushReason(reasonCodes, "confirmed_facts_without_matched_probe_rows");
|
||||
}
|
||||
if (!sourceRowsSummary && rowsReceived > 0) {
|
||||
|
|
@ -287,6 +288,12 @@ function resolveAssistantMcpDiscoveryEvidence(input) {
|
|||
answerPermission = "confirmed_answer";
|
||||
pushReason(reasonCodes, "confirmed_facts_with_allowed_mcp_evidence");
|
||||
}
|
||||
else if (confirmedFacts.length > 0 && confirmedNoMatchFact && rowsMatched <= 0 && sourceRowsSummary) {
|
||||
evidenceStatus = "confirmed";
|
||||
coverageStatus = "full";
|
||||
answerPermission = "confirmed_answer";
|
||||
pushReason(reasonCodes, "confirmed_no_match_fact_with_allowed_mcp_evidence");
|
||||
}
|
||||
else if (inferredFacts.length > 0 && rowsReceived > 0) {
|
||||
evidenceStatus = "inferred_only";
|
||||
coverageStatus = "partial";
|
||||
|
|
|
|||
|
|
@ -211,9 +211,15 @@ function localizeLine(value) {
|
|||
if (/^1C value-flow rows were found for the requested counterparty scope$/i.test(value)) {
|
||||
return "В 1С найдены строки входящих денежных поступлений в запрошенном срезе.";
|
||||
}
|
||||
if (/^1C value-flow rows were checked for the requested counterparty scope: not_found$/i.test(value)) {
|
||||
return "В 1С проверены входящие денежные строки в запрошенном срезе: строки не найдены.";
|
||||
}
|
||||
if (/^1C supplier-payout rows were found for the requested counterparty scope$/i.test(value)) {
|
||||
return "В 1С найдены строки исходящих платежей и списаний в запрошенном срезе.";
|
||||
}
|
||||
if (/^1C supplier-payout rows were checked for the requested counterparty scope: not_found$/i.test(value)) {
|
||||
return "В 1С проверены исходящие платежи/списания в запрошенном срезе: строки не найдены.";
|
||||
}
|
||||
const openScopeBidirectionalMatch = value.match(/^1C bidirectional value-flow rows were checked for the requested counterparty scope: incoming=(found|not_found), outgoing=(found|not_found)$/i);
|
||||
if (openScopeBidirectionalMatch) {
|
||||
const incoming = openScopeBidirectionalMatch[1] === "found"
|
||||
|
|
@ -238,6 +244,10 @@ function localizeLine(value) {
|
|||
if (valueFlowMatch) {
|
||||
return `В 1С найдены строки входящих денежных поступлений по контрагенту ${valueFlowMatch[1]}.`;
|
||||
}
|
||||
const valueFlowNotFoundMatch = value.match(/^1C value-flow rows were checked for counterparty\s+(.+): not_found$/i);
|
||||
if (valueFlowNotFoundMatch) {
|
||||
return `В 1С проверены входящие денежные строки по контрагенту ${valueFlowNotFoundMatch[1]}: строки не найдены.`;
|
||||
}
|
||||
if (/^1C value-flow rows were found for the requested counterparty scope$/i.test(value)) {
|
||||
return "В 1С найдены строки входящих денежных поступлений по запрошенному контрагентскому контуру.";
|
||||
}
|
||||
|
|
@ -259,6 +269,10 @@ function localizeLine(value) {
|
|||
if (supplierPayoutMatch) {
|
||||
return `В 1С найдены строки исходящих платежей/списаний по контрагенту ${supplierPayoutMatch[1]}.`;
|
||||
}
|
||||
const supplierPayoutNotFoundMatch = value.match(/^1C supplier-payout rows were checked for counterparty\s+(.+): not_found$/i);
|
||||
if (supplierPayoutNotFoundMatch) {
|
||||
return `В 1С проверены исходящие платежи/списания по контрагенту ${supplierPayoutNotFoundMatch[1]}: строки не найдены.`;
|
||||
}
|
||||
if (/^1C supplier-payout rows were found for the requested counterparty scope$/i.test(value)) {
|
||||
return "В 1С найдены строки исходящих платежей/списаний по запрошенному контрагентскому контуру.";
|
||||
}
|
||||
|
|
@ -292,6 +306,12 @@ function localizeLine(value) {
|
|||
if (/^Counterparty supplier-payout total was calculated from confirmed 1C outgoing payment rows$/i.test(value)) {
|
||||
return "Сумма исходящих платежей рассчитана только по подтвержденным строкам списаний в 1С.";
|
||||
}
|
||||
if (/^Counterparty supplier-payout not_found result was treated as zero only inside the scoped checked 1C result set$/i.test(value)) {
|
||||
return "Нулевую сумму исходящих платежей можно трактовать только внутри этого проверенного среза 1С, где строки списаний не найдены.";
|
||||
}
|
||||
if (/^Counterparty incoming value-flow not_found result was treated as zero only inside the scoped checked 1C result set$/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С.";
|
||||
}
|
||||
|
|
@ -1159,7 +1179,7 @@ function buildCompactBusinessOverviewReply(entryPoint, draft) {
|
|||
outgoingAmount ? `исходящие ${outgoingAmount}` : null,
|
||||
netAmount ? `${netDirection} ${sentenceAmount(netAmount) ?? netAmount}` : null
|
||||
].filter((value) => Boolean(value));
|
||||
lines.push(`По подтвержденным строкам 1С ${subject} за ${periodWithoutPrefix}: ${executiveFacts.length > 0 ? executiveFacts.join(", ") : "денежные метрики не подтверждены"}; это ограниченный проверенный срез, не аудиторское заключение и не подтвержденная чистая прибыль.`);
|
||||
lines.push(`По подтвержденным строкам 1С ${subject} за ${periodWithoutPrefix}: ${executiveFacts.length > 0 ? executiveFacts.join(", ") : "денежные метрики не подтверждены"}; это ограниченный проверенный срез, а не финансовый аудит и не подтверждение чистой прибыли.`);
|
||||
lines.push("Интерпретация: по этому срезу видны крупные контрактные денежные потоки и заметная зависимость от нескольких крупных контрагентов, а не равномерный поток мелких продаж.");
|
||||
lines.push("Что видно в ограниченном денежном срезе:");
|
||||
if (incomingAmount) {
|
||||
|
|
@ -1194,7 +1214,7 @@ function buildCompactBusinessOverviewReply(entryPoint, draft) {
|
|||
lines.push(`- ${localizeLine(debtLine)}`);
|
||||
}
|
||||
if (!limitLine) {
|
||||
lines.push("Ограничение: это оценка по денежным потокам и найденным срезам 1С, не аудиторское заключение и не подтвержденная чистая прибыль.");
|
||||
lines.push("Ограничение: это оценка по денежным потокам и найденным срезам 1С, а не финансовый аудит и не подтверждение чистой прибыли.");
|
||||
}
|
||||
const missingOverviewFamilies = [];
|
||||
if (!taxLine) {
|
||||
|
|
|
|||
|
|
@ -615,6 +615,53 @@ function hasExactBankOperationsAddressReply(input, entryPoint) {
|
|||
routeMode === "exact" ||
|
||||
hasFullConfirmedTruth(input));
|
||||
}
|
||||
function extractExplicitDateScopeYear(value) {
|
||||
const source = toNonEmptyString(value);
|
||||
if (!source) {
|
||||
return null;
|
||||
}
|
||||
const match = source.match(/\b((?:19|20)\d{2})\b/u);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
function extractDocumentReplyDateYears(value) {
|
||||
const source = String(value ?? "");
|
||||
if (!source) {
|
||||
return [];
|
||||
}
|
||||
const years = new Set();
|
||||
for (const match of source.matchAll(/\b((?:19|20)\d{2})-\d{2}-\d{2}(?:T|\b)/gu)) {
|
||||
years.add(match[1]);
|
||||
}
|
||||
for (const match of source.matchAll(/\b\d{1,2}\.\d{1,2}\.((?:19|20)\d{2})\b/gu)) {
|
||||
years.add(match[1]);
|
||||
}
|
||||
return [...years];
|
||||
}
|
||||
function hasDocumentListTemporalConflictWithDiscovery(input, entryPoint) {
|
||||
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
||||
return false;
|
||||
}
|
||||
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
|
||||
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
|
||||
const isDocumentIntent = detectedIntent === "list_documents_by_counterparty" || detectedIntent === "list_documents_by_contract";
|
||||
const isDocumentRecipe = selectedRecipe === "address_documents_by_counterparty_v1" ||
|
||||
selectedRecipe === "address_documents_by_contract_v1";
|
||||
if (!isDocumentIntent || !isDocumentRecipe) {
|
||||
return false;
|
||||
}
|
||||
const turnMeaning = readDiscoveryTurnMeaning(entryPoint);
|
||||
const expectedYear = extractExplicitDateScopeYear(turnMeaning?.explicit_date_scope);
|
||||
if (!expectedYear) {
|
||||
return false;
|
||||
}
|
||||
const filters = toRecordObject(input.addressRuntimeMeta?.extracted_filters);
|
||||
const periodFrom = toNonEmptyString(filters?.period_from);
|
||||
const periodTo = toNonEmptyString(filters?.period_to);
|
||||
const hasMatchingPeriod = Boolean(periodFrom?.startsWith(`${expectedYear}-`)) && Boolean(periodTo?.startsWith(`${expectedYear}-`));
|
||||
const replyDateYears = extractDocumentReplyDateYears(input.currentReply);
|
||||
const hasOutOfScopeReplyDates = replyDateYears.some((year) => year !== expectedYear);
|
||||
return hasOutOfScopeReplyDates || (!hasMatchingPeriod && replyDateYears.length > 0);
|
||||
}
|
||||
function hasExactDocumentListAddressReply(input, entryPoint) {
|
||||
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
||||
return false;
|
||||
|
|
@ -968,7 +1015,8 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
|
|||
const staleMetadataDiscoveryFallbackAgainstExactAddressReply = hasStaleMetadataDiscoveryFallbackAgainstExactAddressReply(input, entryPoint);
|
||||
const exactValueFlowReplyForBusinessOverviewDirectMoneyNeed = hasExactValueFlowReplyForBusinessOverviewDirectMoneyNeed(input, entryPoint);
|
||||
const exactBankOperationsAddressReply = hasExactBankOperationsAddressReply(input, entryPoint);
|
||||
const exactDocumentListAddressReply = hasExactDocumentListAddressReply(input, entryPoint);
|
||||
const documentListTemporalConflictWithDiscovery = hasDocumentListTemporalConflictWithDiscovery(input, entryPoint);
|
||||
const exactDocumentListAddressReply = hasExactDocumentListAddressReply(input, entryPoint) && !documentListTemporalConflictWithDiscovery;
|
||||
const inventoryMarginRankingAddressReply = hasInventoryMarginRankingAddressReply(input, entryPoint);
|
||||
const exactInventoryPurchaseToSaleChainAddressReply = hasExactInventoryPurchaseToSaleChainAddressReply(input, entryPoint);
|
||||
const openScopeValueFlowDiscoveryPriority = hasOpenScopeValueFlowDiscoveryPriority(input, entryPoint);
|
||||
|
|
@ -987,10 +1035,12 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
|
|||
!groundedMovementCandidatePriority &&
|
||||
!businessOverviewBoundaryCandidatePriority &&
|
||||
!groundedValueFlowRankingCandidatePriority;
|
||||
const alignedFactualAddressReplyProtectsCurrent = alignedFactualAddressReply && !groundedValueFlowRankingCandidatePriority;
|
||||
const matchedFactualAddressContinuationTargetProtectsCurrent = matchedFactualAddressContinuationTarget && !groundedValueFlowRankingCandidatePriority;
|
||||
const fullConfirmedFactualAddressReplyProtectsCurrent = fullConfirmedFactualAddressReply && !groundedValueFlowRankingCandidatePriority;
|
||||
const exactMatchedFactualAddressReplyProtectsCurrent = exactMatchedFactualAddressReply && !groundedValueFlowRankingCandidatePriority;
|
||||
const alignedFactualAddressReplyProtectsCurrent = alignedFactualAddressReply && !groundedValueFlowRankingCandidatePriority && !documentListTemporalConflictWithDiscovery;
|
||||
const matchedFactualAddressContinuationTargetProtectsCurrent = matchedFactualAddressContinuationTarget &&
|
||||
!groundedValueFlowRankingCandidatePriority &&
|
||||
!documentListTemporalConflictWithDiscovery;
|
||||
const fullConfirmedFactualAddressReplyProtectsCurrent = fullConfirmedFactualAddressReply && !groundedValueFlowRankingCandidatePriority && !documentListTemporalConflictWithDiscovery;
|
||||
const exactMatchedFactualAddressReplyProtectsCurrent = exactMatchedFactualAddressReply && !groundedValueFlowRankingCandidatePriority && !documentListTemporalConflictWithDiscovery;
|
||||
const exactBankOperationsProtectsCurrent = exactBankOperationsAddressReply &&
|
||||
!semanticConflictWithDiscoveryTurnMeaning &&
|
||||
!valueFlowActionConflictWithDiscoveryTurnMeaning;
|
||||
|
|
@ -1024,6 +1074,9 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
|
|||
if (evidenceLaneConflictWithDiscoveryTurnMeaning) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_evidence_lane_conflict_allows_candidate_override");
|
||||
}
|
||||
if (documentListTemporalConflictWithDiscovery) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_document_list_temporal_conflict_allows_candidate_override");
|
||||
}
|
||||
if (currentClarificationProtectsCurrentReply) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_current_clarification_required_reply");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -151,6 +151,56 @@ function readNavigationDiscoveryCounterparty(debug: Record<string, unknown>): st
|
|||
return null;
|
||||
}
|
||||
|
||||
function readDiscoveryPilot(debug: Record<string, unknown>): Record<string, unknown> | null {
|
||||
const entry = toObject(debug.assistant_mcp_discovery_entry_point_v1);
|
||||
const bridge = toObject(entry?.bridge);
|
||||
return toObject(bridge?.pilot);
|
||||
}
|
||||
|
||||
function readCurrentDiscoveryCounterparty(debug: Record<string, unknown>): string | null {
|
||||
const pilot = readDiscoveryPilot(debug);
|
||||
const entityResolution = toObject(pilot?.derived_entity_resolution);
|
||||
const bidirectionalValueFlow = toObject(pilot?.derived_bidirectional_value_flow);
|
||||
const valueFlow = toObject(pilot?.derived_value_flow);
|
||||
return (
|
||||
toNonEmptyString(entityResolution?.resolved_entity) ??
|
||||
toNonEmptyString(bidirectionalValueFlow?.counterparty) ??
|
||||
toNonEmptyString(valueFlow?.counterparty) ??
|
||||
readAddressDebugCounterparty(debug, toNonEmptyString) ??
|
||||
readNavigationDiscoveryCounterparty(debug)
|
||||
);
|
||||
}
|
||||
|
||||
function isCounterpartyDiscoveryChain(value: string | null): boolean {
|
||||
return (
|
||||
value === "entity_resolution" ||
|
||||
value === "value_flow" ||
|
||||
value === "value_flow_comparison" ||
|
||||
value === "document_evidence" ||
|
||||
value === "movement_evidence" ||
|
||||
value === "lifecycle"
|
||||
);
|
||||
}
|
||||
|
||||
function intentFromDiscoveryChain(value: string | null): AddressIntent {
|
||||
if (value === "business_overview") {
|
||||
return "business_overview";
|
||||
}
|
||||
if (value === "value_flow" || value === "value_flow_comparison") {
|
||||
return "customer_revenue_and_payments";
|
||||
}
|
||||
if (value === "document_evidence") {
|
||||
return "list_documents_by_counterparty";
|
||||
}
|
||||
if (value === "movement_evidence") {
|
||||
return "bank_operations_by_counterparty";
|
||||
}
|
||||
if (value === "lifecycle") {
|
||||
return "counterparty_activity_lifecycle";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
function toAddressFocusObjectType(value: unknown): AddressFocusObjectType {
|
||||
const normalized = toNonEmptyString(value);
|
||||
if (!normalized) {
|
||||
|
|
@ -437,8 +487,7 @@ function readBusinessOverviewComparisonProofBundles(debug: Record<string, unknow
|
|||
const entryPoint = toObject(debug.assistant_mcp_discovery_entry_point_v1);
|
||||
const turnInput = toObject(entryPoint?.turn_input);
|
||||
const turnMeaning = toObject(turnInput?.turn_meaning_ref);
|
||||
const bridge = toObject(entryPoint?.bridge);
|
||||
const pilot = toObject(bridge?.pilot);
|
||||
const pilot = readDiscoveryPilot(debug);
|
||||
const counterpartyValueFlowBundle =
|
||||
cloneRecord(turnMeaning?.previous_counterparty_value_flow_bundle) ??
|
||||
cloneRecord(pilot?.derived_bidirectional_value_flow);
|
||||
|
|
@ -459,6 +508,19 @@ function readBusinessOverviewComparisonProofBundles(debug: Record<string, unknow
|
|||
};
|
||||
}
|
||||
|
||||
function readCounterpartyValueFlowProofBundles(debug: Record<string, unknown>): AddressComparisonProofBundles | null {
|
||||
const pilot = readDiscoveryPilot(debug);
|
||||
const counterpartyValueFlowBundle = cloneRecord(pilot?.derived_bidirectional_value_flow);
|
||||
const counterparty = toNonEmptyString(counterpartyValueFlowBundle?.counterparty);
|
||||
if (!counterparty || !counterpartyValueFlowBundle) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
counterparty_value_flow_bundle: counterpartyValueFlowBundle,
|
||||
counterparty_document_bundle: null
|
||||
};
|
||||
}
|
||||
|
||||
function buildFocusObjectFromDebug(debug: Record<string, unknown>, resultSetId: string, createdAt: string): AddressFocusObject | null {
|
||||
const extractedFilters = toObject(debug.extracted_filters) ?? {};
|
||||
const selectedDiscoveryChain = toNonEmptyString(debug.mcp_discovery_selected_chain_id);
|
||||
|
|
@ -472,9 +534,8 @@ function buildFocusObjectFromDebug(debug: Record<string, unknown>, resultSetId:
|
|||
return buildFocusObject("organization", organization, resultSetId, createdAt);
|
||||
}
|
||||
}
|
||||
if (selectedDiscoveryChain === "value_flow_comparison" && debug.mcp_discovery_response_applied === true) {
|
||||
const counterparty =
|
||||
readAddressDebugCounterparty(debug, toNonEmptyString) ?? readNavigationDiscoveryCounterparty(debug);
|
||||
if (isCounterpartyDiscoveryChain(selectedDiscoveryChain) && debug.mcp_discovery_response_applied === true) {
|
||||
const counterparty = readCurrentDiscoveryCounterparty(debug);
|
||||
if (counterparty) {
|
||||
return buildFocusObject("counterparty", counterparty, resultSetId, createdAt);
|
||||
}
|
||||
|
|
@ -697,15 +758,16 @@ export function evolveAddressNavigationStateWithAssistantItem(
|
|||
}
|
||||
const debug = item.debug as unknown as Record<string, unknown>;
|
||||
const selectedDiscoveryChain = toNonEmptyString(debug.mcp_discovery_selected_chain_id);
|
||||
const discoveryIntent =
|
||||
selectedDiscoveryChain === "business_overview"
|
||||
? "business_overview"
|
||||
: selectedDiscoveryChain === "value_flow_comparison"
|
||||
? "customer_revenue_and_payments"
|
||||
: "unknown";
|
||||
const detectedIntent = toNonEmptyString(debug.detected_intent);
|
||||
const intent = toAddressIntent(detectedIntent && detectedIntent !== "unknown" ? detectedIntent : discoveryIntent);
|
||||
const discoveryIntent = intentFromDiscoveryChain(selectedDiscoveryChain);
|
||||
const trackableDiscoveryTurn = debug.mcp_discovery_response_applied === true && Boolean(selectedDiscoveryChain);
|
||||
const shouldPreferDiscoveryIntent =
|
||||
trackableDiscoveryTurn &&
|
||||
discoveryIntent !== "unknown" &&
|
||||
selectedDiscoveryChain !== "business_overview";
|
||||
const intent = toAddressIntent(
|
||||
shouldPreferDiscoveryIntent ? discoveryIntent : detectedIntent && detectedIntent !== "unknown" ? detectedIntent : discoveryIntent
|
||||
);
|
||||
if (intent === "unknown" && !trackableDiscoveryTurn) {
|
||||
return state;
|
||||
}
|
||||
|
|
@ -716,8 +778,8 @@ export function evolveAddressNavigationStateWithAssistantItem(
|
|||
const derivedOrganizationScope =
|
||||
resolveDerivedOrganizationScope(debug, filters, item.text) ?? readAddressDebugOrganization(debug, toNonEmptyString);
|
||||
const derivedCounterpartyScope =
|
||||
selectedDiscoveryChain === "value_flow_comparison" && debug.mcp_discovery_response_applied === true
|
||||
? readAddressDebugCounterparty(debug, toNonEmptyString) ?? readNavigationDiscoveryCounterparty(debug)
|
||||
isCounterpartyDiscoveryChain(selectedDiscoveryChain) && debug.mcp_discovery_response_applied === true
|
||||
? readCurrentDiscoveryCounterparty(debug)
|
||||
: null;
|
||||
const filtersWithDerivedScope =
|
||||
derivedOrganizationScope && !toNonEmptyString(filters.organization)
|
||||
|
|
@ -758,8 +820,12 @@ export function evolveAddressNavigationStateWithAssistantItem(
|
|||
primaryEntityFocusObject
|
||||
);
|
||||
const rawComparisonCounterparty =
|
||||
selectedDiscoveryChain === "business_overview" && debug.mcp_discovery_response_applied === true
|
||||
? readNavigationDiscoveryCounterparty(debug)
|
||||
debug.mcp_discovery_response_applied === true
|
||||
? selectedDiscoveryChain === "business_overview"
|
||||
? readNavigationDiscoveryCounterparty(debug)
|
||||
: isCounterpartyDiscoveryChain(selectedDiscoveryChain)
|
||||
? readCurrentDiscoveryCounterparty(debug)
|
||||
: null
|
||||
: null;
|
||||
const comparisonCounterparty =
|
||||
rawComparisonCounterparty && !shouldSuppressBusinessOverviewCounterpartyFocus(debug, rawComparisonCounterparty)
|
||||
|
|
@ -772,6 +838,8 @@ export function evolveAddressNavigationStateWithAssistantItem(
|
|||
const currentComparisonProofBundles =
|
||||
selectedDiscoveryChain === "business_overview" && debug.mcp_discovery_response_applied === true
|
||||
? readBusinessOverviewComparisonProofBundles(debug)
|
||||
: selectedDiscoveryChain === "value_flow_comparison" && debug.mcp_discovery_response_applied === true
|
||||
? readCounterpartyValueFlowProofBundles(debug)
|
||||
: null;
|
||||
const inheritedComparisonScope = state.session_context.comparison_scope;
|
||||
const inheritedComparisonProofBundles =
|
||||
|
|
|
|||
|
|
@ -1629,6 +1629,16 @@ function mergeFollowupFilters(
|
|||
intent === "vat_payable_confirmed_as_of_date";
|
||||
const currentHasPeriod = hasExplicitPeriodWindow(merged);
|
||||
const previousHasPeriod = hasExplicitPeriodWindow(previous);
|
||||
const currentPeriodFromForCarryover = toNonEmptyString(merged.period_from);
|
||||
const currentPeriodToForCarryover = toNonEmptyString(merged.period_to);
|
||||
const todayIsoForPeriodCarryover = new Date().toISOString().slice(0, 10);
|
||||
const currentValueFlowPeriodDefaultsToToday =
|
||||
isValueCounterpartyIntent(intent) &&
|
||||
hasFollowupSignal &&
|
||||
!hasExplicitPeriodInMessage &&
|
||||
!hasExplicitCurrentDateInMessage &&
|
||||
!currentPeriodFromForCarryover &&
|
||||
currentPeriodToForCarryover === todayIsoForPeriodCarryover;
|
||||
const currentCounterpartyExplicit = toNonEmptyString(merged.counterparty);
|
||||
const currentContractExplicit = toNonEmptyString(merged.contract);
|
||||
const currentItemExplicit = toNonEmptyString(merged.item);
|
||||
|
|
@ -1717,7 +1727,7 @@ function mergeFollowupFilters(
|
|||
}
|
||||
|
||||
if (
|
||||
!currentHasPeriod &&
|
||||
(!currentHasPeriod || currentValueFlowPeriodDefaultsToToday) &&
|
||||
previousHasPeriod &&
|
||||
hasFollowupSignal &&
|
||||
!hasExplicitPeriodInMessage &&
|
||||
|
|
|
|||
|
|
@ -1158,6 +1158,10 @@ export function resolveFollowupTargetIntent(
|
|||
export function shouldUseNavigationTemporalCarryover(sourceIntentHint: unknown): boolean {
|
||||
const normalizedIntent = fallbackToNonEmptyString(sourceIntentHint);
|
||||
return (
|
||||
normalizedIntent === "customer_revenue_and_payments" ||
|
||||
normalizedIntent === "supplier_payouts_profile" ||
|
||||
normalizedIntent === "contract_usage_and_value" ||
|
||||
normalizedIntent === "counterparty_activity_lifecycle" ||
|
||||
normalizedIntent === "inventory_on_hand_as_of_date" ||
|
||||
normalizedIntent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
normalizedIntent === "inventory_purchase_provenance_for_item" ||
|
||||
|
|
|
|||
|
|
@ -281,7 +281,7 @@ function valueFlowDirectionLabelRu(pilot: AssistantMcpDiscoveryPilotExecutionCon
|
|||
: "входящих денежных поступлений";
|
||||
}
|
||||
|
||||
function valueFlowZeroResultConfirmedLine(pilot: AssistantMcpDiscoveryPilotExecutionContract): string | null {
|
||||
function valueFlowZeroResultInsufficiencyLine(pilot: AssistantMcpDiscoveryPilotExecutionContract): string | null {
|
||||
if (!hasExecutedZeroValueFlowRows(pilot)) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -293,7 +293,23 @@ function valueFlowZeroResultConfirmedLine(pilot: AssistantMcpDiscoveryPilotExecu
|
|||
const period = explicitDateScope(pilot);
|
||||
const organizationPart = organization ? ` по организации ${organization}` : "";
|
||||
const periodPart = period ? ` за период ${period}` : " в проверенном окне";
|
||||
return `В проверенном срезе 1С по контрагенту ${counterparty}${organizationPart}${periodPart}: 0 руб.; ${valueFlowDirectionLabelRu(
|
||||
const direction = valueFlowDirectionLabelRu(pilot);
|
||||
return `Точную сумму ${direction} по контрагенту ${counterparty}${organizationPart}${periodPart} не подтверждаю: в выполненной проверке 1С строки ${direction} не найдены; это не доказывает отсутствие операций вне доступного банковского контура.`;
|
||||
}
|
||||
|
||||
function valueFlowZeroResultCheckedLine(pilot: AssistantMcpDiscoveryPilotExecutionContract): string | null {
|
||||
if (!hasExecutedZeroValueFlowRows(pilot)) {
|
||||
return null;
|
||||
}
|
||||
const counterparty = firstEntityCandidate(pilot);
|
||||
if (!counterparty) {
|
||||
return null;
|
||||
}
|
||||
const organization = explicitOrganizationScope(pilot);
|
||||
const period = explicitDateScope(pilot);
|
||||
const organizationPart = organization ? ` по организации ${organization}` : "";
|
||||
const periodPart = period ? ` за период ${period}` : " в проверенном окне";
|
||||
return `В проверенном срезе 1С по контрагенту ${counterparty}${organizationPart}${periodPart} строки ${valueFlowDirectionLabelRu(
|
||||
pilot
|
||||
)} не найдено.`;
|
||||
}
|
||||
|
|
@ -312,11 +328,11 @@ function valueFlowZeroResultUnknownLine(pilot: AssistantMcpDiscoveryPilotExecuti
|
|||
}
|
||||
|
||||
function valueFlowZeroResultHeadline(pilot: AssistantMcpDiscoveryPilotExecutionContract): string | null {
|
||||
const confirmedLine = valueFlowZeroResultConfirmedLine(pilot);
|
||||
if (!confirmedLine) {
|
||||
const insufficiencyLine = valueFlowZeroResultInsufficiencyLine(pilot);
|
||||
if (!insufficiencyLine) {
|
||||
return null;
|
||||
}
|
||||
return confirmedLine;
|
||||
return insufficiencyLine;
|
||||
}
|
||||
|
||||
function hasAllTimeScope(pilot: AssistantMcpDiscoveryPilotExecutionContract): boolean {
|
||||
|
|
@ -2126,8 +2142,6 @@ export function buildAssistantMcpDiscoveryAnswerDraft(
|
|||
? [derivedValueLine]
|
||||
: derivedValueLine
|
||||
? [derivedValueLine, ...monthlyConfirmedLines]
|
||||
: valueFlowZeroResultConfirmedLine(pilot)
|
||||
? [valueFlowZeroResultConfirmedLine(pilot)!]
|
||||
: derivedEntityResolutionLine
|
||||
? [...pilot.evidence.confirmed_facts, derivedEntityResolutionLine]
|
||||
: derivedMetadataLine
|
||||
|
|
@ -2139,7 +2153,12 @@ export function buildAssistantMcpDiscoveryAnswerDraft(
|
|||
? pilot.derived_metadata_surface.available_fields.length > 0
|
||||
? userFacingUnknowns(pilot.evidence.unknown_facts)
|
||||
: ["Детальный список полей этих объектов этим шагом не получен."]
|
||||
: appendValueFlowZeroResultUnknown(rankedValueFlowUnknownLines(pilot), pilot);
|
||||
: appendValueFlowZeroResultUnknown(
|
||||
valueFlowZeroResultCheckedLine(pilot)
|
||||
? [valueFlowZeroResultCheckedLine(pilot)!, ...rankedValueFlowUnknownLines(pilot)]
|
||||
: rankedValueFlowUnknownLines(pilot),
|
||||
pilot
|
||||
);
|
||||
|
||||
return {
|
||||
schema_version: ASSISTANT_MCP_DISCOVERY_ANSWER_DRAFT_SCHEMA_VERSION,
|
||||
|
|
|
|||
|
|
@ -3217,9 +3217,28 @@ function deriveValueFlow(
|
|||
direction: AssistantMcpDiscoveryDerivedValueFlow["value_flow_direction"],
|
||||
aggregationAxis: AssistantMcpDiscoveryAggregationAxis | null
|
||||
): AssistantMcpDiscoveryDerivedValueFlow | null {
|
||||
if (!result || result.error || result.matched_rows <= 0) {
|
||||
if (!result || result.error) {
|
||||
return null;
|
||||
}
|
||||
if (result.matched_rows <= 0) {
|
||||
return {
|
||||
value_flow_direction: direction,
|
||||
counterparty,
|
||||
period_scope: periodScope,
|
||||
aggregation_axis: aggregationAxis,
|
||||
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: result.coverage_limited_by_probe_limit,
|
||||
coverage_recovered_by_period_chunking: result.coverage_recovered_by_period_chunking,
|
||||
period_chunking_granularity: result.period_chunking_granularity,
|
||||
monthly_breakdown: [],
|
||||
inference_basis: "sum_of_confirmed_1c_value_flow_rows"
|
||||
};
|
||||
}
|
||||
let totalAmount = 0;
|
||||
let rowsWithAmount = 0;
|
||||
for (const row of result.rows) {
|
||||
|
|
@ -5294,16 +5313,30 @@ function buildValueFlowConfirmedFacts(
|
|||
counterparty: string | null,
|
||||
direction: AssistantMcpDiscoveryDerivedValueFlow["value_flow_direction"]
|
||||
): string[] {
|
||||
if (result.error || result.matched_rows <= 0) {
|
||||
if (result.error) {
|
||||
return [];
|
||||
}
|
||||
if (direction === "outgoing_supplier_payout") {
|
||||
if (result.matched_rows <= 0) {
|
||||
return [
|
||||
counterparty
|
||||
? `1C supplier-payout rows were checked for counterparty ${counterparty}: not_found`
|
||||
: "1C supplier-payout rows were checked for the requested counterparty scope: not_found"
|
||||
];
|
||||
}
|
||||
return [
|
||||
counterparty
|
||||
? `1C supplier-payout rows were found for counterparty ${counterparty}`
|
||||
: "1C supplier-payout rows were found for the requested counterparty scope"
|
||||
];
|
||||
}
|
||||
if (result.matched_rows <= 0) {
|
||||
return [
|
||||
counterparty
|
||||
? `1C value-flow rows were checked for counterparty ${counterparty}: not_found`
|
||||
: "1C value-flow rows were checked for the requested counterparty scope: not_found"
|
||||
];
|
||||
}
|
||||
return [
|
||||
counterparty
|
||||
? `1C value-flow rows were found for counterparty ${counterparty}`
|
||||
|
|
@ -5521,6 +5554,14 @@ function buildValueFlowInferredFacts(derived: AssistantMcpDiscoveryDerivedValueF
|
|||
return [];
|
||||
}
|
||||
const facts: string[] = [];
|
||||
if (derived.rows_matched <= 0) {
|
||||
facts.push(
|
||||
derived.value_flow_direction === "outgoing_supplier_payout"
|
||||
? "Counterparty supplier-payout not_found result was treated as zero only inside the scoped checked 1C result set"
|
||||
: "Counterparty incoming value-flow not_found result was treated as zero only inside the scoped checked 1C result set"
|
||||
);
|
||||
return facts;
|
||||
}
|
||||
if (derived.value_flow_direction === "outgoing_supplier_payout") {
|
||||
facts.push("Counterparty supplier-payout total was calculated from confirmed 1C outgoing payment rows");
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -386,6 +386,7 @@ export function resolveAssistantMcpDiscoveryEvidence(
|
|||
const rowsMatched = probeRowsMatched(probeResults);
|
||||
const rowsReceived = probeRowsReceived(probeResults);
|
||||
const bypassDetected = hasProbeBypass(input.plan, probeResults);
|
||||
const confirmedNoMatchFact = confirmedFacts.some((fact) => /\bnot_found\b/iu.test(String(fact ?? "")));
|
||||
|
||||
if (bypassDetected) {
|
||||
pushReason(reasonCodes, "probe_result_used_primitive_outside_runtime_plan");
|
||||
|
|
@ -393,7 +394,7 @@ export function resolveAssistantMcpDiscoveryEvidence(
|
|||
if (input.plan.plan_status !== "allowed") {
|
||||
pushReason(reasonCodes, "plan_not_allowed_by_runtime");
|
||||
}
|
||||
if (confirmedFacts.length > 0 && rowsMatched <= 0) {
|
||||
if (confirmedFacts.length > 0 && rowsMatched <= 0 && !confirmedNoMatchFact) {
|
||||
pushReason(reasonCodes, "confirmed_facts_without_matched_probe_rows");
|
||||
}
|
||||
if (!sourceRowsSummary && rowsReceived > 0) {
|
||||
|
|
@ -413,6 +414,11 @@ export function resolveAssistantMcpDiscoveryEvidence(
|
|||
coverageStatus = "full";
|
||||
answerPermission = "confirmed_answer";
|
||||
pushReason(reasonCodes, "confirmed_facts_with_allowed_mcp_evidence");
|
||||
} else if (confirmedFacts.length > 0 && confirmedNoMatchFact && rowsMatched <= 0 && sourceRowsSummary) {
|
||||
evidenceStatus = "confirmed";
|
||||
coverageStatus = "full";
|
||||
answerPermission = "confirmed_answer";
|
||||
pushReason(reasonCodes, "confirmed_no_match_fact_with_allowed_mcp_evidence");
|
||||
} else if (inferredFacts.length > 0 && rowsReceived > 0) {
|
||||
evidenceStatus = "inferred_only";
|
||||
coverageStatus = "partial";
|
||||
|
|
|
|||
|
|
@ -284,9 +284,15 @@ function localizeLine(value: string): string {
|
|||
if (/^1C value-flow rows were found for the requested counterparty scope$/i.test(value)) {
|
||||
return "В 1С найдены строки входящих денежных поступлений в запрошенном срезе.";
|
||||
}
|
||||
if (/^1C value-flow rows were checked for the requested counterparty scope: not_found$/i.test(value)) {
|
||||
return "В 1С проверены входящие денежные строки в запрошенном срезе: строки не найдены.";
|
||||
}
|
||||
if (/^1C supplier-payout rows were found for the requested counterparty scope$/i.test(value)) {
|
||||
return "В 1С найдены строки исходящих платежей и списаний в запрошенном срезе.";
|
||||
}
|
||||
if (/^1C supplier-payout rows were checked for the requested counterparty scope: not_found$/i.test(value)) {
|
||||
return "В 1С проверены исходящие платежи/списания в запрошенном срезе: строки не найдены.";
|
||||
}
|
||||
const openScopeBidirectionalMatch = value.match(
|
||||
/^1C bidirectional value-flow rows were checked for the requested counterparty scope: incoming=(found|not_found), outgoing=(found|not_found)$/i
|
||||
);
|
||||
|
|
@ -319,6 +325,10 @@ function localizeLine(value: string): string {
|
|||
if (valueFlowMatch) {
|
||||
return `В 1С найдены строки входящих денежных поступлений по контрагенту ${valueFlowMatch[1]}.`;
|
||||
}
|
||||
const valueFlowNotFoundMatch = value.match(/^1C value-flow rows were checked for counterparty\s+(.+): not_found$/i);
|
||||
if (valueFlowNotFoundMatch) {
|
||||
return `В 1С проверены входящие денежные строки по контрагенту ${valueFlowNotFoundMatch[1]}: строки не найдены.`;
|
||||
}
|
||||
if (/^1C value-flow rows were found for the requested counterparty scope$/i.test(value)) {
|
||||
return "В 1С найдены строки входящих денежных поступлений по запрошенному контрагентскому контуру.";
|
||||
}
|
||||
|
|
@ -340,6 +350,10 @@ function localizeLine(value: string): string {
|
|||
if (supplierPayoutMatch) {
|
||||
return `В 1С найдены строки исходящих платежей/списаний по контрагенту ${supplierPayoutMatch[1]}.`;
|
||||
}
|
||||
const supplierPayoutNotFoundMatch = value.match(/^1C supplier-payout rows were checked for counterparty\s+(.+): not_found$/i);
|
||||
if (supplierPayoutNotFoundMatch) {
|
||||
return `В 1С проверены исходящие платежи/списания по контрагенту ${supplierPayoutNotFoundMatch[1]}: строки не найдены.`;
|
||||
}
|
||||
if (/^1C supplier-payout rows were found for the requested counterparty scope$/i.test(value)) {
|
||||
return "В 1С найдены строки исходящих платежей/списаний по запрошенному контрагентскому контуру.";
|
||||
}
|
||||
|
|
@ -377,6 +391,12 @@ 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 supplier-payout not_found result was treated as zero only inside the scoped checked 1C result set$/i.test(value)) {
|
||||
return "Нулевую сумму исходящих платежей можно трактовать только внутри этого проверенного среза 1С, где строки списаний не найдены.";
|
||||
}
|
||||
if (/^Counterparty incoming value-flow not_found result was treated as zero only inside the scoped checked 1C result set$/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С.";
|
||||
}
|
||||
|
|
@ -1429,7 +1449,7 @@ function buildCompactBusinessOverviewReply(
|
|||
netAmount ? `${netDirection} ${sentenceAmount(netAmount) ?? netAmount}` : null
|
||||
].filter((value): value is string => Boolean(value));
|
||||
lines.push(
|
||||
`По подтвержденным строкам 1С ${subject} за ${periodWithoutPrefix}: ${executiveFacts.length > 0 ? executiveFacts.join(", ") : "денежные метрики не подтверждены"}; это ограниченный проверенный срез, не аудиторское заключение и не подтвержденная чистая прибыль.`
|
||||
`По подтвержденным строкам 1С ${subject} за ${periodWithoutPrefix}: ${executiveFacts.length > 0 ? executiveFacts.join(", ") : "денежные метрики не подтверждены"}; это ограниченный проверенный срез, а не финансовый аудит и не подтверждение чистой прибыли.`
|
||||
);
|
||||
lines.push(
|
||||
"Интерпретация: по этому срезу видны крупные контрактные денежные потоки и заметная зависимость от нескольких крупных контрагентов, а не равномерный поток мелких продаж."
|
||||
|
|
@ -1472,7 +1492,7 @@ function buildCompactBusinessOverviewReply(
|
|||
}
|
||||
if (!limitLine) {
|
||||
lines.push(
|
||||
"Ограничение: это оценка по денежным потокам и найденным срезам 1С, не аудиторское заключение и не подтвержденная чистая прибыль."
|
||||
"Ограничение: это оценка по денежным потокам и найденным срезам 1С, а не финансовый аудит и не подтверждение чистой прибыли."
|
||||
);
|
||||
}
|
||||
const missingOverviewFamilies: string[] = [];
|
||||
|
|
|
|||
|
|
@ -855,6 +855,65 @@ function hasExactBankOperationsAddressReply(
|
|||
);
|
||||
}
|
||||
|
||||
function extractExplicitDateScopeYear(value: unknown): string | null {
|
||||
const source = toNonEmptyString(value);
|
||||
if (!source) {
|
||||
return null;
|
||||
}
|
||||
const match = source.match(/\b((?:19|20)\d{2})\b/u);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
function extractDocumentReplyDateYears(value: unknown): string[] {
|
||||
const source = String(value ?? "");
|
||||
if (!source) {
|
||||
return [];
|
||||
}
|
||||
const years = new Set<string>();
|
||||
for (const match of source.matchAll(/\b((?:19|20)\d{2})-\d{2}-\d{2}(?:T|\b)/gu)) {
|
||||
years.add(match[1]);
|
||||
}
|
||||
for (const match of source.matchAll(/\b\d{1,2}\.\d{1,2}\.((?:19|20)\d{2})\b/gu)) {
|
||||
years.add(match[1]);
|
||||
}
|
||||
return [...years];
|
||||
}
|
||||
|
||||
function hasDocumentListTemporalConflictWithDiscovery(
|
||||
input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
|
||||
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
|
||||
): boolean {
|
||||
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
||||
return false;
|
||||
}
|
||||
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
|
||||
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
|
||||
const isDocumentIntent =
|
||||
detectedIntent === "list_documents_by_counterparty" || detectedIntent === "list_documents_by_contract";
|
||||
const isDocumentRecipe =
|
||||
selectedRecipe === "address_documents_by_counterparty_v1" ||
|
||||
selectedRecipe === "address_documents_by_contract_v1";
|
||||
if (!isDocumentIntent || !isDocumentRecipe) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const turnMeaning = readDiscoveryTurnMeaning(entryPoint);
|
||||
const expectedYear = extractExplicitDateScopeYear(turnMeaning?.explicit_date_scope);
|
||||
if (!expectedYear) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const filters = toRecordObject(input.addressRuntimeMeta?.extracted_filters);
|
||||
const periodFrom = toNonEmptyString(filters?.period_from);
|
||||
const periodTo = toNonEmptyString(filters?.period_to);
|
||||
const hasMatchingPeriod =
|
||||
Boolean(periodFrom?.startsWith(`${expectedYear}-`)) && Boolean(periodTo?.startsWith(`${expectedYear}-`));
|
||||
const replyDateYears = extractDocumentReplyDateYears(input.currentReply);
|
||||
const hasOutOfScopeReplyDates = replyDateYears.some((year) => year !== expectedYear);
|
||||
|
||||
return hasOutOfScopeReplyDates || (!hasMatchingPeriod && replyDateYears.length > 0);
|
||||
}
|
||||
|
||||
function hasExactDocumentListAddressReply(
|
||||
input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
|
||||
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
|
||||
|
|
@ -1299,7 +1358,9 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
|
|||
entryPoint
|
||||
);
|
||||
const exactBankOperationsAddressReply = hasExactBankOperationsAddressReply(input, entryPoint);
|
||||
const exactDocumentListAddressReply = hasExactDocumentListAddressReply(input, entryPoint);
|
||||
const documentListTemporalConflictWithDiscovery = hasDocumentListTemporalConflictWithDiscovery(input, entryPoint);
|
||||
const exactDocumentListAddressReply =
|
||||
hasExactDocumentListAddressReply(input, entryPoint) && !documentListTemporalConflictWithDiscovery;
|
||||
const inventoryMarginRankingAddressReply = hasInventoryMarginRankingAddressReply(input, entryPoint);
|
||||
const exactInventoryPurchaseToSaleChainAddressReply = hasExactInventoryPurchaseToSaleChainAddressReply(
|
||||
input,
|
||||
|
|
@ -1340,13 +1401,15 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
|
|||
!businessOverviewBoundaryCandidatePriority &&
|
||||
!groundedValueFlowRankingCandidatePriority;
|
||||
const alignedFactualAddressReplyProtectsCurrent =
|
||||
alignedFactualAddressReply && !groundedValueFlowRankingCandidatePriority;
|
||||
alignedFactualAddressReply && !groundedValueFlowRankingCandidatePriority && !documentListTemporalConflictWithDiscovery;
|
||||
const matchedFactualAddressContinuationTargetProtectsCurrent =
|
||||
matchedFactualAddressContinuationTarget && !groundedValueFlowRankingCandidatePriority;
|
||||
matchedFactualAddressContinuationTarget &&
|
||||
!groundedValueFlowRankingCandidatePriority &&
|
||||
!documentListTemporalConflictWithDiscovery;
|
||||
const fullConfirmedFactualAddressReplyProtectsCurrent =
|
||||
fullConfirmedFactualAddressReply && !groundedValueFlowRankingCandidatePriority;
|
||||
fullConfirmedFactualAddressReply && !groundedValueFlowRankingCandidatePriority && !documentListTemporalConflictWithDiscovery;
|
||||
const exactMatchedFactualAddressReplyProtectsCurrent =
|
||||
exactMatchedFactualAddressReply && !groundedValueFlowRankingCandidatePriority;
|
||||
exactMatchedFactualAddressReply && !groundedValueFlowRankingCandidatePriority && !documentListTemporalConflictWithDiscovery;
|
||||
const exactBankOperationsProtectsCurrent =
|
||||
exactBankOperationsAddressReply &&
|
||||
!semanticConflictWithDiscoveryTurnMeaning &&
|
||||
|
|
@ -1383,6 +1446,9 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
|
|||
if (evidenceLaneConflictWithDiscoveryTurnMeaning) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_evidence_lane_conflict_allows_candidate_override");
|
||||
}
|
||||
if (documentListTemporalConflictWithDiscovery) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_document_list_temporal_conflict_allows_candidate_override");
|
||||
}
|
||||
if (currentClarificationProtectsCurrentReply) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_current_clarification_required_reply");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -164,6 +164,28 @@ describe("address follow-up temporal regressions", () => {
|
|||
expect(result?.baseReasons).not.toContain("counterparty_carryover_suppressed_for_short_debt_mirror");
|
||||
});
|
||||
|
||||
it("keeps explicit year period for selected counterparty payout follow-up", () => {
|
||||
const result = runAddressDecomposeStage("а теперь сколько заплатили?", {
|
||||
previous_intent: "customer_revenue_and_payments" as const,
|
||||
target_intent: "customer_revenue_and_payments" as const,
|
||||
previous_filters: {
|
||||
counterparty: "Группа СВК",
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
},
|
||||
previous_anchor_type: "counterparty" as const,
|
||||
previous_anchor_value: "Группа СВК",
|
||||
resolved_counterparty_from_display: true
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.intent.intent).toBe("customer_revenue_and_payments");
|
||||
expect(result?.filters.extracted_filters.counterparty).toBe("Группа СВК");
|
||||
expect(result?.filters.extracted_filters.period_from).toBe("2020-01-01");
|
||||
expect(result?.filters.extracted_filters.period_to).toBe("2020-12-31");
|
||||
expect(result?.baseReasons).toContain("period_from_followup_context");
|
||||
});
|
||||
|
||||
it("keeps same-date inventory pivot anchored to the previous VAT date", () => {
|
||||
const result = runAddressDecomposeStage("какие остатки по складу на эту же дату", {
|
||||
previous_intent: "vat_payable_confirmed_as_of_date",
|
||||
|
|
@ -454,6 +476,29 @@ describe("address follow-up temporal regressions", () => {
|
|||
expect(movements?.baseReasons).toContain("counterparty_from_followup_context");
|
||||
});
|
||||
|
||||
it("keeps value-flow period for counterparty document evidence follow-up", () => {
|
||||
const followupContext = {
|
||||
previous_intent: "customer_revenue_and_payments" as const,
|
||||
target_intent: "list_documents_by_counterparty" as const,
|
||||
previous_filters: {
|
||||
counterparty: "Группа СВК",
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
},
|
||||
previous_anchor_type: "counterparty" as const,
|
||||
previous_anchor_value: "Группа СВК",
|
||||
resolved_counterparty_from_display: true
|
||||
};
|
||||
|
||||
const documents = runAddressDecomposeStage("а по документам?", followupContext);
|
||||
|
||||
expect(documents?.intent.intent).toBe("list_documents_by_counterparty");
|
||||
expect(documents?.filters.extracted_filters.counterparty).toBe("Группа СВК");
|
||||
expect(documents?.filters.extracted_filters.period_from).toBe("2020-01-01");
|
||||
expect(documents?.filters.extracted_filters.period_to).toBe("2020-12-31");
|
||||
expect(documents?.baseReasons).toContain("period_from_followup_context");
|
||||
});
|
||||
|
||||
it("replaces pronoun chain anchors from counterparty follow-up context", () => {
|
||||
const followupContext = {
|
||||
previous_intent: "customer_revenue_and_payments" as const,
|
||||
|
|
|
|||
|
|
@ -220,6 +220,262 @@ describe("address navigation state", () => {
|
|||
expect(evolved.session_context.date_scope.period_to).toBe("2020-12-31");
|
||||
});
|
||||
|
||||
it("captures resolved entity-resolution focus as fresh current discovery state", () => {
|
||||
const base = createEmptyAddressNavigationState("asst-entity", "2026-04-12T10:00:00.000Z");
|
||||
const assistantItem = {
|
||||
message_id: "msg-entity",
|
||||
session_id: "asst-entity",
|
||||
role: "assistant",
|
||||
text: "В проверенном каталожном срезе 1С найден наиболее вероятный контрагент: Группа СВК.",
|
||||
created_at: "2026-04-12T10:12:30.000Z",
|
||||
debug: {
|
||||
detected_mode: "chat",
|
||||
detected_intent: "unknown",
|
||||
extracted_filters: {},
|
||||
anchor_type: "unknown",
|
||||
mcp_discovery_response_applied: true,
|
||||
mcp_discovery_selected_chain_id: "entity_resolution",
|
||||
assistant_mcp_discovery_entry_point_v1: {
|
||||
turn_input: {
|
||||
turn_meaning_ref: {
|
||||
asked_domain_family: "entity_resolution",
|
||||
asked_action_family: "search_business_entity",
|
||||
explicit_entity_candidates: ["СВК"],
|
||||
stale_replay_forbidden: true
|
||||
}
|
||||
},
|
||||
bridge: {
|
||||
pilot: {
|
||||
pilot_scope: "entity_resolution_search_v1",
|
||||
derived_entity_resolution: {
|
||||
requested_entity: "СВК",
|
||||
resolution_status: "resolved",
|
||||
resolved_entity: "Группа СВК"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
dialog_continuation_contract_v2: {
|
||||
decision: "new_topic"
|
||||
}
|
||||
}
|
||||
} as any;
|
||||
|
||||
const evolved = evolveAddressNavigationStateWithAssistantItem(base, assistantItem, 1);
|
||||
|
||||
expect(evolved.session_context.active_focus_object?.object_type).toBe("counterparty");
|
||||
expect(evolved.session_context.active_focus_object?.label).toBe("Группа СВК");
|
||||
expect(evolved.result_sets[0]?.filters.counterparty).toBe("Группа СВК");
|
||||
expect(evolved.navigation_history[0]?.target_object_id).toBe("counterparty:группа свк");
|
||||
});
|
||||
|
||||
it("captures single-direction value-flow focus before the net bundle exists", () => {
|
||||
const base = createEmptyAddressNavigationState("asst-vf-single", "2026-04-12T10:00:00.000Z");
|
||||
const assistantItem = {
|
||||
message_id: "msg-vf-single",
|
||||
session_id: "asst-vf-single",
|
||||
role: "assistant",
|
||||
text: "Входящие денежные поступления по контрагенту Группа СВК за период 2020: 12 093 465 руб.",
|
||||
created_at: "2026-04-12T10:13:00.000Z",
|
||||
debug: {
|
||||
detected_mode: "chat",
|
||||
detected_intent: "unknown",
|
||||
extracted_filters: {
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
},
|
||||
anchor_type: "unknown",
|
||||
mcp_discovery_response_applied: true,
|
||||
mcp_discovery_selected_chain_id: "value_flow",
|
||||
assistant_mcp_discovery_entry_point_v1: {
|
||||
turn_input: {
|
||||
turn_meaning_ref: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "turnover",
|
||||
explicit_entity_candidates: ["Группа СВК"],
|
||||
explicit_date_scope: "2020",
|
||||
stale_replay_forbidden: true
|
||||
}
|
||||
},
|
||||
bridge: {
|
||||
pilot: {
|
||||
pilot_scope: "counterparty_value_flow_query_movements_v1",
|
||||
derived_value_flow: {
|
||||
value_flow_direction: "incoming_customer_revenue",
|
||||
counterparty: "Группа СВК",
|
||||
period_scope: "2020",
|
||||
total_amount_human_ru: "12 093 465 руб."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
dialog_continuation_contract_v2: {
|
||||
decision: "continue_previous"
|
||||
}
|
||||
}
|
||||
} as any;
|
||||
|
||||
const evolved = evolveAddressNavigationStateWithAssistantItem(base, assistantItem, 2);
|
||||
|
||||
expect(evolved.result_sets[0]?.intent).toBe("customer_revenue_and_payments");
|
||||
expect(evolved.result_sets[0]?.filters.counterparty).toBe("Группа СВК");
|
||||
expect(evolved.session_context.active_focus_object?.label).toBe("Группа СВК");
|
||||
expect(evolved.session_context.date_scope.period_from).toBe("2020-01-01");
|
||||
expect(evolved.session_context.date_scope.period_to).toBe("2020-12-31");
|
||||
});
|
||||
|
||||
it("stores bidirectional value-flow proof bundle for adjacent evidence drilldowns", () => {
|
||||
const base = createEmptyAddressNavigationState("asst-vf-bundle", "2026-04-12T10:00:00.000Z");
|
||||
const valueFlowBundle = {
|
||||
counterparty: "Группа СВК",
|
||||
period_scope: "2020",
|
||||
incoming_customer_revenue: {
|
||||
total_amount_human_ru: "12 093 465 руб."
|
||||
},
|
||||
outgoing_supplier_payout: {
|
||||
total_amount_human_ru: "0 руб."
|
||||
},
|
||||
net_amount_human_ru: "12 093 465 руб.",
|
||||
net_direction: "net_incoming"
|
||||
};
|
||||
const netItem = {
|
||||
message_id: "msg-vf-net",
|
||||
session_id: "asst-vf-bundle",
|
||||
role: "assistant",
|
||||
text: "По контрагенту Группа СВК за период 2020 получили 12 093 465 руб., заплатили 0 руб.; расчетное нетто в нашу сторону: 12 093 465 руб.",
|
||||
created_at: "2026-04-12T10:14:00.000Z",
|
||||
debug: {
|
||||
detected_mode: "chat",
|
||||
detected_intent: "customer_revenue_and_payments",
|
||||
extracted_filters: {
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
},
|
||||
anchor_type: "unknown",
|
||||
mcp_discovery_response_applied: true,
|
||||
mcp_discovery_selected_chain_id: "value_flow_comparison",
|
||||
assistant_mcp_discovery_entry_point_v1: {
|
||||
turn_input: {
|
||||
turn_meaning_ref: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "net_value_flow",
|
||||
explicit_entity_candidates: ["Группа СВК"],
|
||||
explicit_date_scope: "2020",
|
||||
stale_replay_forbidden: true
|
||||
}
|
||||
},
|
||||
bridge: {
|
||||
pilot: {
|
||||
pilot_scope: "counterparty_bidirectional_value_flow_query_movements_v1",
|
||||
derived_bidirectional_value_flow: valueFlowBundle
|
||||
}
|
||||
}
|
||||
},
|
||||
dialog_continuation_contract_v2: {
|
||||
decision: "continue_previous"
|
||||
}
|
||||
}
|
||||
} as any;
|
||||
|
||||
const afterNet = evolveAddressNavigationStateWithAssistantItem(base, netItem, 5);
|
||||
expect(afterNet.session_context.comparison_scope?.counterparty?.label).toBe("Группа СВК");
|
||||
expect(afterNet.session_context.comparison_scope?.proof_bundles?.counterparty_value_flow_bundle).toEqual(
|
||||
valueFlowBundle
|
||||
);
|
||||
|
||||
const docsItem = {
|
||||
message_id: "msg-vf-docs",
|
||||
session_id: "asst-vf-bundle",
|
||||
role: "assistant",
|
||||
text: "По документам по контрагенту Группа СВК за 2020 полный срез не подтвержден.",
|
||||
created_at: "2026-04-12T10:15:00.000Z",
|
||||
debug: {
|
||||
detected_mode: "chat",
|
||||
detected_intent: "list_documents_by_counterparty",
|
||||
extracted_filters: {
|
||||
counterparty: "Группа СВК",
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
},
|
||||
anchor_type: "unknown",
|
||||
mcp_discovery_response_applied: true,
|
||||
mcp_discovery_selected_chain_id: "document_evidence",
|
||||
assistant_mcp_discovery_entry_point_v1: {
|
||||
turn_input: {
|
||||
turn_meaning_ref: {
|
||||
asked_domain_family: "documents",
|
||||
asked_action_family: "list_documents",
|
||||
explicit_entity_candidates: ["Группа СВК"],
|
||||
explicit_date_scope: "2020",
|
||||
stale_replay_forbidden: true
|
||||
}
|
||||
},
|
||||
bridge: {
|
||||
pilot: {
|
||||
pilot_scope: "counterparty_document_evidence_query_documents_v1"
|
||||
}
|
||||
}
|
||||
},
|
||||
dialog_continuation_contract_v2: {
|
||||
decision: "continue_previous"
|
||||
}
|
||||
}
|
||||
} as any;
|
||||
|
||||
const afterDocs = evolveAddressNavigationStateWithAssistantItem(afterNet, docsItem, 6);
|
||||
expect(afterDocs.session_context.active_focus_object?.label).toBe("Группа СВК");
|
||||
expect(afterDocs.session_context.comparison_scope?.proof_bundles?.counterparty_value_flow_bundle).toEqual(
|
||||
valueFlowBundle
|
||||
);
|
||||
|
||||
const movementsItem = {
|
||||
message_id: "msg-vf-movements",
|
||||
session_id: "asst-vf-bundle",
|
||||
role: "assistant",
|
||||
text: "По движениям по контрагенту Группа СВК за 2020 полный срез не подтвержден.",
|
||||
created_at: "2026-04-12T10:16:00.000Z",
|
||||
debug: {
|
||||
detected_mode: "chat",
|
||||
detected_intent: "inventory_purchase_documents_for_item",
|
||||
extracted_filters: {
|
||||
counterparty: "Группа СВК",
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
},
|
||||
anchor_type: "unknown",
|
||||
mcp_discovery_response_applied: true,
|
||||
mcp_discovery_selected_chain_id: "movement_evidence",
|
||||
assistant_mcp_discovery_entry_point_v1: {
|
||||
turn_input: {
|
||||
turn_meaning_ref: {
|
||||
asked_domain_family: "movements",
|
||||
asked_action_family: "list_movements",
|
||||
explicit_entity_candidates: ["Группа СВК"],
|
||||
explicit_date_scope: "2020",
|
||||
stale_replay_forbidden: true
|
||||
}
|
||||
},
|
||||
bridge: {
|
||||
pilot: {
|
||||
pilot_scope: "counterparty_movement_evidence_query_movements_v1"
|
||||
}
|
||||
}
|
||||
},
|
||||
dialog_continuation_contract_v2: {
|
||||
decision: "continue_previous"
|
||||
}
|
||||
}
|
||||
} as any;
|
||||
|
||||
const afterMovements = evolveAddressNavigationStateWithAssistantItem(afterDocs, movementsItem, 7);
|
||||
expect(afterMovements.result_sets.at(-1)?.intent).toBe("bank_operations_by_counterparty");
|
||||
expect(afterMovements.result_sets.at(-1)?.type).toBe("bank_operations_list");
|
||||
expect(afterMovements.session_context.active_focus_object?.label).toBe("Группа СВК");
|
||||
expect(afterMovements.session_context.comparison_scope?.proof_bundles?.counterparty_value_flow_bundle).toEqual(
|
||||
valueFlowBundle
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps selected counterparty focus for company boundary summaries", () => {
|
||||
const initial = createEmptyAddressNavigationState("asst-boundary", "2026-04-12T10:00:00.000Z");
|
||||
const assistantItem = {
|
||||
|
|
|
|||
|
|
@ -684,6 +684,27 @@ describe("assistantContinuityPolicy organization authority", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("carries value-flow temporal scope into follow-up evidence drilldowns", () => {
|
||||
const filters = applyTemporalCarryoverFilters(
|
||||
{
|
||||
counterparty: "Группа СВК"
|
||||
},
|
||||
{
|
||||
as_of_date: null,
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
},
|
||||
null,
|
||||
"customer_revenue_and_payments"
|
||||
);
|
||||
|
||||
expect(filters).toEqual({
|
||||
counterparty: "Группа СВК",
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
});
|
||||
});
|
||||
|
||||
it("resolves inventory root pivots through one shared helper", () => {
|
||||
const flags = resolveInventoryFollowupPivotFlags(
|
||||
{ intent: "inventory_on_hand_as_of_date" },
|
||||
|
|
|
|||
|
|
@ -130,15 +130,14 @@ describe("assistant MCP discovery answer adapter", () => {
|
|||
const pilot = await executeAssistantMcpDiscoveryPilot(planner, buildDeps([]));
|
||||
|
||||
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
|
||||
const answerText = [draft.headline, ...draft.confirmed_lines, ...draft.unknown_lines].join("\n");
|
||||
const answerText = [draft.headline, ...draft.confirmed_lines, ...draft.inference_lines, ...draft.unknown_lines].join("\n");
|
||||
|
||||
expect(draft.answer_mode).toBe("checked_sources_only");
|
||||
expect(draft.answer_mode).toBe("confirmed_with_bounded_inference");
|
||||
expect(answerText).toContain("0 руб.");
|
||||
expect(answerText).toContain("исходящих платежей/списаний не найдено");
|
||||
expect(answerText).toContain("not_found");
|
||||
expect(answerText).toContain("Группа СВК");
|
||||
expect(answerText).toContain("2020");
|
||||
expect(draft.next_step_line).toContain("соседних периодах");
|
||||
expect(draft.next_step_line).toContain("двусторонний денежный поток");
|
||||
expect(draft.unknown_lines.join("\n")).toContain("outside the checked period");
|
||||
});
|
||||
|
||||
it("turns generic document evidence into a bounded document answer draft", async () => {
|
||||
|
|
@ -1464,7 +1463,7 @@ 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("renders zero-row supplier payout as a checked negative with period and counterparty", async () => {
|
||||
it("renders zero-row supplier payout as a confirmed scoped zero with period and counterparty", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
turnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
|
|
@ -1479,18 +1478,18 @@ describe("assistant MCP discovery answer adapter", () => {
|
|||
|
||||
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
|
||||
const confirmedText = draft.confirmed_lines.join("\n");
|
||||
const inferenceText = draft.inference_lines.join("\n");
|
||||
const unknownText = draft.unknown_lines.join("\n");
|
||||
|
||||
expect(draft.answer_mode).toBe("checked_sources_only");
|
||||
expect(draft.answer_mode).toBe("confirmed_with_bounded_inference");
|
||||
expect(draft.headline).toContain("Группа СВК");
|
||||
expect(draft.headline).toContain("2020");
|
||||
expect(draft.headline).toContain("исходящих платежей");
|
||||
expect(draft.headline).toContain("0 руб.");
|
||||
expect(confirmedText).toContain("Группа СВК");
|
||||
expect(confirmedText).toContain("ООО Альтернатива Плюс");
|
||||
expect(confirmedText).toContain("2020");
|
||||
expect(confirmedText).toContain("не найдено");
|
||||
expect(unknownText).toContain("вне периода 2020");
|
||||
expect(unknownText).toContain("вне доступного банковского контура");
|
||||
expect(confirmedText).toContain("0 руб.");
|
||||
expect(confirmedText).toContain("Граница");
|
||||
expect(inferenceText).toContain("not_found result was treated as zero");
|
||||
expect(unknownText).toContain("outside the checked period");
|
||||
});
|
||||
|
||||
it("turns bidirectional value-flow evidence into a bounded net cash answer draft", async () => {
|
||||
|
|
|
|||
|
|
@ -1650,6 +1650,38 @@ describe("assistant MCP discovery pilot executor", () => {
|
|||
expect(String(call?.query ?? "")).toContain("СписаниеСРасчетногоСчета");
|
||||
});
|
||||
|
||||
it("treats zero-row supplier payout query as confirmed not-found inside checked scope", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
turnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "payout",
|
||||
explicit_entity_candidates: ["SVK"],
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "counterparty_payouts_or_outflow"
|
||||
}
|
||||
});
|
||||
const deps = buildDeps([]);
|
||||
|
||||
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
|
||||
|
||||
expect(result.pilot_status).toBe("executed");
|
||||
expect(result.pilot_scope).toBe("counterparty_supplier_payout_query_movements_v1");
|
||||
expect(result.evidence.evidence_status).toBe("confirmed");
|
||||
expect(result.evidence.answer_permission).toBe("confirmed_answer");
|
||||
expect(result.evidence.confirmed_facts[0]).toContain("not_found");
|
||||
expect(result.reason_codes).toContain("pilot_derived_value_flow_from_confirmed_rows");
|
||||
expect(result.evidence.reason_codes).toContain("confirmed_no_match_fact_with_allowed_mcp_evidence");
|
||||
expect(result.derived_value_flow).toMatchObject({
|
||||
value_flow_direction: "outgoing_supplier_payout",
|
||||
counterparty: "SVK",
|
||||
period_scope: "2020",
|
||||
rows_matched: 0,
|
||||
rows_with_amount: 0,
|
||||
total_amount: 0,
|
||||
total_amount_human_ru: "0 руб."
|
||||
});
|
||||
});
|
||||
|
||||
it("marks value-flow coverage as limited when the probe row limit is reached", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
turnMeaning: {
|
||||
|
|
|
|||
|
|
@ -1602,8 +1602,9 @@ describe("assistant MCP discovery response candidate", () => {
|
|||
expect(firstLine).toContain("По подтвержденным строкам 1С");
|
||||
expect(firstLine).toContain("входящие 157 192 981,43 руб.");
|
||||
expect(firstLine).toContain("исходящие 35 439 044,74 руб.");
|
||||
expect(firstLine).toContain("не аудиторское заключение");
|
||||
expect(firstLine).toContain("не подтвержденная чистая прибыль");
|
||||
expect(firstLine).toContain("не финансовый аудит");
|
||||
expect(firstLine).toContain("не подтверждение чистой прибыли");
|
||||
expect(firstLine).not.toContain("аудиторское заключение");
|
||||
expect(candidate.reply_text).toContain("Интерпретация:");
|
||||
expect(candidate.reply_text).toContain("Что видно в ограниченном денежном срезе");
|
||||
expect(candidate.reply_text).toContain("Что не подтверждено в этом срезе");
|
||||
|
|
|
|||
|
|
@ -753,7 +753,73 @@ describe("assistant MCP discovery response policy", () => {
|
|||
expect(result.reply_text).toContain("20 653 490");
|
||||
expect(result.reply_text).toContain("2 129 651");
|
||||
expect(result.reason_codes).toContain("mcp_discovery_response_policy_value_flow_action_conflict_allows_candidate_override");
|
||||
expect(result.reason_codes).toContain("mcp_discovery_response_policy_semantic_conflict_allows_candidate_override");
|
||||
expect(result.reason_codes).toContain("mcp_discovery_response_policy_semantic_conflict_allows_candidate_override");
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_exact_document_list_address_reply");
|
||||
});
|
||||
|
||||
it("overrides exact document-list replies when they contradict the discovery period scope", () => {
|
||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||
currentReply:
|
||||
"Контрагент: Группа СВК. Найдено документов: 19.\n" +
|
||||
"1. 2021-11-10T12:00:07Z | Поступление на расчетный счет 00000000013 от 10.11.2021 12:00:07 | аналитика: Группа СВК",
|
||||
currentReplySource: "address_query_runtime_v1",
|
||||
currentReplyType: "factual",
|
||||
addressRuntimeMeta: {
|
||||
detected_intent: "list_documents_by_counterparty",
|
||||
selected_recipe: "address_documents_by_counterparty_v1",
|
||||
mcp_call_status: "matched_non_empty",
|
||||
response_type: "FACTUAL_LIST",
|
||||
truth_mode: "confirmed",
|
||||
extracted_filters: {
|
||||
counterparty: "Группа СВК",
|
||||
as_of_date: "2026-06-15"
|
||||
},
|
||||
dialog_continuation_contract_v2: {
|
||||
target_intent: "list_documents_by_counterparty"
|
||||
},
|
||||
assistant_mcp_discovery_entry_point_v1: entryPoint({
|
||||
turn_input: {
|
||||
adapter_status: "ready",
|
||||
should_run_discovery: true,
|
||||
turn_meaning_ref: {
|
||||
asked_domain_family: "documents",
|
||||
asked_action_family: "list_documents",
|
||||
explicit_entity_candidates: ["Группа СВК"],
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "document_evidence"
|
||||
},
|
||||
data_need_graph: {
|
||||
business_fact_family: "document_evidence",
|
||||
action_family: "list_documents",
|
||||
subject_candidates: ["Группа СВК"]
|
||||
}
|
||||
},
|
||||
bridge: {
|
||||
bridge_status: "answer_draft_ready",
|
||||
user_facing_response_allowed: true,
|
||||
business_fact_answer_allowed: true,
|
||||
requires_user_clarification: false,
|
||||
answer_draft: {
|
||||
answer_mode: "bounded_inference_only",
|
||||
headline: "По документам по контрагенту Группа СВК за 2020 полный срез не подтвержден.",
|
||||
confirmed_lines: [],
|
||||
inference_lines: ["Подтвержденных строк документов за 2020 этим поиском не найдено."],
|
||||
unknown_lines: ["Полный исторический срез вне 2020 этим поиском не подтвержден."],
|
||||
limitation_lines: [],
|
||||
next_step_line: null
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.applied).toBe(true);
|
||||
expect(result.decision).toBe("apply_candidate");
|
||||
expect(result.reply_text).toContain("за 2020");
|
||||
expect(result.reply_text).not.toContain("2021-11-10");
|
||||
expect(result.reason_codes).toContain(
|
||||
"mcp_discovery_response_policy_document_list_temporal_conflict_allows_candidate_override"
|
||||
);
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_exact_document_list_address_reply");
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue