Удержать контекст СВК в follow-up ассистента

This commit is contained in:
2026-06-15 22:56:10 +03:00
parent 7e70ec1d46
commit b3f2e405bb
23 changed files with 950 additions and 91 deletions
@@ -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");
}