Закрепить 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",
|
"title": "Broad business evaluation becomes grounded summary instead of stale lifecycle dump",
|
||||||
"question": "Как ты оценишь деятельность компании?",
|
"question": "Как ты оценишь деятельность компании?",
|
||||||
"required_answer_patterns_all": [
|
"required_answer_patterns_all": [
|
||||||
"(?i)коротко|оценк|частичн",
|
"(?i)ограниченн|проверенн|не\\s+аудит|не\\s+аудитор",
|
||||||
"(?i)1с|подтвержд",
|
"(?i)1с|подтвержд",
|
||||||
"(?i)денежн|долг|ндс|контрагент|операц"
|
"(?i)денежн|долг|ндс|контрагент|операц"
|
||||||
],
|
],
|
||||||
|
|
@ -522,7 +522,7 @@
|
||||||
"factual_with_explanation"
|
"factual_with_explanation"
|
||||||
],
|
],
|
||||||
"required_answer_patterns_all": [
|
"required_answer_patterns_all": [
|
||||||
"(?i)metadata|метадан",
|
"(?i)схем|метадан|объект",
|
||||||
"(?i)справоч|catalog|directory",
|
"(?i)справоч|catalog|directory",
|
||||||
"(?i)контрагент"
|
"(?i)контрагент"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,7 @@ function valueFlowZeroResultConfirmedLine(pilot) {
|
||||||
const period = explicitDateScope(pilot);
|
const period = explicitDateScope(pilot);
|
||||||
const organizationPart = organization ? ` по организации ${organization}` : "";
|
const organizationPart = organization ? ` по организации ${organization}` : "";
|
||||||
const periodPart = period ? ` за период ${period}` : " в проверенном окне";
|
const periodPart = period ? ` за период ${period}` : " в проверенном окне";
|
||||||
return `В проверенном срезе 1С по контрагенту ${counterparty}${organizationPart}${periodPart} ${valueFlowDirectionLabelRu(pilot)} не найдено.`;
|
return `В проверенном срезе 1С по контрагенту ${counterparty}${organizationPart}${periodPart}: 0 руб.; ${valueFlowDirectionLabelRu(pilot)} не найдено.`;
|
||||||
}
|
}
|
||||||
function valueFlowZeroResultUnknownLine(pilot) {
|
function valueFlowZeroResultUnknownLine(pilot) {
|
||||||
if (!hasExecutedZeroValueFlowRows(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);
|
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) {
|
function timeScopeNeedFor(input) {
|
||||||
if (input.explicitDateScope) {
|
if (input.explicitDateScope) {
|
||||||
return "explicit_period";
|
return "explicit_period";
|
||||||
|
|
@ -420,7 +428,7 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
|
||||||
const explicitOrganizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
|
const explicitOrganizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
|
||||||
const metadataScopeHint = toNonEmptyString(turnMeaning?.metadata_scope_hint);
|
const metadataScopeHint = toNonEmptyString(turnMeaning?.metadata_scope_hint);
|
||||||
const subjectResolutionOptional = turnMeaning?.subject_resolution_optional === true;
|
const subjectResolutionOptional = turnMeaning?.subject_resolution_optional === true;
|
||||||
const subjectCandidates = (turnMeaning?.explicit_entity_candidates ?? [])
|
const rawSubjectCandidates = (turnMeaning?.explicit_entity_candidates ?? [])
|
||||||
.map((item) => toNonEmptyString(item))
|
.map((item) => toNonEmptyString(item))
|
||||||
.filter((item) => Boolean(item));
|
.filter((item) => Boolean(item));
|
||||||
const initialBusinessFactFamily = businessFactFamilyFor({
|
const initialBusinessFactFamily = businessFactFamilyFor({
|
||||||
|
|
@ -434,10 +442,26 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
|
||||||
const oneSidedBusinessOverviewValueFlowOverride = initialBusinessFactFamily === "business_overview" &&
|
const oneSidedBusinessOverviewValueFlowOverride = initialBusinessFactFamily === "business_overview" &&
|
||||||
Boolean(oneSidedValueAction) &&
|
Boolean(oneSidedValueAction) &&
|
||||||
!detailedBusinessOverviewMoneyBreakdownHint;
|
!detailedBusinessOverviewMoneyBreakdownHint;
|
||||||
const action = oneSidedBusinessOverviewValueFlowOverride ? oneSidedValueAction : rawAction;
|
const bidirectionalBusinessOverviewValueFlowOverride = initialBusinessFactFamily === "business_overview" && hasIncomingVsOutgoingComparisonHint(rawQuestionSignal);
|
||||||
const businessFactFamily = oneSidedBusinessOverviewValueFlowOverride ? "value_flow" : initialBusinessFactFamily;
|
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 aggregationNeed = aggregationNeedFor(aggregationAxis);
|
||||||
const comparisonNeed = comparisonNeedFor(action);
|
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 allTimeScopeHint = hasAllTimeScopeHint(rawUtterance);
|
||||||
const subjectScopedBidirectionalAllTime = businessFactFamily === "value_flow" &&
|
const subjectScopedBidirectionalAllTime = businessFactFamily === "value_flow" &&
|
||||||
comparisonNeed === "incoming_vs_outgoing" &&
|
comparisonNeed === "incoming_vs_outgoing" &&
|
||||||
|
|
@ -556,6 +580,15 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
|
||||||
if (oneSidedBusinessOverviewValueFlowOverride) {
|
if (oneSidedBusinessOverviewValueFlowOverride) {
|
||||||
pushReason(reasonCodes, "data_need_graph_business_overview_one_sided_money_total_routed_to_value_flow");
|
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")) {
|
if (clarificationGaps.includes("organization")) {
|
||||||
pushReason(reasonCodes, "data_need_graph_open_scope_total_needs_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,
|
schema_version: exports.ASSISTANT_MCP_DISCOVERY_DATA_NEED_GRAPH_SCHEMA_VERSION,
|
||||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||||
subject_candidates: subjectCandidates,
|
subject_candidates: subjectCandidates,
|
||||||
metadata_scope_hint: metadataScopeHint,
|
metadata_scope_hint: effectiveMetadataScopeHint,
|
||||||
subject_resolution_optional: subjectResolutionOptional || undefined,
|
subject_resolution_optional: subjectResolutionOptional || undefined,
|
||||||
business_fact_family: businessFactFamily,
|
business_fact_family: businessFactFamily,
|
||||||
action_family: toNonEmptyString(action),
|
action_family: toNonEmptyString(action),
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,10 @@ function scoreExplicitEntityCandidate(raw, cleaned, index) {
|
||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
function firstEntityCandidate(planner) {
|
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;
|
let best = null;
|
||||||
for (let index = 0; index < candidates.length; index += 1) {
|
for (let index = 0; index < candidates.length; index += 1) {
|
||||||
const candidate = candidates[index];
|
const candidate = candidates[index];
|
||||||
|
|
@ -582,10 +585,32 @@ function isEntityResolutionPilotEligible(planner) {
|
||||||
combined.includes("entity discovery") ||
|
combined.includes("entity discovery") ||
|
||||||
combined.includes("counterparty search")));
|
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) {
|
function metadataScopeForPlanner(planner) {
|
||||||
|
const metadataScopeHint = normalizeMetadataScopeHint(planner.discovery_plan.turn_meaning_ref?.metadata_scope_hint);
|
||||||
|
if (metadataScopeHint) {
|
||||||
|
return metadataScopeHint;
|
||||||
|
}
|
||||||
const entityCandidate = firstEntityCandidate(planner);
|
const entityCandidate = firstEntityCandidate(planner);
|
||||||
if (entityCandidate) {
|
if (entityCandidate) {
|
||||||
return entityCandidate;
|
return normalizeMetadataScopeHint(entityCandidate) ?? entityCandidate;
|
||||||
}
|
}
|
||||||
if (planner.selected_chain_id === "catalog_drilldown") {
|
if (planner.selected_chain_id === "catalog_drilldown") {
|
||||||
const surface = planner.metadata_surface_ref;
|
const surface = planner.metadata_surface_ref;
|
||||||
|
|
@ -647,7 +672,9 @@ function valueFlowPilotProfile(planner) {
|
||||||
const meaning = planner.discovery_plan.turn_meaning_ref;
|
const meaning = planner.discovery_plan.turn_meaning_ref;
|
||||||
const action = String(meaning?.asked_action_family ?? "").toLowerCase();
|
const action = String(meaning?.asked_action_family ?? "").toLowerCase();
|
||||||
const unsupported = String(meaning?.unsupported_but_understood_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") ||
|
if (combined.includes("net_value_flow") ||
|
||||||
combined.includes("bidirectional") ||
|
combined.includes("bidirectional") ||
|
||||||
combined.includes("netting") ||
|
combined.includes("netting") ||
|
||||||
|
|
|
||||||
|
|
@ -614,13 +614,19 @@ function businessOverviewInventoryLine(overview) {
|
||||||
].filter((item) => Boolean(item));
|
].filter((item) => Boolean(item));
|
||||||
return pieces.length > 0 ? `Склад: ${pieces.join(", ")}.` : null;
|
return pieces.length > 0 ? `Склад: ${pieces.join(", ")}.` : null;
|
||||||
}
|
}
|
||||||
function bidirectionalNetLabel(direction) {
|
function bidirectionalNetLabel(direction, scopeKind = "counterparty") {
|
||||||
if (direction === "net_outgoing") {
|
if (direction === "net_outgoing") {
|
||||||
|
if (scopeKind === "organization") {
|
||||||
|
return "нетто в сторону исходящих платежей";
|
||||||
|
}
|
||||||
return "нетто в сторону контрагента";
|
return "нетто в сторону контрагента";
|
||||||
}
|
}
|
||||||
if (direction === "balanced") {
|
if (direction === "balanced") {
|
||||||
return "нетто около нуля";
|
return "нетто около нуля";
|
||||||
}
|
}
|
||||||
|
if (scopeKind === "organization") {
|
||||||
|
return "нетто в сторону входящих платежей";
|
||||||
|
}
|
||||||
return "нетто в нашу сторону";
|
return "нетто в нашу сторону";
|
||||||
}
|
}
|
||||||
function buildCompactBidirectionalValueFlowReply(entryPoint, draft) {
|
function buildCompactBidirectionalValueFlowReply(entryPoint, draft) {
|
||||||
|
|
@ -642,6 +648,11 @@ function buildCompactBidirectionalValueFlowReply(entryPoint, draft) {
|
||||||
}
|
}
|
||||||
const counterparty = toNonEmptyString(flow.counterparty);
|
const counterparty = toNonEmptyString(flow.counterparty);
|
||||||
const organizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
|
const organizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
|
||||||
|
const scopeKind = counterparty
|
||||||
|
? "counterparty"
|
||||||
|
: organizationScope
|
||||||
|
? "organization"
|
||||||
|
: "generic";
|
||||||
const subjectLead = counterparty
|
const subjectLead = counterparty
|
||||||
? `по контрагенту ${counterparty}`
|
? `по контрагенту ${counterparty}`
|
||||||
: organizationScope
|
: organizationScope
|
||||||
|
|
@ -649,9 +660,9 @@ function buildCompactBidirectionalValueFlowReply(entryPoint, draft) {
|
||||||
: "по выбранному контуру";
|
: "по выбранному контуру";
|
||||||
const period = toNonEmptyString(flow.period_scope);
|
const period = toNonEmptyString(flow.period_scope);
|
||||||
const periodText = period ? ` за период ${period}` : " в проверенном окне";
|
const periodText = period ? ` за период ${period}` : " в проверенном окне";
|
||||||
const netLabel = bidirectionalNetLabel(flow.net_direction);
|
const netLabel = bidirectionalNetLabel(flow.net_direction, scopeKind);
|
||||||
const lines = [
|
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 = [];
|
const basis = [];
|
||||||
if (incomingAmount) {
|
if (incomingAmount) {
|
||||||
|
|
@ -661,7 +672,12 @@ function buildCompactBidirectionalValueFlowReply(entryPoint, draft) {
|
||||||
basis.push("исходящие платежи");
|
basis.push("исходящие платежи");
|
||||||
}
|
}
|
||||||
if (basis.length > 0) {
|
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) {
|
if (flow.coverage_limited_by_probe_limit === true) {
|
||||||
lines.push("Важно: часть проверки достигла лимита строк, поэтому это проверенный срез найденных движений, а не гарантия полного периода.");
|
lines.push("Важно: часть проверки достигла лимита строк, поэтому это проверенный срез найденных движений, а не гарантия полного периода.");
|
||||||
|
|
|
||||||
|
|
@ -247,6 +247,88 @@ function readTruthAnswerShape(input) {
|
||||||
const truthAnswerPolicy = toRecordObject(input.addressRuntimeMeta?.assistant_truth_answer_policy_v1);
|
const truthAnswerPolicy = toRecordObject(input.addressRuntimeMeta?.assistant_truth_answer_policy_v1);
|
||||||
return toRecordObject(truthAnswerPolicy?.answer_shape);
|
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) {
|
function hasEffectivelyFactualAddressReply(input) {
|
||||||
if (toNonEmptyString(input.currentReplyType) === "factual") {
|
if (toNonEmptyString(input.currentReplyType) === "factual") {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -719,6 +801,12 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
|
||||||
const metadataDiscoveryPriority = hasMetadataDiscoveryPriority(input, entryPoint);
|
const metadataDiscoveryPriority = hasMetadataDiscoveryPriority(input, entryPoint);
|
||||||
const valueFlowActionConflictWithDiscoveryTurnMeaning = hasValueFlowActionConflictWithDiscoveryTurnMeaning(input, entryPoint);
|
const valueFlowActionConflictWithDiscoveryTurnMeaning = hasValueFlowActionConflictWithDiscoveryTurnMeaning(input, entryPoint);
|
||||||
const evidenceLaneConflictWithDiscoveryTurnMeaning = hasEvidenceLaneConflictWithDiscoveryTurnMeaning(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 &&
|
const exactBankOperationsProtectsCurrent = exactBankOperationsAddressReply &&
|
||||||
!semanticConflictWithDiscoveryTurnMeaning &&
|
!semanticConflictWithDiscoveryTurnMeaning &&
|
||||||
!valueFlowActionConflictWithDiscoveryTurnMeaning;
|
!valueFlowActionConflictWithDiscoveryTurnMeaning;
|
||||||
|
|
@ -752,6 +840,15 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
|
||||||
if (evidenceLaneConflictWithDiscoveryTurnMeaning) {
|
if (evidenceLaneConflictWithDiscoveryTurnMeaning) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_response_policy_evidence_lane_conflict_allows_candidate_override");
|
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) {
|
if (openScopeValueFlowDiscoveryPriority) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_response_policy_open_scope_value_flow_candidate_priority");
|
pushReason(reasonCodes, "mcp_discovery_response_policy_open_scope_value_flow_candidate_priority");
|
||||||
}
|
}
|
||||||
|
|
@ -827,6 +924,7 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
|
||||||
!exactDocumentListAddressReply &&
|
!exactDocumentListAddressReply &&
|
||||||
!inventoryMarginRankingAddressReply &&
|
!inventoryMarginRankingAddressReply &&
|
||||||
!exactInventoryChainProtectsCurrent &&
|
!exactInventoryChainProtectsCurrent &&
|
||||||
|
!currentClarificationProtectsCurrentReply &&
|
||||||
!(deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") &&
|
!(deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") &&
|
||||||
ALLOWED_CANDIDATE_STATUSES.has(candidate.candidate_status) &&
|
ALLOWED_CANDIDATE_STATUSES.has(candidate.candidate_status) &&
|
||||||
candidate.eligible_for_future_hot_runtime &&
|
candidate.eligible_for_future_hot_runtime &&
|
||||||
|
|
|
||||||
|
|
@ -60,10 +60,25 @@ function isReferentialEntityPlaceholder(value) {
|
||||||
"этом"
|
"этом"
|
||||||
]).has((0, addressTextRepair_1.normalizeRussianComparableText)(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) {
|
function isReferentialOrganizationPlaceholder(value) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (isNegativeCounterpartyScopePlaceholder(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return new Set([
|
return new Set([
|
||||||
"эта организация",
|
"эта организация",
|
||||||
"этой организации",
|
"этой организации",
|
||||||
|
|
@ -129,6 +144,12 @@ function isGarbageSemanticAnchorCandidate(value) {
|
||||||
"этими",
|
"этими",
|
||||||
"итогу",
|
"итогу",
|
||||||
"итогам",
|
"итогам",
|
||||||
|
"одной",
|
||||||
|
"одному",
|
||||||
|
"одним",
|
||||||
|
"одна",
|
||||||
|
"один",
|
||||||
|
"одно",
|
||||||
"всему",
|
"всему",
|
||||||
"всей",
|
"всей",
|
||||||
"всем",
|
"всем",
|
||||||
|
|
@ -168,7 +189,8 @@ function isGarbageSemanticAnchorCandidate(value) {
|
||||||
]).has(compact)) {
|
]).has(compact)) {
|
||||||
return true;
|
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+)?кто\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}*|основн\p{L}*|крупн\p{L}*)\s+(?:клиент|покупател|поставщик|контрагент)(?:\s+в)?$/iu.test(text) ||
|
||||||
/^(?:или\s+)?(?:обычн\p{L}*\s+)?(?:клиент|поставщик|покупател\p{L}*|заказчик|контрагент)(?:\s+или\s+(?:клиент|поставщик|покупател\p{L}*|заказчик|контрагент))?$/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) {
|
if (!text) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (isNegativeCounterpartyScopePlaceholder(text)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const normalized = text
|
const normalized = text
|
||||||
.replace(/^[«"'“”]+|[»"'“”]+$/gu, "")
|
.replace(/^[«"'“”]+|[»"'“”]+$/gu, "")
|
||||||
.replace(/\s+/g, " ")
|
.replace(/\s+/g, " ")
|
||||||
|
|
|
||||||
|
|
@ -293,7 +293,7 @@ function valueFlowZeroResultConfirmedLine(pilot: AssistantMcpDiscoveryPilotExecu
|
||||||
const period = explicitDateScope(pilot);
|
const period = explicitDateScope(pilot);
|
||||||
const organizationPart = organization ? ` по организации ${organization}` : "";
|
const organizationPart = organization ? ` по организации ${organization}` : "";
|
||||||
const periodPart = period ? ` за период ${period}` : " в проверенном окне";
|
const periodPart = period ? ` за период ${period}` : " в проверенном окне";
|
||||||
return `В проверенном срезе 1С по контрагенту ${counterparty}${organizationPart}${periodPart} ${valueFlowDirectionLabelRu(
|
return `В проверенном срезе 1С по контрагенту ${counterparty}${organizationPart}${periodPart}: 0 руб.; ${valueFlowDirectionLabelRu(
|
||||||
pilot
|
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: {
|
function timeScopeNeedFor(input: {
|
||||||
family: string | null;
|
family: string | null;
|
||||||
explicitDateScope: string | null;
|
explicitDateScope: string | null;
|
||||||
|
|
@ -557,7 +572,7 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
||||||
const explicitOrganizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
|
const explicitOrganizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
|
||||||
const metadataScopeHint = toNonEmptyString(turnMeaning?.metadata_scope_hint);
|
const metadataScopeHint = toNonEmptyString(turnMeaning?.metadata_scope_hint);
|
||||||
const subjectResolutionOptional = turnMeaning?.subject_resolution_optional === true;
|
const subjectResolutionOptional = turnMeaning?.subject_resolution_optional === true;
|
||||||
const subjectCandidates = (turnMeaning?.explicit_entity_candidates ?? [])
|
const rawSubjectCandidates = (turnMeaning?.explicit_entity_candidates ?? [])
|
||||||
.map((item) => toNonEmptyString(item))
|
.map((item) => toNonEmptyString(item))
|
||||||
.filter((item): item is string => Boolean(item));
|
.filter((item): item is string => Boolean(item));
|
||||||
const initialBusinessFactFamily = businessFactFamilyFor({
|
const initialBusinessFactFamily = businessFactFamilyFor({
|
||||||
|
|
@ -572,10 +587,31 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
||||||
initialBusinessFactFamily === "business_overview" &&
|
initialBusinessFactFamily === "business_overview" &&
|
||||||
Boolean(oneSidedValueAction) &&
|
Boolean(oneSidedValueAction) &&
|
||||||
!detailedBusinessOverviewMoneyBreakdownHint;
|
!detailedBusinessOverviewMoneyBreakdownHint;
|
||||||
const action = oneSidedBusinessOverviewValueFlowOverride ? oneSidedValueAction! : rawAction;
|
const bidirectionalBusinessOverviewValueFlowOverride =
|
||||||
const businessFactFamily = oneSidedBusinessOverviewValueFlowOverride ? "value_flow" : initialBusinessFactFamily;
|
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 aggregationNeed = aggregationNeedFor(aggregationAxis);
|
||||||
const comparisonNeed = comparisonNeedFor(action);
|
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 allTimeScopeHint = hasAllTimeScopeHint(rawUtterance);
|
||||||
const subjectScopedBidirectionalAllTime =
|
const subjectScopedBidirectionalAllTime =
|
||||||
businessFactFamily === "value_flow" &&
|
businessFactFamily === "value_flow" &&
|
||||||
|
|
@ -705,6 +741,15 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
||||||
if (oneSidedBusinessOverviewValueFlowOverride) {
|
if (oneSidedBusinessOverviewValueFlowOverride) {
|
||||||
pushReason(reasonCodes, "data_need_graph_business_overview_one_sided_money_total_routed_to_value_flow");
|
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")) {
|
if (clarificationGaps.includes("organization")) {
|
||||||
pushReason(reasonCodes, "data_need_graph_open_scope_total_needs_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,
|
schema_version: ASSISTANT_MCP_DISCOVERY_DATA_NEED_GRAPH_SCHEMA_VERSION,
|
||||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||||
subject_candidates: subjectCandidates,
|
subject_candidates: subjectCandidates,
|
||||||
metadata_scope_hint: metadataScopeHint,
|
metadata_scope_hint: effectiveMetadataScopeHint,
|
||||||
subject_resolution_optional: subjectResolutionOptional || undefined,
|
subject_resolution_optional: subjectResolutionOptional || undefined,
|
||||||
business_fact_family: businessFactFamily,
|
business_fact_family: businessFactFamily,
|
||||||
action_family: toNonEmptyString(action),
|
action_family: toNonEmptyString(action),
|
||||||
|
|
|
||||||
|
|
@ -752,7 +752,10 @@ function scoreExplicitEntityCandidate(raw: string, cleaned: string, index: numbe
|
||||||
}
|
}
|
||||||
|
|
||||||
function firstEntityCandidate(planner: AssistantMcpDiscoveryPlannerContract): string | null {
|
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;
|
let best: { text: string; score: number } | null = null;
|
||||||
for (let index = 0; index < candidates.length; index += 1) {
|
for (let index = 0; index < candidates.length; index += 1) {
|
||||||
const candidate = candidates[index];
|
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 {
|
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);
|
const entityCandidate = firstEntityCandidate(planner);
|
||||||
if (entityCandidate) {
|
if (entityCandidate) {
|
||||||
return entityCandidate;
|
return normalizeMetadataScopeHint(entityCandidate) ?? entityCandidate;
|
||||||
}
|
}
|
||||||
if (planner.selected_chain_id === "catalog_drilldown") {
|
if (planner.selected_chain_id === "catalog_drilldown") {
|
||||||
const surface = planner.metadata_surface_ref;
|
const surface = planner.metadata_surface_ref;
|
||||||
|
|
@ -1375,7 +1401,9 @@ function valueFlowPilotProfile(planner: AssistantMcpDiscoveryPlannerContract): V
|
||||||
const meaning = planner.discovery_plan.turn_meaning_ref;
|
const meaning = planner.discovery_plan.turn_meaning_ref;
|
||||||
const action = String(meaning?.asked_action_family ?? "").toLowerCase();
|
const action = String(meaning?.asked_action_family ?? "").toLowerCase();
|
||||||
const unsupported = String(meaning?.unsupported_but_understood_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 (
|
if (
|
||||||
combined.includes("net_value_flow") ||
|
combined.includes("net_value_flow") ||
|
||||||
combined.includes("bidirectional") ||
|
combined.includes("bidirectional") ||
|
||||||
|
|
|
||||||
|
|
@ -736,13 +736,19 @@ function businessOverviewInventoryLine(overview: Record<string, unknown>): strin
|
||||||
return pieces.length > 0 ? `Склад: ${pieces.join(", ")}.` : null;
|
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 (direction === "net_outgoing") {
|
||||||
|
if (scopeKind === "organization") {
|
||||||
|
return "нетто в сторону исходящих платежей";
|
||||||
|
}
|
||||||
return "нетто в сторону контрагента";
|
return "нетто в сторону контрагента";
|
||||||
}
|
}
|
||||||
if (direction === "balanced") {
|
if (direction === "balanced") {
|
||||||
return "нетто около нуля";
|
return "нетто около нуля";
|
||||||
}
|
}
|
||||||
|
if (scopeKind === "organization") {
|
||||||
|
return "нетто в сторону входящих платежей";
|
||||||
|
}
|
||||||
return "нетто в нашу сторону";
|
return "нетто в нашу сторону";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -770,6 +776,11 @@ function buildCompactBidirectionalValueFlowReply(
|
||||||
|
|
||||||
const counterparty = toNonEmptyString(flow.counterparty);
|
const counterparty = toNonEmptyString(flow.counterparty);
|
||||||
const organizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
|
const organizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
|
||||||
|
const scopeKind: "counterparty" | "organization" | "generic" = counterparty
|
||||||
|
? "counterparty"
|
||||||
|
: organizationScope
|
||||||
|
? "organization"
|
||||||
|
: "generic";
|
||||||
const subjectLead = counterparty
|
const subjectLead = counterparty
|
||||||
? `по контрагенту ${counterparty}`
|
? `по контрагенту ${counterparty}`
|
||||||
: organizationScope
|
: organizationScope
|
||||||
|
|
@ -777,9 +788,9 @@ function buildCompactBidirectionalValueFlowReply(
|
||||||
: "по выбранному контуру";
|
: "по выбранному контуру";
|
||||||
const period = toNonEmptyString(flow.period_scope);
|
const period = toNonEmptyString(flow.period_scope);
|
||||||
const periodText = period ? ` за период ${period}` : " в проверенном окне";
|
const periodText = period ? ` за период ${period}` : " в проверенном окне";
|
||||||
const netLabel = bidirectionalNetLabel(flow.net_direction);
|
const netLabel = bidirectionalNetLabel(flow.net_direction, scopeKind);
|
||||||
const lines = [
|
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[] = [];
|
const basis: string[] = [];
|
||||||
|
|
@ -790,7 +801,13 @@ function buildCompactBidirectionalValueFlowReply(
|
||||||
basis.push("исходящие платежи");
|
basis.push("исходящие платежи");
|
||||||
}
|
}
|
||||||
if (basis.length > 0) {
|
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) {
|
if (flow.coverage_limited_by_probe_limit === true) {
|
||||||
lines.push("Важно: часть проверки достигла лимита строк, поэтому это проверенный срез найденных движений, а не гарантия полного периода.");
|
lines.push("Важно: часть проверки достигла лимита строк, поэтому это проверенный срез найденных движений, а не гарантия полного периода.");
|
||||||
|
|
|
||||||
|
|
@ -367,6 +367,116 @@ function readTruthAnswerShape(input: ApplyAssistantMcpDiscoveryResponsePolicyInp
|
||||||
return toRecordObject(truthAnswerPolicy?.answer_shape);
|
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 {
|
function hasEffectivelyFactualAddressReply(input: ApplyAssistantMcpDiscoveryResponsePolicyInput): boolean {
|
||||||
if (toNonEmptyString(input.currentReplyType) === "factual") {
|
if (toNonEmptyString(input.currentReplyType) === "factual") {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -971,6 +1081,21 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
|
||||||
input,
|
input,
|
||||||
entryPoint
|
entryPoint
|
||||||
);
|
);
|
||||||
|
const currentClarificationRequiredAddressReply = hasCurrentClarificationRequiredAddressReply(input);
|
||||||
|
const discoveryClarificationCandidatePriority = hasDiscoveryClarificationCandidatePriorityOverCurrentClarification(
|
||||||
|
input,
|
||||||
|
entryPoint,
|
||||||
|
candidate
|
||||||
|
);
|
||||||
|
const groundedValueFlowCandidatePriority = hasGroundedValueFlowCandidatePriorityOverCurrentClarification(
|
||||||
|
input,
|
||||||
|
entryPoint,
|
||||||
|
candidate
|
||||||
|
);
|
||||||
|
const currentClarificationProtectsCurrentReply =
|
||||||
|
currentClarificationRequiredAddressReply &&
|
||||||
|
!discoveryClarificationCandidatePriority &&
|
||||||
|
!groundedValueFlowCandidatePriority;
|
||||||
const exactBankOperationsProtectsCurrent =
|
const exactBankOperationsProtectsCurrent =
|
||||||
exactBankOperationsAddressReply &&
|
exactBankOperationsAddressReply &&
|
||||||
!semanticConflictWithDiscoveryTurnMeaning &&
|
!semanticConflictWithDiscoveryTurnMeaning &&
|
||||||
|
|
@ -1007,6 +1132,21 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
|
||||||
if (evidenceLaneConflictWithDiscoveryTurnMeaning) {
|
if (evidenceLaneConflictWithDiscoveryTurnMeaning) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_response_policy_evidence_lane_conflict_allows_candidate_override");
|
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) {
|
if (openScopeValueFlowDiscoveryPriority) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_response_policy_open_scope_value_flow_candidate_priority");
|
pushReason(reasonCodes, "mcp_discovery_response_policy_open_scope_value_flow_candidate_priority");
|
||||||
}
|
}
|
||||||
|
|
@ -1102,6 +1242,7 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
|
||||||
!exactDocumentListAddressReply &&
|
!exactDocumentListAddressReply &&
|
||||||
!inventoryMarginRankingAddressReply &&
|
!inventoryMarginRankingAddressReply &&
|
||||||
!exactInventoryChainProtectsCurrent &&
|
!exactInventoryChainProtectsCurrent &&
|
||||||
|
!currentClarificationProtectsCurrentReply &&
|
||||||
!(deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") &&
|
!(deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") &&
|
||||||
ALLOWED_CANDIDATE_STATUSES.has(candidate.candidate_status) &&
|
ALLOWED_CANDIDATE_STATUSES.has(candidate.candidate_status) &&
|
||||||
candidate.eligible_for_future_hot_runtime &&
|
candidate.eligible_for_future_hot_runtime &&
|
||||||
|
|
|
||||||
|
|
@ -104,10 +104,28 @@ function isReferentialEntityPlaceholder(value: string): boolean {
|
||||||
]).has(normalizeRussianComparableText(value));
|
]).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 {
|
function isReferentialOrganizationPlaceholder(value: string | null): boolean {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (isNegativeCounterpartyScopePlaceholder(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return new Set([
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
|
isNegativeCounterpartyScopePlaceholder(text) ||
|
||||||
/^(?:по\s+)?(?:этим|этими)\s+данн\p{L}*$/iu.test(text) ||
|
/^(?:по\s+)?(?:этим|этими)\s+данн\p{L}*$/iu.test(text) ||
|
||||||
/^(?:и\s+)?кто\s+(?:главн\p{L}*|основн\p{L}*|крупн\p{L}*)\s+(?:клиент|покупател|поставщик|контрагент)(?:\s+в)?$/iu.test(
|
/^(?:и\s+)?кто\s+(?:главн\p{L}*|основн\p{L}*|крупн\p{L}*)\s+(?:клиент|покупател|поставщик|контрагент)(?:\s+в)?$/iu.test(
|
||||||
text
|
text
|
||||||
|
|
@ -1294,6 +1319,9 @@ function normalizeLooseOrganizationAlias(value: string | null): string | null {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (isNegativeCounterpartyScopePlaceholder(text)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const normalized = text
|
const normalized = text
|
||||||
.replace(/^[«"'“”]+|[»"'“”]+$/gu, "")
|
.replace(/^[«"'“”]+|[»"'“”]+$/gu, "")
|
||||||
.replace(/\s+/g, " ")
|
.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.");
|
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 () => {
|
it("turns generic document evidence into a bounded document answer draft", async () => {
|
||||||
const planner = planAssistantMcpDiscovery({
|
const planner = planAssistantMcpDiscovery({
|
||||||
turnMeaning: {
|
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", () => {
|
it("treats a generic incoming total as an understood open-scope ask that still needs organization", () => {
|
||||||
const result = buildAssistantMcpDiscoveryDataNeedGraph({
|
const result = buildAssistantMcpDiscoveryDataNeedGraph({
|
||||||
semanticDataNeed: "counterparty value-flow evidence",
|
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 () => {
|
it("executes catalog drilldown through a narrowed metadata probe seeded from the confirmed surface object", async () => {
|
||||||
const planner = planAssistantMcpDiscovery({
|
const planner = planAssistantMcpDiscovery({
|
||||||
metadataSurface: {
|
metadataSurface: {
|
||||||
|
|
@ -1750,6 +1785,75 @@ describe("assistant MCP discovery pilot executor", () => {
|
||||||
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(2);
|
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 () => {
|
it("prefers clean counterparty candidates for bidirectional net value-flow probes", async () => {
|
||||||
const planner = planAssistantMcpDiscovery({
|
const planner = planAssistantMcpDiscovery({
|
||||||
turnMeaning: {
|
turnMeaning: {
|
||||||
|
|
|
||||||
|
|
@ -1280,6 +1280,10 @@ describe("assistant MCP discovery response candidate", () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(candidate.reply_text?.split("\n")[0]).toContain("по компании ООО Альтернатива Плюс за период 2020");
|
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("по контрагенту запрошенному контрагенту");
|
expect(candidate.reply_text).not.toContain("по контрагенту запрошенному контрагенту");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1918,7 +1922,7 @@ describe("assistant MCP discovery response candidate", () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const firstLine = candidate.reply_text?.split("\n")[0] ?? "";
|
const firstLine = candidate.reply_text?.split("\n")[0] ?? "";
|
||||||
expect(firstLine).toContain("Коротко:");
|
expect(firstLine).not.toContain("Коротко:");
|
||||||
expect(firstLine).toContain("SVK");
|
expect(firstLine).toContain("SVK");
|
||||||
expect(firstLine).toContain("получили 12 500,50 руб.");
|
expect(firstLine).toContain("получили 12 500,50 руб.");
|
||||||
expect(firstLine).toContain("заплатили 4 000 руб.");
|
expect(firstLine).toContain("заплатили 4 000 руб.");
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ describe("assistant MCP discovery response policy", () => {
|
||||||
mcpDiscoveryRuntimeEntryPoint: entryPoint({
|
mcpDiscoveryRuntimeEntryPoint: entryPoint({
|
||||||
bridge: {
|
bridge: {
|
||||||
bridge_status: "answer_draft_ready",
|
bridge_status: "answer_draft_ready",
|
||||||
|
hot_runtime_wired: true,
|
||||||
user_facing_response_allowed: true,
|
user_facing_response_allowed: true,
|
||||||
business_fact_answer_allowed: true,
|
business_fact_answer_allowed: true,
|
||||||
requires_user_clarification: false,
|
requires_user_clarification: false,
|
||||||
|
|
@ -79,6 +80,56 @@ describe("assistant MCP discovery response policy", () => {
|
||||||
expect(result.reply_text).not.toContain("Коротко:");
|
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", () => {
|
it("keeps the current reply when the turn is not an unsupported boundary", () => {
|
||||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||||
currentReply: "regular chat",
|
currentReply: "regular chat",
|
||||||
|
|
@ -236,6 +287,7 @@ describe("assistant MCP discovery response policy", () => {
|
||||||
},
|
},
|
||||||
bridge: {
|
bridge: {
|
||||||
bridge_status: "answer_draft_ready",
|
bridge_status: "answer_draft_ready",
|
||||||
|
hot_runtime_wired: true,
|
||||||
user_facing_response_allowed: true,
|
user_facing_response_allowed: true,
|
||||||
business_fact_answer_allowed: true,
|
business_fact_answer_allowed: true,
|
||||||
requires_user_clarification: false,
|
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");
|
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", () => {
|
it("overrides exact document-list replies for explicit counterparty received-paid-net questions", () => {
|
||||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||||
currentReply: "Контрагент: Группа СВК. Найдено документов: 19.",
|
currentReply: "Контрагент: Группа СВК. Найдено документов: 19.",
|
||||||
|
|
@ -1509,4 +1623,95 @@ describe("assistant MCP discovery response policy", () => {
|
||||||
"mcp_discovery_response_policy_keep_broad_business_summary_over_clarification_candidate"
|
"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();
|
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", () => {
|
it("keeps payout wording as outgoing supplier-payout discovery", () => {
|
||||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
userMessage: "сколько мы заплатили Группа СВК за 2020 год?",
|
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",
|
"generation_id": "gen-ag06031054-1bd955",
|
||||||
"created_at": "2026-06-03T10:54:28+00:00",
|
"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