Закрепить phase83 planner-brain replay ассистента
This commit is contained in:
parent
f24cddc492
commit
5ed25bad81
|
|
@ -465,7 +465,7 @@
|
|||
"title": "Broad business evaluation becomes grounded summary instead of stale lifecycle dump",
|
||||
"question": "Как ты оценишь деятельность компании?",
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)коротко|оценк|частичн",
|
||||
"(?i)ограниченн|проверенн|не\\s+аудит|не\\s+аудитор",
|
||||
"(?i)1с|подтвержд",
|
||||
"(?i)денежн|долг|ндс|контрагент|операц"
|
||||
],
|
||||
|
|
@ -522,7 +522,7 @@
|
|||
"factual_with_explanation"
|
||||
],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)metadata|метадан",
|
||||
"(?i)схем|метадан|объект",
|
||||
"(?i)справоч|catalog|directory",
|
||||
"(?i)контрагент"
|
||||
],
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ function valueFlowZeroResultConfirmedLine(pilot) {
|
|||
const period = explicitDateScope(pilot);
|
||||
const organizationPart = organization ? ` по организации ${organization}` : "";
|
||||
const periodPart = period ? ` за период ${period}` : " в проверенном окне";
|
||||
return `В проверенном срезе 1С по контрагенту ${counterparty}${organizationPart}${periodPart} ${valueFlowDirectionLabelRu(pilot)} не найдено.`;
|
||||
return `В проверенном срезе 1С по контрагенту ${counterparty}${organizationPart}${periodPart}: 0 руб.; ${valueFlowDirectionLabelRu(pilot)} не найдено.`;
|
||||
}
|
||||
function valueFlowZeroResultUnknownLine(pilot) {
|
||||
if (!hasExecutedZeroValueFlowRows(pilot)) {
|
||||
|
|
|
|||
|
|
@ -123,6 +123,14 @@ function hasBusinessOverviewDirectMoneyAnswerHint(input) {
|
|||
}
|
||||
return /(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|how\s+much)[\s\S]{0,120}(?:\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u0432\u044b\u0440\u0443\u0447|\u0434\u0435\u043d\p{L}*|\u043f\u043e\u043b\u0443\u0447|\u043f\u043e\u0441\u0442\u0443\u043f\p{L}*)|(?:\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u0432\u044b\u0440\u0443\u0447)[\s\S]{0,120}(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|\u0432\u0441\u0435\u0433\u043e|\u0432\u043e\u043e\u0431\u0449\u0435|(?:19|20)\d{2}|all\s+time)|(?:\u043a\u0430\u043a\u043e\u0439|\u043a\u0430\u043a\u0430\u044f|\u043a\u0430\u043a\u0438\u0435|which|what)[\s\S]{0,80}(?:\u0441\u0430\u043c\p{L}*|top|best|most)[\s\S]{0,80}(?:\u0434\u043e\u0445\u043e\u0434\u043d|\u0432\u044b\u0440\u0443\u0447|\u043e\u0431\u043e\u0440\u043e\u0442|revenue|turnover)[\s\S]{0,40}(?:\u0433\u043e\u0434|year)/iu.test(text);
|
||||
}
|
||||
function hasIncomingVsOutgoingComparisonHint(rawUtterance) {
|
||||
if (!rawUtterance) {
|
||||
return false;
|
||||
}
|
||||
const mentionsBothDirections = /(?:\u0432\u0445\u043e\u0434\p{L}*[\s\S]{0,100}\u0438(?:\u043b\u0438)?\s+\u0438\u0441\u0445\u043e\u0434\p{L}*|\u0438\u0441\u0445\u043e\u0434\p{L}*[\s\S]{0,100}\u0438(?:\u043b\u0438)?\s+\u0432\u0445\u043e\u0434\p{L}*|incoming[\s\S]{0,100}outgoing|outgoing[\s\S]{0,100}incoming)/iu.test(rawUtterance);
|
||||
const asksComparison = /(?:\u0447\u0442\u043e[\s\S]{0,40}\u0431\u043e\u043b\p{L}*|\u0431\u043e\u043b\p{L}*[\s\S]{0,40}\u0447\u0442\u043e|\u043f\u0440\u0435\u0432\u044b\p{L}*|\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u0441\u0440\u0430\u0432\u043d\p{L}*|which[\s\S]{0,40}(?:more|larger|higher|bigger)|what[\s\S]{0,40}(?:more|larger|higher|bigger)|(?:more|larger|higher|bigger)[\s\S]{0,40}(?:incoming|outgoing))/iu.test(rawUtterance);
|
||||
return mentionsBothDirections && asksComparison;
|
||||
}
|
||||
function timeScopeNeedFor(input) {
|
||||
if (input.explicitDateScope) {
|
||||
return "explicit_period";
|
||||
|
|
@ -420,7 +428,7 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
|
|||
const explicitOrganizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
|
||||
const metadataScopeHint = toNonEmptyString(turnMeaning?.metadata_scope_hint);
|
||||
const subjectResolutionOptional = turnMeaning?.subject_resolution_optional === true;
|
||||
const subjectCandidates = (turnMeaning?.explicit_entity_candidates ?? [])
|
||||
const rawSubjectCandidates = (turnMeaning?.explicit_entity_candidates ?? [])
|
||||
.map((item) => toNonEmptyString(item))
|
||||
.filter((item) => Boolean(item));
|
||||
const initialBusinessFactFamily = businessFactFamilyFor({
|
||||
|
|
@ -434,10 +442,26 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
|
|||
const oneSidedBusinessOverviewValueFlowOverride = initialBusinessFactFamily === "business_overview" &&
|
||||
Boolean(oneSidedValueAction) &&
|
||||
!detailedBusinessOverviewMoneyBreakdownHint;
|
||||
const action = oneSidedBusinessOverviewValueFlowOverride ? oneSidedValueAction : rawAction;
|
||||
const businessFactFamily = oneSidedBusinessOverviewValueFlowOverride ? "value_flow" : initialBusinessFactFamily;
|
||||
const bidirectionalBusinessOverviewValueFlowOverride = initialBusinessFactFamily === "business_overview" && hasIncomingVsOutgoingComparisonHint(rawQuestionSignal);
|
||||
const oneSidedValueFlowActionOverride = initialBusinessFactFamily === "value_flow" &&
|
||||
Boolean(oneSidedValueAction) &&
|
||||
(rawAction === "counterparty_value_or_turnover" || rawAction === "counterparty_value");
|
||||
const action = bidirectionalBusinessOverviewValueFlowOverride
|
||||
? "net_value_flow"
|
||||
: oneSidedBusinessOverviewValueFlowOverride || oneSidedValueFlowActionOverride
|
||||
? oneSidedValueAction
|
||||
: rawAction;
|
||||
const businessFactFamily = oneSidedBusinessOverviewValueFlowOverride || bidirectionalBusinessOverviewValueFlowOverride
|
||||
? "value_flow"
|
||||
: initialBusinessFactFamily;
|
||||
const aggregationNeed = aggregationNeedFor(aggregationAxis);
|
||||
const comparisonNeed = comparisonNeedFor(action);
|
||||
const organizationScopedValueComparison = businessFactFamily === "value_flow" &&
|
||||
comparisonNeed === "incoming_vs_outgoing" &&
|
||||
Boolean(explicitOrganizationScope) &&
|
||||
hasIncomingVsOutgoingComparisonHint(rawQuestionSignal);
|
||||
const subjectCandidates = organizationScopedValueComparison ? [] : rawSubjectCandidates;
|
||||
const effectiveMetadataScopeHint = organizationScopedValueComparison ? null : metadataScopeHint;
|
||||
const allTimeScopeHint = hasAllTimeScopeHint(rawUtterance);
|
||||
const subjectScopedBidirectionalAllTime = businessFactFamily === "value_flow" &&
|
||||
comparisonNeed === "incoming_vs_outgoing" &&
|
||||
|
|
@ -556,6 +580,15 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
|
|||
if (oneSidedBusinessOverviewValueFlowOverride) {
|
||||
pushReason(reasonCodes, "data_need_graph_business_overview_one_sided_money_total_routed_to_value_flow");
|
||||
}
|
||||
if (bidirectionalBusinessOverviewValueFlowOverride) {
|
||||
pushReason(reasonCodes, "data_need_graph_business_overview_bidirectional_money_comparison_routed_to_value_flow");
|
||||
}
|
||||
if (organizationScopedValueComparison) {
|
||||
pushReason(reasonCodes, "data_need_graph_organization_scoped_value_comparison_ignores_stale_subject");
|
||||
}
|
||||
if (oneSidedValueFlowActionOverride) {
|
||||
pushReason(reasonCodes, "data_need_graph_value_flow_one_sided_money_total_action_normalized");
|
||||
}
|
||||
if (clarificationGaps.includes("organization")) {
|
||||
pushReason(reasonCodes, "data_need_graph_open_scope_total_needs_organization");
|
||||
}
|
||||
|
|
@ -566,7 +599,7 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
|
|||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_DATA_NEED_GRAPH_SCHEMA_VERSION,
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: subjectCandidates,
|
||||
metadata_scope_hint: metadataScopeHint,
|
||||
metadata_scope_hint: effectiveMetadataScopeHint,
|
||||
subject_resolution_optional: subjectResolutionOptional || undefined,
|
||||
business_fact_family: businessFactFamily,
|
||||
action_family: toNonEmptyString(action),
|
||||
|
|
|
|||
|
|
@ -107,7 +107,10 @@ function scoreExplicitEntityCandidate(raw, cleaned, index) {
|
|||
return score;
|
||||
}
|
||||
function firstEntityCandidate(planner) {
|
||||
const candidates = planner.discovery_plan.turn_meaning_ref?.explicit_entity_candidates ?? [];
|
||||
const graphCandidates = planner.data_need_graph?.subject_candidates;
|
||||
const candidates = Array.isArray(graphCandidates)
|
||||
? graphCandidates
|
||||
: planner.discovery_plan.turn_meaning_ref?.explicit_entity_candidates ?? [];
|
||||
let best = null;
|
||||
for (let index = 0; index < candidates.length; index += 1) {
|
||||
const candidate = candidates[index];
|
||||
|
|
@ -582,10 +585,32 @@ function isEntityResolutionPilotEligible(planner) {
|
|||
combined.includes("entity discovery") ||
|
||||
combined.includes("counterparty search")));
|
||||
}
|
||||
function normalizeMetadataScopeHint(value) {
|
||||
const text = toNonEmptyString(value);
|
||||
if (!text) {
|
||||
return null;
|
||||
}
|
||||
const normalized = text
|
||||
.replace(/[«»"']/gu, "")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
const comparable = normalized.toLowerCase().replace(/ё/g, "е");
|
||||
if (/^контрагент(?:ам|ами|ах|ов|у|а|ом|е|ы)?$/iu.test(comparable)) {
|
||||
return "контрагент";
|
||||
}
|
||||
if (/^(?:справочник\.)?контрагенты$/iu.test(comparable)) {
|
||||
return "контрагент";
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
function metadataScopeForPlanner(planner) {
|
||||
const metadataScopeHint = normalizeMetadataScopeHint(planner.discovery_plan.turn_meaning_ref?.metadata_scope_hint);
|
||||
if (metadataScopeHint) {
|
||||
return metadataScopeHint;
|
||||
}
|
||||
const entityCandidate = firstEntityCandidate(planner);
|
||||
if (entityCandidate) {
|
||||
return entityCandidate;
|
||||
return normalizeMetadataScopeHint(entityCandidate) ?? entityCandidate;
|
||||
}
|
||||
if (planner.selected_chain_id === "catalog_drilldown") {
|
||||
const surface = planner.metadata_surface_ref;
|
||||
|
|
@ -647,7 +672,9 @@ function valueFlowPilotProfile(planner) {
|
|||
const meaning = planner.discovery_plan.turn_meaning_ref;
|
||||
const action = String(meaning?.asked_action_family ?? "").toLowerCase();
|
||||
const unsupported = String(meaning?.unsupported_but_understood_family ?? "").toLowerCase();
|
||||
const combined = `${action} ${unsupported}`;
|
||||
const graphAction = String(planner.data_need_graph?.action_family ?? "").toLowerCase();
|
||||
const graphComparison = String(planner.data_need_graph?.comparison_need ?? "").toLowerCase();
|
||||
const combined = `${action} ${unsupported} ${graphAction} ${graphComparison} ${planner.selected_chain_id}`;
|
||||
if (combined.includes("net_value_flow") ||
|
||||
combined.includes("bidirectional") ||
|
||||
combined.includes("netting") ||
|
||||
|
|
|
|||
|
|
@ -614,13 +614,19 @@ function businessOverviewInventoryLine(overview) {
|
|||
].filter((item) => Boolean(item));
|
||||
return pieces.length > 0 ? `Склад: ${pieces.join(", ")}.` : null;
|
||||
}
|
||||
function bidirectionalNetLabel(direction) {
|
||||
function bidirectionalNetLabel(direction, scopeKind = "counterparty") {
|
||||
if (direction === "net_outgoing") {
|
||||
if (scopeKind === "organization") {
|
||||
return "нетто в сторону исходящих платежей";
|
||||
}
|
||||
return "нетто в сторону контрагента";
|
||||
}
|
||||
if (direction === "balanced") {
|
||||
return "нетто около нуля";
|
||||
}
|
||||
if (scopeKind === "organization") {
|
||||
return "нетто в сторону входящих платежей";
|
||||
}
|
||||
return "нетто в нашу сторону";
|
||||
}
|
||||
function buildCompactBidirectionalValueFlowReply(entryPoint, draft) {
|
||||
|
|
@ -642,6 +648,11 @@ function buildCompactBidirectionalValueFlowReply(entryPoint, draft) {
|
|||
}
|
||||
const counterparty = toNonEmptyString(flow.counterparty);
|
||||
const organizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
|
||||
const scopeKind = counterparty
|
||||
? "counterparty"
|
||||
: organizationScope
|
||||
? "organization"
|
||||
: "generic";
|
||||
const subjectLead = counterparty
|
||||
? `по контрагенту ${counterparty}`
|
||||
: organizationScope
|
||||
|
|
@ -649,9 +660,9 @@ function buildCompactBidirectionalValueFlowReply(entryPoint, draft) {
|
|||
: "по выбранному контуру";
|
||||
const period = toNonEmptyString(flow.period_scope);
|
||||
const periodText = period ? ` за период ${period}` : " в проверенном окне";
|
||||
const netLabel = bidirectionalNetLabel(flow.net_direction);
|
||||
const netLabel = bidirectionalNetLabel(flow.net_direction, scopeKind);
|
||||
const lines = [
|
||||
`Коротко: ${subjectLead}${periodText} по найденным строкам 1С получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}; расчетное ${netLabel}: ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}.`
|
||||
`${subjectLead}${periodText} по найденным строкам 1С получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}; расчетное ${netLabel}: ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}.`
|
||||
];
|
||||
const basis = [];
|
||||
if (incomingAmount) {
|
||||
|
|
@ -661,7 +672,12 @@ function buildCompactBidirectionalValueFlowReply(entryPoint, draft) {
|
|||
basis.push("исходящие платежи");
|
||||
}
|
||||
if (basis.length > 0) {
|
||||
lines.push(`Основа: проверенные ${basis.join(" и ")} по этому контрагенту в 1С.`);
|
||||
const basisScope = scopeKind === "counterparty"
|
||||
? "по этому контрагенту"
|
||||
: scopeKind === "organization"
|
||||
? "по этой компании"
|
||||
: "по выбранному контуру";
|
||||
lines.push(`Основа: проверенные ${basis.join(" и ")} ${basisScope} в 1С.`);
|
||||
}
|
||||
if (flow.coverage_limited_by_probe_limit === true) {
|
||||
lines.push("Важно: часть проверки достигла лимита строк, поэтому это проверенный срез найденных движений, а не гарантия полного периода.");
|
||||
|
|
|
|||
|
|
@ -247,6 +247,88 @@ function readTruthAnswerShape(input) {
|
|||
const truthAnswerPolicy = toRecordObject(input.addressRuntimeMeta?.assistant_truth_answer_policy_v1);
|
||||
return toRecordObject(truthAnswerPolicy?.answer_shape);
|
||||
}
|
||||
function hasCurrentClarificationRequiredAddressReply(input) {
|
||||
const answerShape = toNonEmptyString(input.addressRuntimeMeta?.answer_shape) ??
|
||||
toNonEmptyString(input.addressRuntimeMeta?.truth_mode) ??
|
||||
toNonEmptyString(readTruthAnswerShape(input)?.answer_shape);
|
||||
if (answerShape === "clarification_required") {
|
||||
return true;
|
||||
}
|
||||
const truthAnswerPolicy = toRecordObject(input.addressRuntimeMeta?.assistant_truth_answer_policy_v1);
|
||||
const truthGate = toRecordObject(truthAnswerPolicy?.truth_gate);
|
||||
const policyAnswerShape = toRecordObject(truthAnswerPolicy?.answer_shape);
|
||||
return (toNonEmptyString(truthGate?.truth_mode) === "clarification_required" ||
|
||||
toNonEmptyString(policyAnswerShape?.answer_shape) === "clarification_required");
|
||||
}
|
||||
function hasDiscoveryClarificationCandidatePriorityOverCurrentClarification(input, entryPoint, candidate) {
|
||||
if (!hasCurrentClarificationRequiredAddressReply(input)) {
|
||||
return false;
|
||||
}
|
||||
if (candidate.candidate_status !== "clarification_candidate") {
|
||||
return false;
|
||||
}
|
||||
if (!candidate.eligible_for_future_hot_runtime || !toNonEmptyString(candidate.reply_text)) {
|
||||
return false;
|
||||
}
|
||||
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
||||
return false;
|
||||
}
|
||||
const graph = readDiscoveryDataNeedGraph(entryPoint);
|
||||
const turnMeaning = readDiscoveryTurnMeaning(entryPoint);
|
||||
const discoveryFamily = toNonEmptyString(graph?.business_fact_family);
|
||||
const askedDomain = toNonEmptyString(turnMeaning?.asked_domain_family);
|
||||
const askedAction = toNonEmptyString(turnMeaning?.asked_action_family) ?? toNonEmptyString(graph?.action_family);
|
||||
const asksForMovements = discoveryFamily === "movement_evidence" || askedDomain === "movements" || askedAction === "list_movements";
|
||||
const truthAnswerShape = readTruthAnswerShape(input);
|
||||
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
|
||||
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
|
||||
const capabilityId = toNonEmptyString(input.addressRuntimeMeta?.capability_id) ??
|
||||
toNonEmptyString(input.addressRuntimeMeta?.capability_contract_id) ??
|
||||
toNonEmptyString(truthAnswerShape?.capability_contract_id);
|
||||
const missingFilters = [
|
||||
...readStringArray(input.addressRuntimeMeta?.missing_required_filters),
|
||||
...readStringArray(input.addressRuntimeMeta?.missing_anchors),
|
||||
...readStringArray(toRecordObject(input.addressRuntimeMeta?.capability_binding_contract)?.missing_anchors)
|
||||
];
|
||||
const staleInventoryItemClarification = Boolean(missingFilters.includes("item") &&
|
||||
(detectedIntent?.startsWith("inventory_") ||
|
||||
selectedRecipe?.includes("inventory_") ||
|
||||
capabilityId?.includes("inventory_")));
|
||||
return asksForMovements && staleInventoryItemClarification;
|
||||
}
|
||||
function hasGroundedValueFlowCandidatePriorityOverCurrentClarification(input, entryPoint, candidate) {
|
||||
if (!hasCurrentClarificationRequiredAddressReply(input)) {
|
||||
return false;
|
||||
}
|
||||
if (candidate.candidate_status !== "ready_for_guarded_use") {
|
||||
return false;
|
||||
}
|
||||
if (!candidate.hot_runtime_wired || !candidate.eligible_for_future_hot_runtime || !toNonEmptyString(candidate.reply_text)) {
|
||||
return false;
|
||||
}
|
||||
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
||||
return false;
|
||||
}
|
||||
const graph = readDiscoveryDataNeedGraph(entryPoint);
|
||||
const bridge = toRecordObject(entryPoint?.bridge);
|
||||
const pilot = toRecordObject(bridge?.pilot);
|
||||
const handoff = toRecordObject(bridge?.execution_handoff);
|
||||
const routeCandidate = toRecordObject(bridge?.route_candidate);
|
||||
const selectedChainId = toNonEmptyString(routeCandidate?.selected_chain_id) ??
|
||||
toNonEmptyString(bridge?.selected_chain_id) ??
|
||||
toNonEmptyString(pilot?.selected_chain_id);
|
||||
const pilotScope = toNonEmptyString(pilot?.pilot_scope);
|
||||
const actionFamily = toNonEmptyString(graph?.action_family);
|
||||
const comparisonNeed = toNonEmptyString(graph?.comparison_need);
|
||||
const valueFlowComparison = Boolean(selectedChainId === "value_flow_comparison" ||
|
||||
pilotScope === "counterparty_bidirectional_value_flow_query_movements_v1" ||
|
||||
(actionFamily === "net_value_flow" && comparisonNeed === "incoming_vs_outgoing"));
|
||||
const mcpExecutionPerformed = handoff?.mcp_execution_performed === true || pilot?.mcp_execution_performed === true;
|
||||
const guardedBusinessAnswerAllowed = bridge?.user_facing_response_allowed === true &&
|
||||
bridge?.business_fact_answer_allowed === true &&
|
||||
(handoff?.can_use_guarded_response === true || toNonEmptyString(bridge?.bridge_status) === "answer_draft_ready");
|
||||
return valueFlowComparison && mcpExecutionPerformed && guardedBusinessAnswerAllowed;
|
||||
}
|
||||
function hasEffectivelyFactualAddressReply(input) {
|
||||
if (toNonEmptyString(input.currentReplyType) === "factual") {
|
||||
return true;
|
||||
|
|
@ -719,6 +801,12 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
|
|||
const metadataDiscoveryPriority = hasMetadataDiscoveryPriority(input, entryPoint);
|
||||
const valueFlowActionConflictWithDiscoveryTurnMeaning = hasValueFlowActionConflictWithDiscoveryTurnMeaning(input, entryPoint);
|
||||
const evidenceLaneConflictWithDiscoveryTurnMeaning = hasEvidenceLaneConflictWithDiscoveryTurnMeaning(input, entryPoint);
|
||||
const currentClarificationRequiredAddressReply = hasCurrentClarificationRequiredAddressReply(input);
|
||||
const discoveryClarificationCandidatePriority = hasDiscoveryClarificationCandidatePriorityOverCurrentClarification(input, entryPoint, candidate);
|
||||
const groundedValueFlowCandidatePriority = hasGroundedValueFlowCandidatePriorityOverCurrentClarification(input, entryPoint, candidate);
|
||||
const currentClarificationProtectsCurrentReply = currentClarificationRequiredAddressReply &&
|
||||
!discoveryClarificationCandidatePriority &&
|
||||
!groundedValueFlowCandidatePriority;
|
||||
const exactBankOperationsProtectsCurrent = exactBankOperationsAddressReply &&
|
||||
!semanticConflictWithDiscoveryTurnMeaning &&
|
||||
!valueFlowActionConflictWithDiscoveryTurnMeaning;
|
||||
|
|
@ -752,6 +840,15 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
|
|||
if (evidenceLaneConflictWithDiscoveryTurnMeaning) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_evidence_lane_conflict_allows_candidate_override");
|
||||
}
|
||||
if (currentClarificationProtectsCurrentReply) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_current_clarification_required_reply");
|
||||
}
|
||||
if (discoveryClarificationCandidatePriority) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_clarification_candidate_priority_over_stale_address_clarification");
|
||||
}
|
||||
if (groundedValueFlowCandidatePriority) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_grounded_value_flow_candidate_priority_over_current_clarification");
|
||||
}
|
||||
if (openScopeValueFlowDiscoveryPriority) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_open_scope_value_flow_candidate_priority");
|
||||
}
|
||||
|
|
@ -827,6 +924,7 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
|
|||
!exactDocumentListAddressReply &&
|
||||
!inventoryMarginRankingAddressReply &&
|
||||
!exactInventoryChainProtectsCurrent &&
|
||||
!currentClarificationProtectsCurrentReply &&
|
||||
!(deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") &&
|
||||
ALLOWED_CANDIDATE_STATUSES.has(candidate.candidate_status) &&
|
||||
candidate.eligible_for_future_hot_runtime &&
|
||||
|
|
|
|||
|
|
@ -60,10 +60,25 @@ function isReferentialEntityPlaceholder(value) {
|
|||
"этом"
|
||||
]).has((0, addressTextRepair_1.normalizeRussianComparableText)(value));
|
||||
}
|
||||
function isNegativeCounterpartyScopePlaceholder(value) {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
const text = compactLower((0, addressTextRepair_1.repairAddressMojibakeText)(value));
|
||||
if (!text) {
|
||||
return false;
|
||||
}
|
||||
return (/(?:без\s+(?:привязк\p{L}*|конкретн\p{L}*)\s+к\s+контрагент\p{L}*|без\s+контрагент\p{L}*)/iu.test(text) ||
|
||||
/(?:одн\p{L}*\s+организац\p{L}*\s+без\s+привязк\p{L}*)/iu.test(text) ||
|
||||
/(?:organization\s+without\s+(?:counterparty|specific\s+counterparty))/iu.test(text));
|
||||
}
|
||||
function isReferentialOrganizationPlaceholder(value) {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
if (isNegativeCounterpartyScopePlaceholder(value)) {
|
||||
return true;
|
||||
}
|
||||
return new Set([
|
||||
"эта организация",
|
||||
"этой организации",
|
||||
|
|
@ -129,6 +144,12 @@ function isGarbageSemanticAnchorCandidate(value) {
|
|||
"этими",
|
||||
"итогу",
|
||||
"итогам",
|
||||
"одной",
|
||||
"одному",
|
||||
"одним",
|
||||
"одна",
|
||||
"один",
|
||||
"одно",
|
||||
"всему",
|
||||
"всей",
|
||||
"всем",
|
||||
|
|
@ -168,7 +189,8 @@ function isGarbageSemanticAnchorCandidate(value) {
|
|||
]).has(compact)) {
|
||||
return true;
|
||||
}
|
||||
if (/^(?:по\s+)?(?:этим|этими)\s+данн\p{L}*$/iu.test(text) ||
|
||||
if (isNegativeCounterpartyScopePlaceholder(text) ||
|
||||
/^(?:по\s+)?(?:этим|этими)\s+данн\p{L}*$/iu.test(text) ||
|
||||
/^(?:и\s+)?кто\s+(?:главн\p{L}*|основн\p{L}*|крупн\p{L}*)\s+(?:клиент|покупател|поставщик|контрагент)(?:\s+в)?$/iu.test(text) ||
|
||||
/^(?:и\s+)?(?:главн\p{L}*|основн\p{L}*|крупн\p{L}*)\s+(?:клиент|покупател|поставщик|контрагент)(?:\s+в)?$/iu.test(text) ||
|
||||
/^(?:или\s+)?(?:обычн\p{L}*\s+)?(?:клиент|поставщик|покупател\p{L}*|заказчик|контрагент)(?:\s+или\s+(?:клиент|поставщик|покупател\p{L}*|заказчик|контрагент))?$/iu.test(text) ||
|
||||
|
|
@ -898,6 +920,9 @@ function normalizeLooseOrganizationAlias(value) {
|
|||
if (!text) {
|
||||
return null;
|
||||
}
|
||||
if (isNegativeCounterpartyScopePlaceholder(text)) {
|
||||
return null;
|
||||
}
|
||||
const normalized = text
|
||||
.replace(/^[«"'“”]+|[»"'“”]+$/gu, "")
|
||||
.replace(/\s+/g, " ")
|
||||
|
|
|
|||
|
|
@ -293,7 +293,7 @@ function valueFlowZeroResultConfirmedLine(pilot: AssistantMcpDiscoveryPilotExecu
|
|||
const period = explicitDateScope(pilot);
|
||||
const organizationPart = organization ? ` по организации ${organization}` : "";
|
||||
const periodPart = period ? ` за период ${period}` : " в проверенном окне";
|
||||
return `В проверенном срезе 1С по контрагенту ${counterparty}${organizationPart}${periodPart} ${valueFlowDirectionLabelRu(
|
||||
return `В проверенном срезе 1С по контрагенту ${counterparty}${organizationPart}${periodPart}: 0 руб.; ${valueFlowDirectionLabelRu(
|
||||
pilot
|
||||
)} не найдено.`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -193,6 +193,21 @@ function hasBusinessOverviewDirectMoneyAnswerHint(input: {
|
|||
);
|
||||
}
|
||||
|
||||
function hasIncomingVsOutgoingComparisonHint(rawUtterance: string): boolean {
|
||||
if (!rawUtterance) {
|
||||
return false;
|
||||
}
|
||||
const mentionsBothDirections =
|
||||
/(?:\u0432\u0445\u043e\u0434\p{L}*[\s\S]{0,100}\u0438(?:\u043b\u0438)?\s+\u0438\u0441\u0445\u043e\u0434\p{L}*|\u0438\u0441\u0445\u043e\u0434\p{L}*[\s\S]{0,100}\u0438(?:\u043b\u0438)?\s+\u0432\u0445\u043e\u0434\p{L}*|incoming[\s\S]{0,100}outgoing|outgoing[\s\S]{0,100}incoming)/iu.test(
|
||||
rawUtterance
|
||||
);
|
||||
const asksComparison =
|
||||
/(?:\u0447\u0442\u043e[\s\S]{0,40}\u0431\u043e\u043b\p{L}*|\u0431\u043e\u043b\p{L}*[\s\S]{0,40}\u0447\u0442\u043e|\u043f\u0440\u0435\u0432\u044b\p{L}*|\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u0441\u0440\u0430\u0432\u043d\p{L}*|which[\s\S]{0,40}(?:more|larger|higher|bigger)|what[\s\S]{0,40}(?:more|larger|higher|bigger)|(?:more|larger|higher|bigger)[\s\S]{0,40}(?:incoming|outgoing))/iu.test(
|
||||
rawUtterance
|
||||
);
|
||||
return mentionsBothDirections && asksComparison;
|
||||
}
|
||||
|
||||
function timeScopeNeedFor(input: {
|
||||
family: string | null;
|
||||
explicitDateScope: string | null;
|
||||
|
|
@ -557,7 +572,7 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
|||
const explicitOrganizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
|
||||
const metadataScopeHint = toNonEmptyString(turnMeaning?.metadata_scope_hint);
|
||||
const subjectResolutionOptional = turnMeaning?.subject_resolution_optional === true;
|
||||
const subjectCandidates = (turnMeaning?.explicit_entity_candidates ?? [])
|
||||
const rawSubjectCandidates = (turnMeaning?.explicit_entity_candidates ?? [])
|
||||
.map((item) => toNonEmptyString(item))
|
||||
.filter((item): item is string => Boolean(item));
|
||||
const initialBusinessFactFamily = businessFactFamilyFor({
|
||||
|
|
@ -572,10 +587,31 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
|||
initialBusinessFactFamily === "business_overview" &&
|
||||
Boolean(oneSidedValueAction) &&
|
||||
!detailedBusinessOverviewMoneyBreakdownHint;
|
||||
const action = oneSidedBusinessOverviewValueFlowOverride ? oneSidedValueAction! : rawAction;
|
||||
const businessFactFamily = oneSidedBusinessOverviewValueFlowOverride ? "value_flow" : initialBusinessFactFamily;
|
||||
const bidirectionalBusinessOverviewValueFlowOverride =
|
||||
initialBusinessFactFamily === "business_overview" && hasIncomingVsOutgoingComparisonHint(rawQuestionSignal);
|
||||
const oneSidedValueFlowActionOverride =
|
||||
initialBusinessFactFamily === "value_flow" &&
|
||||
Boolean(oneSidedValueAction) &&
|
||||
(rawAction === "counterparty_value_or_turnover" || rawAction === "counterparty_value");
|
||||
const action =
|
||||
bidirectionalBusinessOverviewValueFlowOverride
|
||||
? "net_value_flow"
|
||||
: oneSidedBusinessOverviewValueFlowOverride || oneSidedValueFlowActionOverride
|
||||
? oneSidedValueAction!
|
||||
: rawAction;
|
||||
const businessFactFamily =
|
||||
oneSidedBusinessOverviewValueFlowOverride || bidirectionalBusinessOverviewValueFlowOverride
|
||||
? "value_flow"
|
||||
: initialBusinessFactFamily;
|
||||
const aggregationNeed = aggregationNeedFor(aggregationAxis);
|
||||
const comparisonNeed = comparisonNeedFor(action);
|
||||
const organizationScopedValueComparison =
|
||||
businessFactFamily === "value_flow" &&
|
||||
comparisonNeed === "incoming_vs_outgoing" &&
|
||||
Boolean(explicitOrganizationScope) &&
|
||||
hasIncomingVsOutgoingComparisonHint(rawQuestionSignal);
|
||||
const subjectCandidates = organizationScopedValueComparison ? [] : rawSubjectCandidates;
|
||||
const effectiveMetadataScopeHint = organizationScopedValueComparison ? null : metadataScopeHint;
|
||||
const allTimeScopeHint = hasAllTimeScopeHint(rawUtterance);
|
||||
const subjectScopedBidirectionalAllTime =
|
||||
businessFactFamily === "value_flow" &&
|
||||
|
|
@ -705,6 +741,15 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
|||
if (oneSidedBusinessOverviewValueFlowOverride) {
|
||||
pushReason(reasonCodes, "data_need_graph_business_overview_one_sided_money_total_routed_to_value_flow");
|
||||
}
|
||||
if (bidirectionalBusinessOverviewValueFlowOverride) {
|
||||
pushReason(reasonCodes, "data_need_graph_business_overview_bidirectional_money_comparison_routed_to_value_flow");
|
||||
}
|
||||
if (organizationScopedValueComparison) {
|
||||
pushReason(reasonCodes, "data_need_graph_organization_scoped_value_comparison_ignores_stale_subject");
|
||||
}
|
||||
if (oneSidedValueFlowActionOverride) {
|
||||
pushReason(reasonCodes, "data_need_graph_value_flow_one_sided_money_total_action_normalized");
|
||||
}
|
||||
if (clarificationGaps.includes("organization")) {
|
||||
pushReason(reasonCodes, "data_need_graph_open_scope_total_needs_organization");
|
||||
}
|
||||
|
|
@ -716,7 +761,7 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
|||
schema_version: ASSISTANT_MCP_DISCOVERY_DATA_NEED_GRAPH_SCHEMA_VERSION,
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: subjectCandidates,
|
||||
metadata_scope_hint: metadataScopeHint,
|
||||
metadata_scope_hint: effectiveMetadataScopeHint,
|
||||
subject_resolution_optional: subjectResolutionOptional || undefined,
|
||||
business_fact_family: businessFactFamily,
|
||||
action_family: toNonEmptyString(action),
|
||||
|
|
|
|||
|
|
@ -752,7 +752,10 @@ function scoreExplicitEntityCandidate(raw: string, cleaned: string, index: numbe
|
|||
}
|
||||
|
||||
function firstEntityCandidate(planner: AssistantMcpDiscoveryPlannerContract): string | null {
|
||||
const candidates = planner.discovery_plan.turn_meaning_ref?.explicit_entity_candidates ?? [];
|
||||
const graphCandidates = planner.data_need_graph?.subject_candidates;
|
||||
const candidates = Array.isArray(graphCandidates)
|
||||
? graphCandidates
|
||||
: planner.discovery_plan.turn_meaning_ref?.explicit_entity_candidates ?? [];
|
||||
let best: { text: string; score: number } | null = null;
|
||||
for (let index = 0; index < candidates.length; index += 1) {
|
||||
const candidate = candidates[index];
|
||||
|
|
@ -1296,10 +1299,33 @@ function isEntityResolutionPilotEligible(planner: AssistantMcpDiscoveryPlannerCo
|
|||
);
|
||||
}
|
||||
|
||||
function normalizeMetadataScopeHint(value: unknown): string | null {
|
||||
const text = toNonEmptyString(value);
|
||||
if (!text) {
|
||||
return null;
|
||||
}
|
||||
const normalized = text
|
||||
.replace(/[«»"']/gu, "")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
const comparable = normalized.toLowerCase().replace(/ё/g, "е");
|
||||
if (/^контрагент(?:ам|ами|ах|ов|у|а|ом|е|ы)?$/iu.test(comparable)) {
|
||||
return "контрагент";
|
||||
}
|
||||
if (/^(?:справочник\.)?контрагенты$/iu.test(comparable)) {
|
||||
return "контрагент";
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function metadataScopeForPlanner(planner: AssistantMcpDiscoveryPlannerContract): string | null {
|
||||
const metadataScopeHint = normalizeMetadataScopeHint(planner.discovery_plan.turn_meaning_ref?.metadata_scope_hint);
|
||||
if (metadataScopeHint) {
|
||||
return metadataScopeHint;
|
||||
}
|
||||
const entityCandidate = firstEntityCandidate(planner);
|
||||
if (entityCandidate) {
|
||||
return entityCandidate;
|
||||
return normalizeMetadataScopeHint(entityCandidate) ?? entityCandidate;
|
||||
}
|
||||
if (planner.selected_chain_id === "catalog_drilldown") {
|
||||
const surface = planner.metadata_surface_ref;
|
||||
|
|
@ -1375,7 +1401,9 @@ function valueFlowPilotProfile(planner: AssistantMcpDiscoveryPlannerContract): V
|
|||
const meaning = planner.discovery_plan.turn_meaning_ref;
|
||||
const action = String(meaning?.asked_action_family ?? "").toLowerCase();
|
||||
const unsupported = String(meaning?.unsupported_but_understood_family ?? "").toLowerCase();
|
||||
const combined = `${action} ${unsupported}`;
|
||||
const graphAction = String(planner.data_need_graph?.action_family ?? "").toLowerCase();
|
||||
const graphComparison = String(planner.data_need_graph?.comparison_need ?? "").toLowerCase();
|
||||
const combined = `${action} ${unsupported} ${graphAction} ${graphComparison} ${planner.selected_chain_id}`;
|
||||
if (
|
||||
combined.includes("net_value_flow") ||
|
||||
combined.includes("bidirectional") ||
|
||||
|
|
|
|||
|
|
@ -736,13 +736,19 @@ function businessOverviewInventoryLine(overview: Record<string, unknown>): strin
|
|||
return pieces.length > 0 ? `Склад: ${pieces.join(", ")}.` : null;
|
||||
}
|
||||
|
||||
function bidirectionalNetLabel(direction: unknown): string {
|
||||
function bidirectionalNetLabel(direction: unknown, scopeKind: "counterparty" | "organization" | "generic" = "counterparty"): string {
|
||||
if (direction === "net_outgoing") {
|
||||
if (scopeKind === "organization") {
|
||||
return "нетто в сторону исходящих платежей";
|
||||
}
|
||||
return "нетто в сторону контрагента";
|
||||
}
|
||||
if (direction === "balanced") {
|
||||
return "нетто около нуля";
|
||||
}
|
||||
if (scopeKind === "organization") {
|
||||
return "нетто в сторону входящих платежей";
|
||||
}
|
||||
return "нетто в нашу сторону";
|
||||
}
|
||||
|
||||
|
|
@ -770,6 +776,11 @@ function buildCompactBidirectionalValueFlowReply(
|
|||
|
||||
const counterparty = toNonEmptyString(flow.counterparty);
|
||||
const organizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
|
||||
const scopeKind: "counterparty" | "organization" | "generic" = counterparty
|
||||
? "counterparty"
|
||||
: organizationScope
|
||||
? "organization"
|
||||
: "generic";
|
||||
const subjectLead = counterparty
|
||||
? `по контрагенту ${counterparty}`
|
||||
: organizationScope
|
||||
|
|
@ -777,9 +788,9 @@ function buildCompactBidirectionalValueFlowReply(
|
|||
: "по выбранному контуру";
|
||||
const period = toNonEmptyString(flow.period_scope);
|
||||
const periodText = period ? ` за период ${period}` : " в проверенном окне";
|
||||
const netLabel = bidirectionalNetLabel(flow.net_direction);
|
||||
const netLabel = bidirectionalNetLabel(flow.net_direction, scopeKind);
|
||||
const lines = [
|
||||
`Коротко: ${subjectLead}${periodText} по найденным строкам 1С получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}; расчетное ${netLabel}: ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}.`
|
||||
`${subjectLead}${periodText} по найденным строкам 1С получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}; расчетное ${netLabel}: ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}.`
|
||||
];
|
||||
|
||||
const basis: string[] = [];
|
||||
|
|
@ -790,7 +801,13 @@ function buildCompactBidirectionalValueFlowReply(
|
|||
basis.push("исходящие платежи");
|
||||
}
|
||||
if (basis.length > 0) {
|
||||
lines.push(`Основа: проверенные ${basis.join(" и ")} по этому контрагенту в 1С.`);
|
||||
const basisScope =
|
||||
scopeKind === "counterparty"
|
||||
? "по этому контрагенту"
|
||||
: scopeKind === "organization"
|
||||
? "по этой компании"
|
||||
: "по выбранному контуру";
|
||||
lines.push(`Основа: проверенные ${basis.join(" и ")} ${basisScope} в 1С.`);
|
||||
}
|
||||
if (flow.coverage_limited_by_probe_limit === true) {
|
||||
lines.push("Важно: часть проверки достигла лимита строк, поэтому это проверенный срез найденных движений, а не гарантия полного периода.");
|
||||
|
|
|
|||
|
|
@ -367,6 +367,116 @@ function readTruthAnswerShape(input: ApplyAssistantMcpDiscoveryResponsePolicyInp
|
|||
return toRecordObject(truthAnswerPolicy?.answer_shape);
|
||||
}
|
||||
|
||||
function hasCurrentClarificationRequiredAddressReply(input: ApplyAssistantMcpDiscoveryResponsePolicyInput): boolean {
|
||||
const answerShape =
|
||||
toNonEmptyString(input.addressRuntimeMeta?.answer_shape) ??
|
||||
toNonEmptyString(input.addressRuntimeMeta?.truth_mode) ??
|
||||
toNonEmptyString(readTruthAnswerShape(input)?.answer_shape);
|
||||
if (answerShape === "clarification_required") {
|
||||
return true;
|
||||
}
|
||||
const truthAnswerPolicy = toRecordObject(input.addressRuntimeMeta?.assistant_truth_answer_policy_v1);
|
||||
const truthGate = toRecordObject(truthAnswerPolicy?.truth_gate);
|
||||
const policyAnswerShape = toRecordObject(truthAnswerPolicy?.answer_shape);
|
||||
return (
|
||||
toNonEmptyString(truthGate?.truth_mode) === "clarification_required" ||
|
||||
toNonEmptyString(policyAnswerShape?.answer_shape) === "clarification_required"
|
||||
);
|
||||
}
|
||||
|
||||
function hasDiscoveryClarificationCandidatePriorityOverCurrentClarification(
|
||||
input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
|
||||
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null,
|
||||
candidate: AssistantMcpDiscoveryResponseCandidateContract
|
||||
): boolean {
|
||||
if (!hasCurrentClarificationRequiredAddressReply(input)) {
|
||||
return false;
|
||||
}
|
||||
if (candidate.candidate_status !== "clarification_candidate") {
|
||||
return false;
|
||||
}
|
||||
if (!candidate.eligible_for_future_hot_runtime || !toNonEmptyString(candidate.reply_text)) {
|
||||
return false;
|
||||
}
|
||||
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const graph = readDiscoveryDataNeedGraph(entryPoint);
|
||||
const turnMeaning = readDiscoveryTurnMeaning(entryPoint);
|
||||
const discoveryFamily = toNonEmptyString(graph?.business_fact_family);
|
||||
const askedDomain = toNonEmptyString(turnMeaning?.asked_domain_family);
|
||||
const askedAction =
|
||||
toNonEmptyString(turnMeaning?.asked_action_family) ?? toNonEmptyString(graph?.action_family);
|
||||
const asksForMovements =
|
||||
discoveryFamily === "movement_evidence" || askedDomain === "movements" || askedAction === "list_movements";
|
||||
|
||||
const truthAnswerShape = readTruthAnswerShape(input);
|
||||
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
|
||||
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
|
||||
const capabilityId =
|
||||
toNonEmptyString(input.addressRuntimeMeta?.capability_id) ??
|
||||
toNonEmptyString(input.addressRuntimeMeta?.capability_contract_id) ??
|
||||
toNonEmptyString(truthAnswerShape?.capability_contract_id);
|
||||
const missingFilters = [
|
||||
...readStringArray(input.addressRuntimeMeta?.missing_required_filters),
|
||||
...readStringArray(input.addressRuntimeMeta?.missing_anchors),
|
||||
...readStringArray(toRecordObject(input.addressRuntimeMeta?.capability_binding_contract)?.missing_anchors)
|
||||
];
|
||||
const staleInventoryItemClarification = Boolean(
|
||||
missingFilters.includes("item") &&
|
||||
(detectedIntent?.startsWith("inventory_") ||
|
||||
selectedRecipe?.includes("inventory_") ||
|
||||
capabilityId?.includes("inventory_"))
|
||||
);
|
||||
|
||||
return asksForMovements && staleInventoryItemClarification;
|
||||
}
|
||||
|
||||
function hasGroundedValueFlowCandidatePriorityOverCurrentClarification(
|
||||
input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
|
||||
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null,
|
||||
candidate: AssistantMcpDiscoveryResponseCandidateContract
|
||||
): boolean {
|
||||
if (!hasCurrentClarificationRequiredAddressReply(input)) {
|
||||
return false;
|
||||
}
|
||||
if (candidate.candidate_status !== "ready_for_guarded_use") {
|
||||
return false;
|
||||
}
|
||||
if (!candidate.hot_runtime_wired || !candidate.eligible_for_future_hot_runtime || !toNonEmptyString(candidate.reply_text)) {
|
||||
return false;
|
||||
}
|
||||
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const graph = readDiscoveryDataNeedGraph(entryPoint);
|
||||
const bridge = toRecordObject(entryPoint?.bridge);
|
||||
const pilot = toRecordObject(bridge?.pilot);
|
||||
const handoff = toRecordObject(bridge?.execution_handoff);
|
||||
const routeCandidate = toRecordObject(bridge?.route_candidate);
|
||||
const selectedChainId =
|
||||
toNonEmptyString(routeCandidate?.selected_chain_id) ??
|
||||
toNonEmptyString(bridge?.selected_chain_id) ??
|
||||
toNonEmptyString(pilot?.selected_chain_id);
|
||||
const pilotScope = toNonEmptyString(pilot?.pilot_scope);
|
||||
const actionFamily = toNonEmptyString(graph?.action_family);
|
||||
const comparisonNeed = toNonEmptyString(graph?.comparison_need);
|
||||
const valueFlowComparison = Boolean(
|
||||
selectedChainId === "value_flow_comparison" ||
|
||||
pilotScope === "counterparty_bidirectional_value_flow_query_movements_v1" ||
|
||||
(actionFamily === "net_value_flow" && comparisonNeed === "incoming_vs_outgoing")
|
||||
);
|
||||
const mcpExecutionPerformed = handoff?.mcp_execution_performed === true || pilot?.mcp_execution_performed === true;
|
||||
const guardedBusinessAnswerAllowed =
|
||||
bridge?.user_facing_response_allowed === true &&
|
||||
bridge?.business_fact_answer_allowed === true &&
|
||||
(handoff?.can_use_guarded_response === true || toNonEmptyString(bridge?.bridge_status) === "answer_draft_ready");
|
||||
|
||||
return valueFlowComparison && mcpExecutionPerformed && guardedBusinessAnswerAllowed;
|
||||
}
|
||||
|
||||
function hasEffectivelyFactualAddressReply(input: ApplyAssistantMcpDiscoveryResponsePolicyInput): boolean {
|
||||
if (toNonEmptyString(input.currentReplyType) === "factual") {
|
||||
return true;
|
||||
|
|
@ -971,6 +1081,21 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
|
|||
input,
|
||||
entryPoint
|
||||
);
|
||||
const currentClarificationRequiredAddressReply = hasCurrentClarificationRequiredAddressReply(input);
|
||||
const discoveryClarificationCandidatePriority = hasDiscoveryClarificationCandidatePriorityOverCurrentClarification(
|
||||
input,
|
||||
entryPoint,
|
||||
candidate
|
||||
);
|
||||
const groundedValueFlowCandidatePriority = hasGroundedValueFlowCandidatePriorityOverCurrentClarification(
|
||||
input,
|
||||
entryPoint,
|
||||
candidate
|
||||
);
|
||||
const currentClarificationProtectsCurrentReply =
|
||||
currentClarificationRequiredAddressReply &&
|
||||
!discoveryClarificationCandidatePriority &&
|
||||
!groundedValueFlowCandidatePriority;
|
||||
const exactBankOperationsProtectsCurrent =
|
||||
exactBankOperationsAddressReply &&
|
||||
!semanticConflictWithDiscoveryTurnMeaning &&
|
||||
|
|
@ -1007,6 +1132,21 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
|
|||
if (evidenceLaneConflictWithDiscoveryTurnMeaning) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_evidence_lane_conflict_allows_candidate_override");
|
||||
}
|
||||
if (currentClarificationProtectsCurrentReply) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_current_clarification_required_reply");
|
||||
}
|
||||
if (discoveryClarificationCandidatePriority) {
|
||||
pushReason(
|
||||
reasonCodes,
|
||||
"mcp_discovery_response_policy_clarification_candidate_priority_over_stale_address_clarification"
|
||||
);
|
||||
}
|
||||
if (groundedValueFlowCandidatePriority) {
|
||||
pushReason(
|
||||
reasonCodes,
|
||||
"mcp_discovery_response_policy_grounded_value_flow_candidate_priority_over_current_clarification"
|
||||
);
|
||||
}
|
||||
if (openScopeValueFlowDiscoveryPriority) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_open_scope_value_flow_candidate_priority");
|
||||
}
|
||||
|
|
@ -1102,6 +1242,7 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
|
|||
!exactDocumentListAddressReply &&
|
||||
!inventoryMarginRankingAddressReply &&
|
||||
!exactInventoryChainProtectsCurrent &&
|
||||
!currentClarificationProtectsCurrentReply &&
|
||||
!(deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") &&
|
||||
ALLOWED_CANDIDATE_STATUSES.has(candidate.candidate_status) &&
|
||||
candidate.eligible_for_future_hot_runtime &&
|
||||
|
|
|
|||
|
|
@ -104,10 +104,28 @@ function isReferentialEntityPlaceholder(value: string): boolean {
|
|||
]).has(normalizeRussianComparableText(value));
|
||||
}
|
||||
|
||||
function isNegativeCounterpartyScopePlaceholder(value: string | null): boolean {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
const text = compactLower(repairAddressMojibakeText(value));
|
||||
if (!text) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
/(?:без\s+(?:привязк\p{L}*|конкретн\p{L}*)\s+к\s+контрагент\p{L}*|без\s+контрагент\p{L}*)/iu.test(text) ||
|
||||
/(?:одн\p{L}*\s+организац\p{L}*\s+без\s+привязк\p{L}*)/iu.test(text) ||
|
||||
/(?:organization\s+without\s+(?:counterparty|specific\s+counterparty))/iu.test(text)
|
||||
);
|
||||
}
|
||||
|
||||
function isReferentialOrganizationPlaceholder(value: string | null): boolean {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
if (isNegativeCounterpartyScopePlaceholder(value)) {
|
||||
return true;
|
||||
}
|
||||
return new Set([
|
||||
"эта организация",
|
||||
"этой организации",
|
||||
|
|
@ -180,6 +198,12 @@ function isGarbageSemanticAnchorCandidate(value: string | null): boolean {
|
|||
"этими",
|
||||
"итогу",
|
||||
"итогам",
|
||||
"одной",
|
||||
"одному",
|
||||
"одним",
|
||||
"одна",
|
||||
"один",
|
||||
"одно",
|
||||
"всему",
|
||||
"всей",
|
||||
"всем",
|
||||
|
|
@ -221,6 +245,7 @@ function isGarbageSemanticAnchorCandidate(value: string | null): boolean {
|
|||
return true;
|
||||
}
|
||||
if (
|
||||
isNegativeCounterpartyScopePlaceholder(text) ||
|
||||
/^(?:по\s+)?(?:этим|этими)\s+данн\p{L}*$/iu.test(text) ||
|
||||
/^(?:и\s+)?кто\s+(?:главн\p{L}*|основн\p{L}*|крупн\p{L}*)\s+(?:клиент|покупател|поставщик|контрагент)(?:\s+в)?$/iu.test(
|
||||
text
|
||||
|
|
@ -1294,6 +1319,9 @@ function normalizeLooseOrganizationAlias(value: string | null): string | null {
|
|||
if (!text) {
|
||||
return null;
|
||||
}
|
||||
if (isNegativeCounterpartyScopePlaceholder(text)) {
|
||||
return null;
|
||||
}
|
||||
const normalized = text
|
||||
.replace(/^[«"'“”]+|[»"'“”]+$/gu, "")
|
||||
.replace(/\s+/g, " ")
|
||||
|
|
|
|||
|
|
@ -117,6 +117,28 @@ describe("assistant MCP discovery answer adapter", () => {
|
|||
expect(draft.must_not_claim).toContain("Do not claim a confirmed business fact when confirmed_facts is empty.");
|
||||
});
|
||||
|
||||
it("states zero rubles for checked payout searches with no matched rows", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
turnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "payout",
|
||||
explicit_entity_candidates: ["Группа СВК"],
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "counterparty_payouts_or_outflow"
|
||||
}
|
||||
});
|
||||
const pilot = await executeAssistantMcpDiscoveryPilot(planner, buildDeps([]));
|
||||
|
||||
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
|
||||
const answerText = [draft.headline, ...draft.confirmed_lines, ...draft.unknown_lines].join("\n");
|
||||
|
||||
expect(draft.answer_mode).toBe("checked_sources_only");
|
||||
expect(answerText).toContain("0 руб.");
|
||||
expect(answerText).toContain("исходящих платежей/списаний не найдено");
|
||||
expect(answerText).toContain("Группа СВК");
|
||||
expect(answerText).toContain("2020");
|
||||
});
|
||||
|
||||
it("turns generic document evidence into a bounded document answer draft", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
turnMeaning: {
|
||||
|
|
|
|||
|
|
@ -367,6 +367,42 @@ describe("assistant MCP discovery data need graph", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("routes broad-overview incoming-vs-outgoing money comparison to value-flow comparison", () => {
|
||||
const result = buildAssistantMcpDiscoveryDataNeedGraph({
|
||||
semanticDataNeed: "business overview evidence with bounded analyst interpretation",
|
||||
rawUtterance:
|
||||
"Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
|
||||
turnMeaning: {
|
||||
asked_domain_family: "business_overview",
|
||||
asked_action_family: "broad_evaluation",
|
||||
unsupported_but_understood_family: "broad_business_evaluation",
|
||||
explicit_entity_candidates: ["Группа СВК", "ООО Альтернатива Плюс больше"],
|
||||
metadata_scope_hint: "Группа СВК",
|
||||
explicit_date_scope: "2020",
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс"
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.business_fact_family).toBe("value_flow");
|
||||
expect(result.subject_candidates).toEqual([]);
|
||||
expect(result.metadata_scope_hint).toBeNull();
|
||||
expect(result.action_family).toBe("net_value_flow");
|
||||
expect(result.comparison_need).toBe("incoming_vs_outgoing");
|
||||
expect(result.clarification_gaps).toEqual([]);
|
||||
expect(result.decomposition_candidates).toEqual([
|
||||
"collect_incoming_movements",
|
||||
"collect_outgoing_movements",
|
||||
"probe_coverage"
|
||||
]);
|
||||
expect(result.reason_codes).toContain(
|
||||
"data_need_graph_business_overview_bidirectional_money_comparison_routed_to_value_flow"
|
||||
);
|
||||
expect(result.reason_codes).toContain(
|
||||
"data_need_graph_organization_scoped_value_comparison_ignores_stale_subject"
|
||||
);
|
||||
expect(result.reason_codes).toContain("data_need_graph_comparison_incoming_vs_outgoing");
|
||||
});
|
||||
|
||||
it("treats a generic incoming total as an understood open-scope ask that still needs organization", () => {
|
||||
const result = buildAssistantMcpDiscoveryDataNeedGraph({
|
||||
semanticDataNeed: "counterparty value-flow evidence",
|
||||
|
|
|
|||
|
|
@ -1170,6 +1170,41 @@ describe("assistant MCP discovery pilot executor", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("prefers normalized metadata scope hint over inflected entity candidate", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
turnMeaning: {
|
||||
asked_domain_family: "metadata",
|
||||
asked_action_family: "inspect_catalog",
|
||||
explicit_entity_candidates: ["контрагентам", "контрагент"],
|
||||
metadata_scope_hint: "контрагент",
|
||||
unsupported_but_understood_family: "1c_metadata_surface"
|
||||
}
|
||||
});
|
||||
const deps = buildMetadataDeps([
|
||||
{
|
||||
FullName: "Справочник.Контрагенты",
|
||||
MetaType: "Справочник",
|
||||
attributes: [{ Name: "Наименование" }, { Name: "ИНН" }]
|
||||
}
|
||||
]);
|
||||
|
||||
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
|
||||
|
||||
expect(result.evidence.evidence_status).toBe("confirmed");
|
||||
expect(result.derived_metadata_surface).toMatchObject({
|
||||
metadata_scope: "контрагент",
|
||||
requested_meta_types: ["Справочник"],
|
||||
matched_rows: 1,
|
||||
available_entity_sets: ["Справочник"],
|
||||
matched_objects: ["Справочник.Контрагенты"]
|
||||
});
|
||||
expect(deps.executeAddressMcpMetadata).toHaveBeenCalledTimes(1);
|
||||
expect(deps.executeAddressMcpMetadata.mock.calls[0]?.[0]).toMatchObject({
|
||||
meta_type: ["Справочник"],
|
||||
name_mask: "контрагент"
|
||||
});
|
||||
});
|
||||
|
||||
it("executes catalog drilldown through a narrowed metadata probe seeded from the confirmed surface object", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
metadataSurface: {
|
||||
|
|
@ -1750,6 +1785,75 @@ describe("assistant MCP discovery pilot executor", () => {
|
|||
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("executes organization-scoped value-flow comparison without stale counterparty subject", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: [],
|
||||
metadata_scope_hint: null,
|
||||
business_fact_family: "value_flow",
|
||||
action_family: "net_value_flow",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "explicit_period",
|
||||
comparison_need: "incoming_vs_outgoing",
|
||||
ranking_need: null,
|
||||
proof_expectation: "coverage_checked_fact",
|
||||
clarification_gaps: [],
|
||||
decomposition_candidates: ["collect_incoming_movements", "collect_outgoing_movements", "probe_coverage"],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: [
|
||||
"data_need_graph_built",
|
||||
"data_need_graph_family_value_flow",
|
||||
"data_need_graph_comparison_incoming_vs_outgoing",
|
||||
"data_need_graph_organization_scoped_value_comparison_ignores_stale_subject"
|
||||
]
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "business_overview",
|
||||
asked_action_family: "broad_evaluation",
|
||||
explicit_entity_candidates: ["SVK", "OOO Alternative Plus more"],
|
||||
metadata_scope_hint: "SVK",
|
||||
explicit_organization_scope: "OOO Alternative Plus",
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "broad_business_evaluation"
|
||||
}
|
||||
});
|
||||
const deps = buildSequentialDeps([
|
||||
{
|
||||
rows: [{ Period: "2020-01-15T00:00:00", Amount: 10000, Organization: "OOO Alternative Plus" }]
|
||||
},
|
||||
{
|
||||
rows: [{ Period: "2020-03-10T00:00:00", Amount: 4000, Organization: "OOO Alternative Plus" }]
|
||||
}
|
||||
]);
|
||||
|
||||
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
|
||||
|
||||
expect(result.pilot_scope).toBe("counterparty_bidirectional_value_flow_query_movements_v1");
|
||||
expect(result.derived_value_flow).toBeNull();
|
||||
expect(result.derived_bidirectional_value_flow).toMatchObject({
|
||||
counterparty: null,
|
||||
period_scope: "2020",
|
||||
net_amount: 6000,
|
||||
net_direction: "net_incoming",
|
||||
incoming_customer_revenue: {
|
||||
rows_matched: 1,
|
||||
total_amount: 10000
|
||||
},
|
||||
outgoing_supplier_payout: {
|
||||
rows_matched: 1,
|
||||
total_amount: 4000
|
||||
}
|
||||
});
|
||||
expect(result.reason_codes).toContain("pilot_bidirectional_value_flow_recipes_selected");
|
||||
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(2);
|
||||
const serializedCalls = deps.executeAddressMcpQuery.mock.calls
|
||||
.map((call) => JSON.stringify(call[0]))
|
||||
.join("\n");
|
||||
expect(serializedCalls).not.toContain("SVK");
|
||||
});
|
||||
|
||||
it("prefers clean counterparty candidates for bidirectional net value-flow probes", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
turnMeaning: {
|
||||
|
|
|
|||
|
|
@ -1280,6 +1280,10 @@ describe("assistant MCP discovery response candidate", () => {
|
|||
);
|
||||
|
||||
expect(candidate.reply_text?.split("\n")[0]).toContain("по компании ООО Альтернатива Плюс за период 2020");
|
||||
expect(candidate.reply_text).toContain("Основа: проверенные входящие платежи и исходящие платежи по этой компании в 1С.");
|
||||
expect(candidate.reply_text).toContain("нетто в сторону входящих платежей: 3 865 501,50 руб.");
|
||||
expect(candidate.reply_text).not.toContain("Коротко:");
|
||||
expect(candidate.reply_text).not.toContain("по этому контрагенту");
|
||||
expect(candidate.reply_text).not.toContain("по контрагенту запрошенному контрагенту");
|
||||
});
|
||||
|
||||
|
|
@ -1918,7 +1922,7 @@ describe("assistant MCP discovery response candidate", () => {
|
|||
);
|
||||
|
||||
const firstLine = candidate.reply_text?.split("\n")[0] ?? "";
|
||||
expect(firstLine).toContain("Коротко:");
|
||||
expect(firstLine).not.toContain("Коротко:");
|
||||
expect(firstLine).toContain("SVK");
|
||||
expect(firstLine).toContain("получили 12 500,50 руб.");
|
||||
expect(firstLine).toContain("заплатили 4 000 руб.");
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ describe("assistant MCP discovery response policy", () => {
|
|||
mcpDiscoveryRuntimeEntryPoint: entryPoint({
|
||||
bridge: {
|
||||
bridge_status: "answer_draft_ready",
|
||||
hot_runtime_wired: true,
|
||||
user_facing_response_allowed: true,
|
||||
business_fact_answer_allowed: true,
|
||||
requires_user_clarification: false,
|
||||
|
|
@ -79,6 +80,56 @@ describe("assistant MCP discovery response policy", () => {
|
|||
expect(result.reply_text).not.toContain("Коротко:");
|
||||
});
|
||||
|
||||
it("keeps current organization clarification over a checked-sources value-flow candidate", () => {
|
||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||
currentReply:
|
||||
"Нужно уточнить организацию, чтобы не смешивать компании в одном ответе.\nСейчас в доступном контуре вижу такие организации:\n- ООО Альтернатива Плюс\n- ООО Ромашка",
|
||||
currentReplySource: "address_query_runtime_v1",
|
||||
currentReplyType: "partial_coverage",
|
||||
addressRuntimeMeta: {
|
||||
truth_mode: "clarification_required",
|
||||
answer_shape: "clarification_required",
|
||||
answer_shape_contract: {
|
||||
answer_shape: "clarification_required",
|
||||
reply_type: "partial_coverage"
|
||||
},
|
||||
assistant_mcp_discovery_entry_point_v1: entryPoint({
|
||||
turn_input: {
|
||||
adapter_status: "ready",
|
||||
should_run_discovery: true,
|
||||
data_need_graph: {
|
||||
business_fact_family: "value_flow",
|
||||
subject_candidates: [],
|
||||
clarification_gaps: ["organization"],
|
||||
reason_codes: ["data_need_graph_open_scope_total_needs_organization"]
|
||||
}
|
||||
},
|
||||
bridge: {
|
||||
bridge_status: "checked_sources_only",
|
||||
user_facing_response_allowed: true,
|
||||
business_fact_answer_allowed: false,
|
||||
requires_user_clarification: true,
|
||||
answer_draft: {
|
||||
answer_mode: "checked_sources_only",
|
||||
headline: "В проверенном срезе 1С входящих денежных поступлений не найдено.",
|
||||
confirmed_lines: ["В проверенном срезе 1С входящих денежных поступлений не найдено."],
|
||||
inference_lines: [],
|
||||
unknown_lines: [],
|
||||
limitation_lines: [],
|
||||
next_step_line: null
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.applied).toBe(false);
|
||||
expect(result.reply_text).toContain("Нужно уточнить организацию");
|
||||
expect(result.reply_text).toContain("ООО Альтернатива Плюс");
|
||||
expect(result.reply_text).not.toContain("не найдено");
|
||||
expect(result.reason_codes).toContain("mcp_discovery_response_policy_keep_current_clarification_required_reply");
|
||||
});
|
||||
|
||||
it("keeps the current reply when the turn is not an unsupported boundary", () => {
|
||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||
currentReply: "regular chat",
|
||||
|
|
@ -236,6 +287,7 @@ describe("assistant MCP discovery response policy", () => {
|
|||
},
|
||||
bridge: {
|
||||
bridge_status: "answer_draft_ready",
|
||||
hot_runtime_wired: true,
|
||||
user_facing_response_allowed: true,
|
||||
business_fact_answer_allowed: true,
|
||||
requires_user_clarification: false,
|
||||
|
|
@ -341,6 +393,68 @@ describe("assistant MCP discovery response policy", () => {
|
|||
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_exact_matched_factual_address_reply");
|
||||
});
|
||||
|
||||
it("uses a movement clarification candidate over a stale inventory-item clarification", () => {
|
||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||
currentReply: "Need item before I can continue.",
|
||||
currentReplySource: "address_query_runtime_v1",
|
||||
currentReplyType: "partial_coverage",
|
||||
addressRuntimeMeta: {
|
||||
detected_intent: "inventory_purchase_documents_for_item",
|
||||
selected_recipe: "address_inventory_purchase_documents_for_item_v1",
|
||||
capability_id: "inventory_inventory_purchase_documents_for_item",
|
||||
truth_mode: "clarification_required",
|
||||
answer_shape: "clarification_required",
|
||||
missing_required_filters: ["item"],
|
||||
answer_shape_contract: {
|
||||
answer_shape: "clarification_required",
|
||||
reply_type: "partial_coverage",
|
||||
capability_contract_id: "inventory_inventory_purchase_documents_for_item"
|
||||
},
|
||||
assistant_mcp_discovery_entry_point_v1: entryPoint({
|
||||
turn_input: {
|
||||
adapter_status: "ready",
|
||||
should_run_discovery: true,
|
||||
turn_meaning_ref: {
|
||||
asked_domain_family: "movements",
|
||||
asked_action_family: "list_movements",
|
||||
explicit_entity_candidates: ["Group SVK"],
|
||||
unsupported_but_understood_family: "movement_evidence"
|
||||
},
|
||||
data_need_graph: {
|
||||
business_fact_family: "movement_evidence",
|
||||
action_family: "list_movements",
|
||||
clarification_gaps: ["period"]
|
||||
}
|
||||
},
|
||||
bridge: {
|
||||
bridge_status: "needs_clarification",
|
||||
user_facing_response_allowed: true,
|
||||
business_fact_answer_allowed: false,
|
||||
requires_user_clarification: true,
|
||||
answer_draft: {
|
||||
answer_mode: "needs_clarification",
|
||||
headline: "I can continue by movements for Group SVK, but I need a period.",
|
||||
confirmed_lines: [],
|
||||
inference_lines: [],
|
||||
unknown_lines: ["The period is required before searching movements."],
|
||||
limitation_lines: [],
|
||||
next_step_line: "Specify the period and I will search movements for Group SVK."
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.applied).toBe(true);
|
||||
expect(result.decision).toBe("apply_candidate");
|
||||
expect(result.reply_text).toContain("movements for Group SVK");
|
||||
expect(result.reply_text).not.toContain("Need item");
|
||||
expect(result.reason_codes).toContain(
|
||||
"mcp_discovery_response_policy_clarification_candidate_priority_over_stale_address_clarification"
|
||||
);
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_current_clarification_required_reply");
|
||||
});
|
||||
|
||||
it("overrides exact document-list replies for explicit counterparty received-paid-net questions", () => {
|
||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||
currentReply: "Контрагент: Группа СВК. Найдено документов: 19.",
|
||||
|
|
@ -1509,4 +1623,95 @@ describe("assistant MCP discovery response policy", () => {
|
|||
"mcp_discovery_response_policy_keep_broad_business_summary_over_clarification_candidate"
|
||||
);
|
||||
});
|
||||
|
||||
it("lets a grounded bidirectional value-flow candidate override a stale address clarification reply", () => {
|
||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||
currentReply:
|
||||
"Проверка данных за 2020 год по контрагенту «Группа СВК» не удалась — такой точный или аналогичный вариант имени не найден.",
|
||||
currentReplySource: "address_query_runtime_v1",
|
||||
currentReplyType: "partial_coverage",
|
||||
addressRuntimeMeta: {
|
||||
truth_mode: "clarification_required",
|
||||
answer_shape: "clarification_required",
|
||||
detected_intent: "counterparty_population_and_roles",
|
||||
selected_recipe: "address_counterparty_population_roles_v1",
|
||||
assistant_mcp_discovery_entry_point_v1: entryPoint({
|
||||
hot_runtime_wired: true,
|
||||
turn_input: {
|
||||
adapter_status: "ready",
|
||||
should_run_discovery: true,
|
||||
turn_meaning_ref: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "net_value_flow",
|
||||
explicit_entity_candidates: ["Группа СВК"],
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "counterparty_bidirectional_value_flow_or_netting"
|
||||
},
|
||||
data_need_graph: {
|
||||
business_fact_family: "value_flow",
|
||||
action_family: "net_value_flow",
|
||||
comparison_need: "incoming_vs_outgoing",
|
||||
clarification_gaps: []
|
||||
}
|
||||
},
|
||||
bridge: {
|
||||
bridge_status: "answer_draft_ready",
|
||||
hot_runtime_wired: true,
|
||||
user_facing_response_allowed: true,
|
||||
business_fact_answer_allowed: true,
|
||||
requires_user_clarification: false,
|
||||
route_candidate: {
|
||||
selected_chain_id: "value_flow_comparison"
|
||||
},
|
||||
execution_handoff: {
|
||||
mcp_execution_performed: true,
|
||||
can_use_guarded_response: true,
|
||||
must_keep_internal_mechanics_hidden: true
|
||||
},
|
||||
pilot: {
|
||||
pilot_scope: "counterparty_bidirectional_value_flow_query_movements_v1",
|
||||
mcp_execution_performed: true,
|
||||
derived_bidirectional_value_flow: {
|
||||
counterparty: "Группа СВК",
|
||||
period_scope: "2020",
|
||||
net_amount_human_ru: "12 093 465 руб.",
|
||||
net_direction: "net_incoming",
|
||||
coverage_limited_by_probe_limit: false,
|
||||
incoming_customer_revenue: {
|
||||
total_amount_human_ru: "12 093 465 руб."
|
||||
},
|
||||
outgoing_supplier_payout: {
|
||||
total_amount_human_ru: "0 руб."
|
||||
}
|
||||
}
|
||||
},
|
||||
answer_draft: {
|
||||
answer_mode: "confirmed_with_bounded_inference",
|
||||
headline: "Двусторонний денежный поток подтвержден.",
|
||||
confirmed_lines: [
|
||||
"1C bidirectional value-flow rows were checked for counterparty Группа СВК: incoming=found, outgoing=found"
|
||||
],
|
||||
inference_lines: [
|
||||
"Counterparty net value-flow was calculated as incoming confirmed 1C rows minus outgoing confirmed 1C rows"
|
||||
],
|
||||
unknown_lines: ["Full bidirectional value-flow outside the checked period is not proven by this MCP discovery pilot"],
|
||||
limitation_lines: [],
|
||||
next_step_line: null
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.applied).toBe(true);
|
||||
expect(result.decision).toBe("apply_candidate");
|
||||
expect(result.reply_text).toContain("Группа СВК");
|
||||
expect(result.reply_text).toContain("получили 12 093 465 руб.");
|
||||
expect(result.reply_text).toContain("заплатили 0 руб.");
|
||||
expect(result.reply_text).not.toContain("не найден");
|
||||
expect(result.reason_codes).toContain(
|
||||
"mcp_discovery_response_policy_grounded_value_flow_candidate_priority_over_current_clarification"
|
||||
);
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_current_clarification_required_reply");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -88,6 +88,36 @@ describe("assistant MCP discovery turn input adapter", () => {
|
|||
expect(result.turn_meaning_ref?.explicit_organization_scope).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not treat no-counterparty organization scope as a counterparty entity", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage:
|
||||
"Хочу быстрый денежный срез по одной организации без привязки к контрагенту. Сколько вообще входящих денег было за 2020 год?",
|
||||
assistantTurnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "counterparty_value_or_turnover",
|
||||
explicit_entity_candidates: ["одной организации без привязки"],
|
||||
explicit_organization_scope: "без привязки к контрагенту",
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "counterparty_value_or_turnover",
|
||||
stale_replay_forbidden: true
|
||||
},
|
||||
predecomposeContract: {
|
||||
entities: {},
|
||||
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
|
||||
},
|
||||
knownOrganizations: ["ООО Альтернатива Плюс", "ООО Ромашка"]
|
||||
});
|
||||
|
||||
expect(result.adapter_status).toBe("ready");
|
||||
expect(result.data_need_graph?.business_fact_family).toBe("value_flow");
|
||||
expect(result.data_need_graph?.subject_candidates).toEqual([]);
|
||||
expect(result.data_need_graph?.clarification_gaps).toContain("organization");
|
||||
expect(result.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||||
expect(result.turn_meaning_ref?.explicit_organization_scope).toBeUndefined();
|
||||
expect(result.turn_meaning_ref?.explicit_date_scope).toBe("2020");
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_counterparty_from_raw_scope");
|
||||
});
|
||||
|
||||
it("keeps payout wording as outgoing supplier-payout discovery", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage: "сколько мы заплатили Группа СВК за 2020 год?",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,94 @@
|
|||
[
|
||||
{
|
||||
"generation_id": "gen-ag06031252-8c5319",
|
||||
"created_at": "2026-06-03T12:52:51+00:00",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | Phase 83 mixed planner-brain replay for catalog alignment, pivots, and legacy continuity",
|
||||
"count": 20,
|
||||
"domain": "planner_autonomy_consolidation",
|
||||
"questions": [
|
||||
"привет, ты на связи?",
|
||||
"найди в 1С контрагента СВК",
|
||||
"сколько получили по нему за 2020 год",
|
||||
"а теперь сколько заплатили?",
|
||||
"а какое нетто?",
|
||||
"а по документам?",
|
||||
"а по движениям?",
|
||||
"Хочу быстрый денежный срез по одной организации без привязки к контрагенту. Сколько вообще входящих денег было за 2020 год?",
|
||||
"По ООО Альтернатива Плюс.",
|
||||
"Понял, тогда за все время.",
|
||||
"Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
|
||||
"А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?",
|
||||
"И кто больше всего принес денег этой организации в 2020 году?",
|
||||
"А в 2021 году?",
|
||||
"а по Альтернативе Плюс сколько лет активности в базе 1С?",
|
||||
"Как ты оценишь деятельность компании?",
|
||||
"какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?",
|
||||
"какие справочники 1С есть по контрагентам?",
|
||||
"давай дальше",
|
||||
"а чем капибара отличается от утки?"
|
||||
],
|
||||
"generated_by": "codex_agent",
|
||||
"saved_case_set_file": "assistant_autogen_saved_user_sessions_20260603125251_gen-ag06031252-8c5319.json",
|
||||
"context": {
|
||||
"llm_provider": null,
|
||||
"model": null,
|
||||
"assistant_prompt_version": null,
|
||||
"decomposition_prompt_version": null,
|
||||
"prompt_fingerprint": null,
|
||||
"autogen_personality_id": null,
|
||||
"autogen_personality_prompt": null,
|
||||
"source_session_id": null,
|
||||
"saved_session_file": "assistant_saved_session_20260603125251_gen-ag06031252-8c5319.json",
|
||||
"saved_case_set_kind": "agent_semantic_scenario",
|
||||
"agent_run": true,
|
||||
"agent_focus": "Mixed AGENT replay for Planner Autonomy Consolidation. The pack interleaves selected-counterparty catalog-alignment probes, open-organization money flow, ranking, broad-evaluation continuity, metadata drilldown, and off-domain living-chat safety.",
|
||||
"architecture_phase": "turnaround_11",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase83_planner_brain_alignment_mix.json",
|
||||
"scenario_id": "address_truth_harness_phase83_planner_brain_alignment_mix",
|
||||
"semantic_tags": [
|
||||
"alias_grounding",
|
||||
"all_time_followup",
|
||||
"broad_business_evaluation",
|
||||
"broad_eval_bridge_preserved",
|
||||
"catalog_drilldown",
|
||||
"catalog_metadata_surface",
|
||||
"company_activity_lifecycle",
|
||||
"counterparty_catalog_scope",
|
||||
"counterparty_net_cash_flow",
|
||||
"date_carryover",
|
||||
"document_evidence",
|
||||
"document_pivot",
|
||||
"entity_resolution",
|
||||
"followup_anchor",
|
||||
"followup_reuse",
|
||||
"grounded_context_seed",
|
||||
"grounded_summary",
|
||||
"human_answer",
|
||||
"human_dialog",
|
||||
"incoming_value_flow",
|
||||
"mcp_discovery_gate_sanity",
|
||||
"meta_smalltalk",
|
||||
"movement_evidence",
|
||||
"net_value_flow",
|
||||
"neutral_followup",
|
||||
"off_domain_living_chat",
|
||||
"open_scope_total",
|
||||
"organization_clarification",
|
||||
"organization_scope",
|
||||
"payout_switch",
|
||||
"planner_catalog_alignment",
|
||||
"stale_replay_forbidden",
|
||||
"value_flow_comparison",
|
||||
"value_flow_pivot",
|
||||
"value_flow_ranking",
|
||||
"year_switch"
|
||||
],
|
||||
"validation_status": "accepted_live_replay",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\phase83_planner_brain_alignment_mix_20260603_live6",
|
||||
"saved_after_validated_replay": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"generation_id": "gen-ag06031054-1bd955",
|
||||
"created_at": "2026-06-03T10:54:28+00:00",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,377 @@
|
|||
{
|
||||
"saved_at": "2026-06-03T12:52:51+00:00",
|
||||
"generation_id": "gen-ag06031252-8c5319",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | Phase 83 mixed planner-brain replay for catalog alignment, pivots, and legacy continuity",
|
||||
"agent_run": true,
|
||||
"questions": [
|
||||
"привет, ты на связи?",
|
||||
"найди в 1С контрагента СВК",
|
||||
"сколько получили по нему за 2020 год",
|
||||
"а теперь сколько заплатили?",
|
||||
"а какое нетто?",
|
||||
"а по документам?",
|
||||
"а по движениям?",
|
||||
"Хочу быстрый денежный срез по одной организации без привязки к контрагенту. Сколько вообще входящих денег было за 2020 год?",
|
||||
"По ООО Альтернатива Плюс.",
|
||||
"Понял, тогда за все время.",
|
||||
"Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
|
||||
"А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?",
|
||||
"И кто больше всего принес денег этой организации в 2020 году?",
|
||||
"А в 2021 году?",
|
||||
"а по Альтернативе Плюс сколько лет активности в базе 1С?",
|
||||
"Как ты оценишь деятельность компании?",
|
||||
"какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?",
|
||||
"какие справочники 1С есть по контрагентам?",
|
||||
"давай дальше",
|
||||
"а чем капибара отличается от утки?"
|
||||
],
|
||||
"metadata": {
|
||||
"assistant_prompt_version": null,
|
||||
"decomposition_prompt_version": null,
|
||||
"prompt_fingerprint": null,
|
||||
"agent_focus": "Mixed AGENT replay for Planner Autonomy Consolidation. The pack interleaves selected-counterparty catalog-alignment probes, open-organization money flow, ranking, broad-evaluation continuity, metadata drilldown, and off-domain living-chat safety.",
|
||||
"architecture_phase": "turnaround_11",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase83_planner_brain_alignment_mix.json",
|
||||
"scenario_id": "address_truth_harness_phase83_planner_brain_alignment_mix",
|
||||
"semantic_tags": [
|
||||
"alias_grounding",
|
||||
"all_time_followup",
|
||||
"broad_business_evaluation",
|
||||
"broad_eval_bridge_preserved",
|
||||
"catalog_drilldown",
|
||||
"catalog_metadata_surface",
|
||||
"company_activity_lifecycle",
|
||||
"counterparty_catalog_scope",
|
||||
"counterparty_net_cash_flow",
|
||||
"date_carryover",
|
||||
"document_evidence",
|
||||
"document_pivot",
|
||||
"entity_resolution",
|
||||
"followup_anchor",
|
||||
"followup_reuse",
|
||||
"grounded_context_seed",
|
||||
"grounded_summary",
|
||||
"human_answer",
|
||||
"human_dialog",
|
||||
"incoming_value_flow",
|
||||
"mcp_discovery_gate_sanity",
|
||||
"meta_smalltalk",
|
||||
"movement_evidence",
|
||||
"net_value_flow",
|
||||
"neutral_followup",
|
||||
"off_domain_living_chat",
|
||||
"open_scope_total",
|
||||
"organization_clarification",
|
||||
"organization_scope",
|
||||
"payout_switch",
|
||||
"planner_catalog_alignment",
|
||||
"stale_replay_forbidden",
|
||||
"value_flow_comparison",
|
||||
"value_flow_pivot",
|
||||
"value_flow_ranking",
|
||||
"year_switch"
|
||||
],
|
||||
"validation_status": "accepted_live_replay",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\phase83_planner_brain_alignment_mix_20260603_live6",
|
||||
"saved_after_validated_replay": true,
|
||||
"save_gate": {
|
||||
"schema_version": "agent_semantic_save_gate_v1",
|
||||
"validation_status": "accepted_live_replay",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\phase83_planner_brain_alignment_mix_20260603_live6",
|
||||
"final_status": "accepted",
|
||||
"review_overall_status": "pass",
|
||||
"business_overall_status": "pass",
|
||||
"steps_total": 20,
|
||||
"steps_passed": 20,
|
||||
"steps_failed": 0,
|
||||
"steps_with_business_failures": 0,
|
||||
"steps_with_business_warnings": 0,
|
||||
"acceptance_gate_passed": true,
|
||||
"effective_runtime": {
|
||||
"manifest_path": "artifacts\\domain_runs\\phase83_planner_brain_alignment_mix_20260603_live6\\effective_runtime.json",
|
||||
"runner": "domain_truth_harness.run-live",
|
||||
"git_sha": "f24cddc492138745aa896f52d1a6ebee8bab08ab",
|
||||
"backend_url": "http://127.0.0.1:8787",
|
||||
"mcp_proxy_url": "http://127.0.0.1:6003",
|
||||
"llm_provider": "local",
|
||||
"llm_model": "unsloth/qwen3-30b-a3b-instruct-2507",
|
||||
"temperature": 0.8,
|
||||
"max_output_tokens": 900,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"prompt_source": "file",
|
||||
"prompt_hash": "f36e36a5e491cd24511b380e0c0059cb01aea5e2af738fb9f01671cd291735c1",
|
||||
"prompt_registry_status": "pass"
|
||||
},
|
||||
"saved_after_validated_replay": true
|
||||
}
|
||||
},
|
||||
"source_session_id": null,
|
||||
"session": {
|
||||
"session_id": null,
|
||||
"mode": "agent_semantic_run",
|
||||
"items": [
|
||||
{
|
||||
"message_id": "agent-user-001",
|
||||
"role": "user",
|
||||
"text": "привет, ты на связи?",
|
||||
"created_at": "2026-06-03T12:52:51+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-002",
|
||||
"role": "user",
|
||||
"text": "найди в 1С контрагента СВК",
|
||||
"created_at": "2026-06-03T12:52:51+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-003",
|
||||
"role": "user",
|
||||
"text": "сколько получили по нему за 2020 год",
|
||||
"created_at": "2026-06-03T12:52:51+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-004",
|
||||
"role": "user",
|
||||
"text": "а теперь сколько заплатили?",
|
||||
"created_at": "2026-06-03T12:52:51+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-005",
|
||||
"role": "user",
|
||||
"text": "а какое нетто?",
|
||||
"created_at": "2026-06-03T12:52:51+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-006",
|
||||
"role": "user",
|
||||
"text": "а по документам?",
|
||||
"created_at": "2026-06-03T12:52:51+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-007",
|
||||
"role": "user",
|
||||
"text": "а по движениям?",
|
||||
"created_at": "2026-06-03T12:52:51+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-008",
|
||||
"role": "user",
|
||||
"text": "Хочу быстрый денежный срез по одной организации без привязки к контрагенту. Сколько вообще входящих денег было за 2020 год?",
|
||||
"created_at": "2026-06-03T12:52:51+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-009",
|
||||
"role": "user",
|
||||
"text": "По ООО Альтернатива Плюс.",
|
||||
"created_at": "2026-06-03T12:52:51+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-010",
|
||||
"role": "user",
|
||||
"text": "Понял, тогда за все время.",
|
||||
"created_at": "2026-06-03T12:52:51+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-011",
|
||||
"role": "user",
|
||||
"text": "Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
|
||||
"created_at": "2026-06-03T12:52:51+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-012",
|
||||
"role": "user",
|
||||
"text": "А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?",
|
||||
"created_at": "2026-06-03T12:52:51+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-013",
|
||||
"role": "user",
|
||||
"text": "И кто больше всего принес денег этой организации в 2020 году?",
|
||||
"created_at": "2026-06-03T12:52:51+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-014",
|
||||
"role": "user",
|
||||
"text": "А в 2021 году?",
|
||||
"created_at": "2026-06-03T12:52:51+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-015",
|
||||
"role": "user",
|
||||
"text": "а по Альтернативе Плюс сколько лет активности в базе 1С?",
|
||||
"created_at": "2026-06-03T12:52:51+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-016",
|
||||
"role": "user",
|
||||
"text": "Как ты оценишь деятельность компании?",
|
||||
"created_at": "2026-06-03T12:52:51+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-017",
|
||||
"role": "user",
|
||||
"text": "какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?",
|
||||
"created_at": "2026-06-03T12:52:51+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-018",
|
||||
"role": "user",
|
||||
"text": "какие справочники 1С есть по контрагентам?",
|
||||
"created_at": "2026-06-03T12:52:51+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-019",
|
||||
"role": "user",
|
||||
"text": "давай дальше",
|
||||
"created_at": "2026-06-03T12:52:51+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-020",
|
||||
"role": "user",
|
||||
"text": "а чем капибара отличается от утки?",
|
||||
"created_at": "2026-06-03T12:52:51+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
}
|
||||
],
|
||||
"agent_run": true,
|
||||
"metadata": {
|
||||
"assistant_prompt_version": null,
|
||||
"decomposition_prompt_version": null,
|
||||
"prompt_fingerprint": null,
|
||||
"agent_focus": "Mixed AGENT replay for Planner Autonomy Consolidation. The pack interleaves selected-counterparty catalog-alignment probes, open-organization money flow, ranking, broad-evaluation continuity, metadata drilldown, and off-domain living-chat safety.",
|
||||
"architecture_phase": "turnaround_11",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase83_planner_brain_alignment_mix.json",
|
||||
"scenario_id": "address_truth_harness_phase83_planner_brain_alignment_mix",
|
||||
"semantic_tags": [
|
||||
"alias_grounding",
|
||||
"all_time_followup",
|
||||
"broad_business_evaluation",
|
||||
"broad_eval_bridge_preserved",
|
||||
"catalog_drilldown",
|
||||
"catalog_metadata_surface",
|
||||
"company_activity_lifecycle",
|
||||
"counterparty_catalog_scope",
|
||||
"counterparty_net_cash_flow",
|
||||
"date_carryover",
|
||||
"document_evidence",
|
||||
"document_pivot",
|
||||
"entity_resolution",
|
||||
"followup_anchor",
|
||||
"followup_reuse",
|
||||
"grounded_context_seed",
|
||||
"grounded_summary",
|
||||
"human_answer",
|
||||
"human_dialog",
|
||||
"incoming_value_flow",
|
||||
"mcp_discovery_gate_sanity",
|
||||
"meta_smalltalk",
|
||||
"movement_evidence",
|
||||
"net_value_flow",
|
||||
"neutral_followup",
|
||||
"off_domain_living_chat",
|
||||
"open_scope_total",
|
||||
"organization_clarification",
|
||||
"organization_scope",
|
||||
"payout_switch",
|
||||
"planner_catalog_alignment",
|
||||
"stale_replay_forbidden",
|
||||
"value_flow_comparison",
|
||||
"value_flow_pivot",
|
||||
"value_flow_ranking",
|
||||
"year_switch"
|
||||
],
|
||||
"validation_status": "accepted_live_replay",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\phase83_planner_brain_alignment_mix_20260603_live6",
|
||||
"saved_after_validated_replay": true,
|
||||
"save_gate": {
|
||||
"schema_version": "agent_semantic_save_gate_v1",
|
||||
"validation_status": "accepted_live_replay",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\phase83_planner_brain_alignment_mix_20260603_live6",
|
||||
"final_status": "accepted",
|
||||
"review_overall_status": "pass",
|
||||
"business_overall_status": "pass",
|
||||
"steps_total": 20,
|
||||
"steps_passed": 20,
|
||||
"steps_failed": 0,
|
||||
"steps_with_business_failures": 0,
|
||||
"steps_with_business_warnings": 0,
|
||||
"acceptance_gate_passed": true,
|
||||
"effective_runtime": {
|
||||
"manifest_path": "artifacts\\domain_runs\\phase83_planner_brain_alignment_mix_20260603_live6\\effective_runtime.json",
|
||||
"runner": "domain_truth_harness.run-live",
|
||||
"git_sha": "f24cddc492138745aa896f52d1a6ebee8bab08ab",
|
||||
"backend_url": "http://127.0.0.1:8787",
|
||||
"mcp_proxy_url": "http://127.0.0.1:6003",
|
||||
"llm_provider": "local",
|
||||
"llm_model": "unsloth/qwen3-30b-a3b-instruct-2507",
|
||||
"temperature": 0.8,
|
||||
"max_output_tokens": 900,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"prompt_source": "file",
|
||||
"prompt_hash": "f36e36a5e491cd24511b380e0c0059cb01aea5e2af738fb9f01671cd291735c1",
|
||||
"prompt_registry_status": "pass"
|
||||
},
|
||||
"saved_after_validated_replay": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
{
|
||||
"suite_id": "assistant_saved_session_gen-ag06031252-8c5319",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
||||
"generated_at": "2026-06-03T12:52:51+00:00",
|
||||
"generation_id": "gen-ag06031252-8c5319",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | Phase 83 mixed planner-brain replay for catalog alignment, pivots, and legacy continuity",
|
||||
"domain": "planner_autonomy_consolidation",
|
||||
"scenario_count": 1,
|
||||
"case_ids": [
|
||||
"SAVED-001"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "SAVED-001",
|
||||
"scenario_tag": "agent_saved_user_sessions",
|
||||
"title": "AGENT | Phase 83 mixed planner-brain replay for catalog alignment, pivots, and legacy continuity",
|
||||
"question_type": "followup",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "привет, ты на связи?"
|
||||
},
|
||||
{
|
||||
"user_message": "найди в 1С контрагента СВК"
|
||||
},
|
||||
{
|
||||
"user_message": "сколько получили по нему за 2020 год"
|
||||
},
|
||||
{
|
||||
"user_message": "а теперь сколько заплатили?"
|
||||
},
|
||||
{
|
||||
"user_message": "а какое нетто?"
|
||||
},
|
||||
{
|
||||
"user_message": "а по документам?"
|
||||
},
|
||||
{
|
||||
"user_message": "а по движениям?"
|
||||
},
|
||||
{
|
||||
"user_message": "Хочу быстрый денежный срез по одной организации без привязки к контрагенту. Сколько вообще входящих денег было за 2020 год?"
|
||||
},
|
||||
{
|
||||
"user_message": "По ООО Альтернатива Плюс."
|
||||
},
|
||||
{
|
||||
"user_message": "Понял, тогда за все время."
|
||||
},
|
||||
{
|
||||
"user_message": "Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?"
|
||||
},
|
||||
{
|
||||
"user_message": "А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?"
|
||||
},
|
||||
{
|
||||
"user_message": "И кто больше всего принес денег этой организации в 2020 году?"
|
||||
},
|
||||
{
|
||||
"user_message": "А в 2021 году?"
|
||||
},
|
||||
{
|
||||
"user_message": "а по Альтернативе Плюс сколько лет активности в базе 1С?"
|
||||
},
|
||||
{
|
||||
"user_message": "Как ты оценишь деятельность компании?"
|
||||
},
|
||||
{
|
||||
"user_message": "какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?"
|
||||
},
|
||||
{
|
||||
"user_message": "какие справочники 1С есть по контрагентам?"
|
||||
},
|
||||
{
|
||||
"user_message": "давай дальше"
|
||||
},
|
||||
{
|
||||
"user_message": "а чем капибара отличается от утки?"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue