ARCH: продолжить metadata continuity для MCP discovery
This commit is contained in:
parent
561b4ea45c
commit
d66e2bfb01
|
|
@ -112,6 +112,29 @@ function localizeLine(value) {
|
|||
if (/^Counterparty monthly net value-flow breakdown was grouped by month over confirmed incoming and outgoing 1C rows$/i.test(value)) {
|
||||
return "Помесячная нетто-раскладка сгруппирована только по подтвержденным входящим и исходящим строкам 1С.";
|
||||
}
|
||||
const metadataSurfaceMatch = value.match(/^Confirmed 1C metadata surface(?: for scope "([^"]+)")?: (\d+) rows and (\d+) matching objects$/i);
|
||||
if (metadataSurfaceMatch) {
|
||||
const scopePart = metadataSurfaceMatch[1] ? ` по области "${metadataSurfaceMatch[1]}"` : "";
|
||||
return `В 1С подтверждена metadata-поверхность${scopePart}: ${metadataSurfaceMatch[2]} строк metadata-ответа и ${metadataSurfaceMatch[3]} совпавших объекта(ов).`;
|
||||
}
|
||||
const metadataObjectSetsMatch = value.match(/^Available metadata object sets: (.+)$/i);
|
||||
if (metadataObjectSetsMatch) {
|
||||
return `Доступные типы metadata-объектов: ${metadataObjectSetsMatch[1]}.`;
|
||||
}
|
||||
const metadataFieldsMatch = value.match(/^Available metadata fields\/sections: (.+)$/i);
|
||||
if (metadataFieldsMatch) {
|
||||
return `Доступные metadata-поля/секции: ${metadataFieldsMatch[1]}.`;
|
||||
}
|
||||
if (/^Detailed metadata fields were not returned by this MCP metadata probe$/i.test(value)) {
|
||||
return "Эта MCP-проверка metadata не вернула детальный список полей.";
|
||||
}
|
||||
const noMatchingMetadataScopeMatch = value.match(/^No matching 1C metadata objects were confirmed for scope "([^"]+)"$/i);
|
||||
if (noMatchingMetadataScopeMatch) {
|
||||
return `В 1С не подтверждены metadata-объекты по области "${noMatchingMetadataScopeMatch[1]}".`;
|
||||
}
|
||||
if (/^No matching 1C metadata objects were confirmed by this MCP metadata probe$/i.test(value)) {
|
||||
return "В 1С эта MCP-проверка не подтвердила подходящих metadata-объектов.";
|
||||
}
|
||||
if (/^Legal registration date is not proven by this MCP discovery pilot$/i.test(value)) {
|
||||
return "Юридическая дата регистрации этим поиском не подтверждена.";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,6 +141,13 @@ function mapPilotScopeToFollowupMeaning(pilotScope) {
|
|||
unsupported: "counterparty_bidirectional_value_flow_or_netting"
|
||||
};
|
||||
}
|
||||
if (pilotScope === "metadata_inspection_v1") {
|
||||
return {
|
||||
domain: "metadata",
|
||||
action: "inspect_catalog",
|
||||
unsupported: "1c_metadata_surface"
|
||||
};
|
||||
}
|
||||
return {
|
||||
domain: null,
|
||||
action: null,
|
||||
|
|
@ -183,6 +190,7 @@ function collectFollowupDiscoverySeed(followupContext) {
|
|||
const mapped = mapPilotScopeToFollowupMeaning(pilotScope).domain !== null
|
||||
? mapPilotScopeToFollowupMeaning(pilotScope)
|
||||
: mapAddressIntentToFollowupMeaning(previousIntent);
|
||||
const discoveryEntities = collectEntityCandidates(followupContext?.previous_discovery_entity_candidates);
|
||||
const counterparty = toNonEmptyString(previousFilters?.counterparty) ??
|
||||
toNonEmptyString(rootFilters?.counterparty) ??
|
||||
(toNonEmptyString(followupContext?.previous_anchor_type) === "counterparty"
|
||||
|
|
@ -201,6 +209,7 @@ function collectFollowupDiscoverySeed(followupContext) {
|
|||
action: mapped.action,
|
||||
unsupported: mapped.unsupported,
|
||||
counterparty,
|
||||
discoveryEntity: discoveryEntities[0] ?? null,
|
||||
organization,
|
||||
dateScope
|
||||
};
|
||||
|
|
@ -227,6 +236,9 @@ function hasMetadataSignal(text) {
|
|||
return (/(?:\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u044b|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b|\u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a\u0438|\u043f\u043e\u043b(?:\u0435|\u044f)|registers?|documents?|catalogs?|fields?)/iu.test(text) &&
|
||||
/(?:\u0435\u0441\u0442\u044c|\u0434\u043e\u0441\u0442\u0443\u043f\u043d|\u0432\s+1\u0441|available|exist)/iu.test(text));
|
||||
}
|
||||
function hasMetadataObjectHint(text) {
|
||||
return /(?:\u0440\u0435\u0433\u0438\u0441\u0442\u0440(?:\u044b)?|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442(?:\u044b)?|\u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a(?:\u0438)?|\u043f\u043e\u043b(?:\u0435|\u044f)|registers?|documents?|catalogs?|fields?)/iu.test(text);
|
||||
}
|
||||
function metadataActionFromRawText(text) {
|
||||
if (/(?:\u043f\u043e\u043b(?:\u0435|\u044f)|field)/iu.test(text)) {
|
||||
return "inspect_fields";
|
||||
|
|
@ -322,9 +334,13 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
!rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
(monthlyAggregationSignal || explicitDateScopeLiteralDetected || predecomposeDateScope));
|
||||
const seededDomain = followupDiscoverySeedApplicable ? followupSeed.domain : null;
|
||||
const seededAction = followupDiscoverySeedApplicable ? followupSeed.action : null;
|
||||
const seededUnsupported = followupDiscoverySeedApplicable ? followupSeed.unsupported : null;
|
||||
const metadataFollowupSeedApplicable = Boolean(followupSeed.domain === "metadata" &&
|
||||
!rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
hasMetadataObjectHint(rawText));
|
||||
const seededDomain = followupDiscoverySeedApplicable || metadataFollowupSeedApplicable ? followupSeed.domain : null;
|
||||
const seededAction = followupDiscoverySeedApplicable || metadataFollowupSeedApplicable ? followupSeed.action : null;
|
||||
const seededUnsupported = followupDiscoverySeedApplicable || metadataFollowupSeedApplicable ? followupSeed.unsupported : null;
|
||||
const lifecycleSignal = rawLifecycleSignal || seededDomain === "counterparty_lifecycle";
|
||||
const bidirectionalValueFlowSignal = !lifecycleSignal &&
|
||||
(rawBidirectionalValueFlowSignal || seededAction === "net_value_flow");
|
||||
|
|
@ -338,11 +354,14 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
unsupported: unsupported ?? seededUnsupported,
|
||||
lifecycleSignal,
|
||||
valueFlowSignal,
|
||||
metadataSignal: rawMetadataSignal
|
||||
metadataSignal: rawMetadataSignal || metadataFollowupSeedApplicable
|
||||
});
|
||||
const entityCandidates = collectEntityCandidates(assistantTurnMeaning?.explicit_entity_candidates);
|
||||
pushUnique(entityCandidates, predecomposeEntities.counterparty);
|
||||
pushUnique(entityCandidates, followupSeed.counterparty);
|
||||
if ((rawMetadataSignal || metadataFollowupSeedApplicable) && !followupSeed.counterparty) {
|
||||
pushUnique(entityCandidates, followupSeed.discoveryEntity);
|
||||
}
|
||||
if (valueFlowSignal && !predecomposeEntities.counterparty && !followupSeed.counterparty) {
|
||||
pushUnique(entityCandidates, predecomposeEntities.organization);
|
||||
pushUnique(entityCandidates, followupSeed.organization);
|
||||
|
|
@ -356,7 +375,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
? "counterparty_lifecycle"
|
||||
: valueFlowSignal
|
||||
? "counterparty_value"
|
||||
: rawMetadataSignal
|
||||
: rawMetadataSignal || metadataFollowupSeedApplicable
|
||||
? "metadata"
|
||||
: rawDomain ?? seededDomain,
|
||||
asked_action_family: lifecycleSignal
|
||||
|
|
@ -367,8 +386,8 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
: payoutSignal
|
||||
? "payout"
|
||||
: rawAction ?? seededAction ?? "turnover"
|
||||
: rawMetadataSignal
|
||||
? metadataActionFromRawText(rawText)
|
||||
: rawMetadataSignal || metadataFollowupSeedApplicable
|
||||
? metadataActionFromRawText(rawText) ?? seededAction
|
||||
: rawAction ?? seededAction,
|
||||
asked_aggregation_axis: monthlyAggregationSignal ? "month" : rawAggregationAxis,
|
||||
explicit_entity_candidates: entityCandidates,
|
||||
|
|
@ -383,7 +402,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
: payoutSignal
|
||||
? "counterparty_payouts_or_outflow"
|
||||
: seededUnsupported ?? "counterparty_value_or_turnover"
|
||||
: rawMetadataSignal
|
||||
: rawMetadataSignal || metadataFollowupSeedApplicable
|
||||
? "1c_metadata_surface"
|
||||
: followupDiscoverySeedApplicable
|
||||
? seededUnsupported
|
||||
|
|
@ -393,6 +412,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
lifecycleSignal ||
|
||||
valueFlowSignal ||
|
||||
rawMetadataSignal ||
|
||||
metadataFollowupSeedApplicable ||
|
||||
followupDiscoverySeedApplicable)
|
||||
};
|
||||
const cleanTurnMeaning = {};
|
||||
|
|
@ -424,15 +444,15 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
unsupported: unsupported ?? seededUnsupported,
|
||||
lifecycleSignal,
|
||||
valueFlowSignal,
|
||||
metadataSignal: rawMetadataSignal,
|
||||
metadataSignal: rawMetadataSignal || metadataFollowupSeedApplicable,
|
||||
semanticDataNeed,
|
||||
explicitIntentCandidate,
|
||||
followupDiscoverySeedApplicable
|
||||
followupDiscoverySeedApplicable: followupDiscoverySeedApplicable || metadataFollowupSeedApplicable
|
||||
});
|
||||
const hasTurnMeaning = Object.keys(cleanTurnMeaning).length > 0;
|
||||
const sourceSignal = assistantTurnMeaning
|
||||
? "assistant_turn_meaning"
|
||||
: followupDiscoverySeedApplicable
|
||||
: followupDiscoverySeedApplicable || metadataFollowupSeedApplicable
|
||||
? "followup_context"
|
||||
: predecomposeContract
|
||||
? "predecompose_contract"
|
||||
|
|
@ -440,7 +460,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
? "raw_text"
|
||||
: valueFlowSignal
|
||||
? "raw_text"
|
||||
: rawMetadataSignal
|
||||
: rawMetadataSignal || metadataFollowupSeedApplicable
|
||||
? "raw_text"
|
||||
: "none";
|
||||
if (lifecycleSignal) {
|
||||
|
|
@ -464,6 +484,9 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
if (followupDiscoverySeedApplicable) {
|
||||
pushReason(reasonCodes, "mcp_discovery_seeded_from_followup_context");
|
||||
}
|
||||
if (metadataFollowupSeedApplicable) {
|
||||
pushReason(reasonCodes, "mcp_discovery_metadata_seeded_from_followup_context");
|
||||
}
|
||||
if (unsupported) {
|
||||
pushReason(reasonCodes, "mcp_discovery_unsupported_but_understood_turn");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,8 +34,30 @@ function periodPartForRecap(scopedDate) {
|
|||
}
|
||||
return /^\d{2}\.\d{2}\.\d{4}$/.test(scopedDate) ? ` на ${scopedDate}` : ` за период ${scopedDate}`;
|
||||
}
|
||||
function readDiscoveryMetadataScope(debug) {
|
||||
const discoveryEntry = toRecordObject(debug?.assistant_mcp_discovery_entry_point_v1);
|
||||
const bridge = toRecordObject(discoveryEntry?.bridge);
|
||||
const pilot = toRecordObject(bridge?.pilot);
|
||||
const surface = toRecordObject(pilot?.derived_metadata_surface);
|
||||
const surfaceScope = toNonEmptyString(surface?.metadata_scope);
|
||||
if (surfaceScope) {
|
||||
return surfaceScope;
|
||||
}
|
||||
const turnInput = toRecordObject(discoveryEntry?.turn_input);
|
||||
const turnMeaningRef = toRecordObject(turnInput?.turn_meaning_ref);
|
||||
const entityCandidates = Array.isArray(turnMeaningRef?.explicit_entity_candidates)
|
||||
? turnMeaningRef.explicit_entity_candidates
|
||||
: [];
|
||||
for (const candidate of entityCandidates) {
|
||||
const text = toNonEmptyString(candidate);
|
||||
if (text) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function buildDiscoveryRecapFactLine(input) {
|
||||
if (!input.debug || !input.counterparty) {
|
||||
if (!input.debug) {
|
||||
return null;
|
||||
}
|
||||
const pilotScope = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryPilotScope)(input.debug, toNonEmptyString);
|
||||
|
|
@ -43,6 +65,34 @@ function buildDiscoveryRecapFactLine(input) {
|
|||
const bridge = toRecordObject(discoveryEntry?.bridge);
|
||||
const pilot = toRecordObject(bridge?.pilot);
|
||||
const periodPart = periodPartForRecap(input.scopedDate);
|
||||
if (pilotScope === "metadata_inspection_v1") {
|
||||
const metadataScope = readDiscoveryMetadataScope(input.debug);
|
||||
const surface = toRecordObject(pilot?.derived_metadata_surface);
|
||||
const entitySets = Array.isArray(surface?.available_entity_sets)
|
||||
? surface.available_entity_sets
|
||||
.map((item) => toNonEmptyString(item))
|
||||
.filter((item) => Boolean(item))
|
||||
: [];
|
||||
const fields = Array.isArray(surface?.available_fields)
|
||||
? surface.available_fields
|
||||
.map((item) => toNonEmptyString(item))
|
||||
.filter((item) => Boolean(item))
|
||||
: [];
|
||||
const objects = Array.isArray(surface?.matched_objects)
|
||||
? surface.matched_objects
|
||||
.map((item) => toNonEmptyString(item))
|
||||
.filter((item) => Boolean(item))
|
||||
: [];
|
||||
const rows = Number(surface?.matched_rows ?? 0);
|
||||
const scopePart = metadataScope ? ` по области «${metadataScope}»` : "";
|
||||
const objectsPart = objects.length > 0 ? `, нашли объекты ${objects.slice(0, 4).join(", ")}` : "";
|
||||
const entitySetsPart = entitySets.length > 0 ? `, видны типы ${entitySets.slice(0, 4).join(", ")}` : "";
|
||||
const fieldsPart = fields.length > 0 ? `, доступны поля/секции ${fields.slice(0, 5).join(", ")}` : "";
|
||||
return `смотрели metadata-поверхность 1С${scopePart}${periodPart}: ${rows} подтвержденных строк${objectsPart}${entitySetsPart}${fieldsPart}`.trim();
|
||||
}
|
||||
if (!input.counterparty) {
|
||||
return null;
|
||||
}
|
||||
if (pilotScope === "counterparty_lifecycle_query_documents_v1") {
|
||||
const activityPeriod = toRecordObject(pilot?.derived_activity_period);
|
||||
const duration = toNonEmptyString(activityPeriod?.duration_human_ru);
|
||||
|
|
|
|||
|
|
@ -146,6 +146,31 @@ function localizeLine(value: string): string {
|
|||
if (/^Counterparty monthly net value-flow breakdown was grouped by month over confirmed incoming and outgoing 1C rows$/i.test(value)) {
|
||||
return "Помесячная нетто-раскладка сгруппирована только по подтвержденным входящим и исходящим строкам 1С.";
|
||||
}
|
||||
const metadataSurfaceMatch = value.match(
|
||||
/^Confirmed 1C metadata surface(?: for scope "([^"]+)")?: (\d+) rows and (\d+) matching objects$/i
|
||||
);
|
||||
if (metadataSurfaceMatch) {
|
||||
const scopePart = metadataSurfaceMatch[1] ? ` по области "${metadataSurfaceMatch[1]}"` : "";
|
||||
return `В 1С подтверждена metadata-поверхность${scopePart}: ${metadataSurfaceMatch[2]} строк metadata-ответа и ${metadataSurfaceMatch[3]} совпавших объекта(ов).`;
|
||||
}
|
||||
const metadataObjectSetsMatch = value.match(/^Available metadata object sets: (.+)$/i);
|
||||
if (metadataObjectSetsMatch) {
|
||||
return `Доступные типы metadata-объектов: ${metadataObjectSetsMatch[1]}.`;
|
||||
}
|
||||
const metadataFieldsMatch = value.match(/^Available metadata fields\/sections: (.+)$/i);
|
||||
if (metadataFieldsMatch) {
|
||||
return `Доступные metadata-поля/секции: ${metadataFieldsMatch[1]}.`;
|
||||
}
|
||||
if (/^Detailed metadata fields were not returned by this MCP metadata probe$/i.test(value)) {
|
||||
return "Эта MCP-проверка metadata не вернула детальный список полей.";
|
||||
}
|
||||
const noMatchingMetadataScopeMatch = value.match(/^No matching 1C metadata objects were confirmed for scope "([^"]+)"$/i);
|
||||
if (noMatchingMetadataScopeMatch) {
|
||||
return `В 1С не подтверждены metadata-объекты по области "${noMatchingMetadataScopeMatch[1]}".`;
|
||||
}
|
||||
if (/^No matching 1C metadata objects were confirmed by this MCP metadata probe$/i.test(value)) {
|
||||
return "В 1С эта MCP-проверка не подтвердила подходящих metadata-объектов.";
|
||||
}
|
||||
if (/^Legal registration date is not proven by this MCP discovery pilot$/i.test(value)) {
|
||||
return "Юридическая дата регистрации этим поиском не подтверждена.";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -190,6 +190,13 @@ function mapPilotScopeToFollowupMeaning(
|
|||
unsupported: "counterparty_bidirectional_value_flow_or_netting"
|
||||
};
|
||||
}
|
||||
if (pilotScope === "metadata_inspection_v1") {
|
||||
return {
|
||||
domain: "metadata",
|
||||
action: "inspect_catalog",
|
||||
unsupported: "1c_metadata_surface"
|
||||
};
|
||||
}
|
||||
return {
|
||||
domain: null,
|
||||
action: null,
|
||||
|
|
@ -238,6 +245,7 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
|
|||
action: string | null;
|
||||
unsupported: string | null;
|
||||
counterparty: string | null;
|
||||
discoveryEntity: string | null;
|
||||
organization: string | null;
|
||||
dateScope: string | null;
|
||||
} {
|
||||
|
|
@ -250,6 +258,7 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
|
|||
mapPilotScopeToFollowupMeaning(pilotScope).domain !== null
|
||||
? mapPilotScopeToFollowupMeaning(pilotScope)
|
||||
: mapAddressIntentToFollowupMeaning(previousIntent);
|
||||
const discoveryEntities = collectEntityCandidates(followupContext?.previous_discovery_entity_candidates);
|
||||
const counterparty =
|
||||
toNonEmptyString(previousFilters?.counterparty) ??
|
||||
toNonEmptyString(rootFilters?.counterparty) ??
|
||||
|
|
@ -271,6 +280,7 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
|
|||
action: mapped.action,
|
||||
unsupported: mapped.unsupported,
|
||||
counterparty,
|
||||
discoveryEntity: discoveryEntities[0] ?? null,
|
||||
organization,
|
||||
dateScope
|
||||
};
|
||||
|
|
@ -322,6 +332,12 @@ function hasMetadataSignal(text: string): boolean {
|
|||
);
|
||||
}
|
||||
|
||||
function hasMetadataObjectHint(text: string): boolean {
|
||||
return /(?:\u0440\u0435\u0433\u0438\u0441\u0442\u0440(?:\u044b)?|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442(?:\u044b)?|\u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a(?:\u0438)?|\u043f\u043e\u043b(?:\u0435|\u044f)|registers?|documents?|catalogs?|fields?)/iu.test(
|
||||
text
|
||||
);
|
||||
}
|
||||
|
||||
function metadataActionFromRawText(text: string): string {
|
||||
if (/(?:\u043f\u043e\u043b(?:\u0435|\u044f)|field)/iu.test(text)) {
|
||||
return "inspect_fields";
|
||||
|
|
@ -443,9 +459,15 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
!rawValueFlowSignal &&
|
||||
(monthlyAggregationSignal || explicitDateScopeLiteralDetected || predecomposeDateScope)
|
||||
);
|
||||
const seededDomain = followupDiscoverySeedApplicable ? followupSeed.domain : null;
|
||||
const seededAction = followupDiscoverySeedApplicable ? followupSeed.action : null;
|
||||
const seededUnsupported = followupDiscoverySeedApplicable ? followupSeed.unsupported : null;
|
||||
const metadataFollowupSeedApplicable = Boolean(
|
||||
followupSeed.domain === "metadata" &&
|
||||
!rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
hasMetadataObjectHint(rawText)
|
||||
);
|
||||
const seededDomain = followupDiscoverySeedApplicable || metadataFollowupSeedApplicable ? followupSeed.domain : null;
|
||||
const seededAction = followupDiscoverySeedApplicable || metadataFollowupSeedApplicable ? followupSeed.action : null;
|
||||
const seededUnsupported = followupDiscoverySeedApplicable || metadataFollowupSeedApplicable ? followupSeed.unsupported : null;
|
||||
const lifecycleSignal =
|
||||
rawLifecycleSignal || seededDomain === "counterparty_lifecycle";
|
||||
const bidirectionalValueFlowSignal =
|
||||
|
|
@ -463,11 +485,14 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
unsupported: unsupported ?? seededUnsupported,
|
||||
lifecycleSignal,
|
||||
valueFlowSignal,
|
||||
metadataSignal: rawMetadataSignal
|
||||
metadataSignal: rawMetadataSignal || metadataFollowupSeedApplicable
|
||||
});
|
||||
const entityCandidates = collectEntityCandidates(assistantTurnMeaning?.explicit_entity_candidates);
|
||||
pushUnique(entityCandidates, predecomposeEntities.counterparty);
|
||||
pushUnique(entityCandidates, followupSeed.counterparty);
|
||||
if ((rawMetadataSignal || metadataFollowupSeedApplicable) && !followupSeed.counterparty) {
|
||||
pushUnique(entityCandidates, followupSeed.discoveryEntity);
|
||||
}
|
||||
if (valueFlowSignal && !predecomposeEntities.counterparty && !followupSeed.counterparty) {
|
||||
pushUnique(entityCandidates, predecomposeEntities.organization);
|
||||
pushUnique(entityCandidates, followupSeed.organization);
|
||||
|
|
@ -484,7 +509,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
? "counterparty_lifecycle"
|
||||
: valueFlowSignal
|
||||
? "counterparty_value"
|
||||
: rawMetadataSignal
|
||||
: rawMetadataSignal || metadataFollowupSeedApplicable
|
||||
? "metadata"
|
||||
: rawDomain ?? seededDomain,
|
||||
asked_action_family: lifecycleSignal
|
||||
|
|
@ -495,8 +520,8 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
: payoutSignal
|
||||
? "payout"
|
||||
: rawAction ?? seededAction ?? "turnover"
|
||||
: rawMetadataSignal
|
||||
? metadataActionFromRawText(rawText)
|
||||
: rawMetadataSignal || metadataFollowupSeedApplicable
|
||||
? metadataActionFromRawText(rawText) ?? seededAction
|
||||
: rawAction ?? seededAction,
|
||||
asked_aggregation_axis: monthlyAggregationSignal ? "month" : rawAggregationAxis,
|
||||
explicit_entity_candidates: entityCandidates,
|
||||
|
|
@ -512,7 +537,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
: payoutSignal
|
||||
? "counterparty_payouts_or_outflow"
|
||||
: seededUnsupported ?? "counterparty_value_or_turnover"
|
||||
: rawMetadataSignal
|
||||
: rawMetadataSignal || metadataFollowupSeedApplicable
|
||||
? "1c_metadata_surface"
|
||||
: followupDiscoverySeedApplicable
|
||||
? seededUnsupported
|
||||
|
|
@ -523,6 +548,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
lifecycleSignal ||
|
||||
valueFlowSignal ||
|
||||
rawMetadataSignal ||
|
||||
metadataFollowupSeedApplicable ||
|
||||
followupDiscoverySeedApplicable
|
||||
)
|
||||
};
|
||||
|
|
@ -557,15 +583,15 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
unsupported: unsupported ?? seededUnsupported,
|
||||
lifecycleSignal,
|
||||
valueFlowSignal,
|
||||
metadataSignal: rawMetadataSignal,
|
||||
metadataSignal: rawMetadataSignal || metadataFollowupSeedApplicable,
|
||||
semanticDataNeed,
|
||||
explicitIntentCandidate,
|
||||
followupDiscoverySeedApplicable
|
||||
followupDiscoverySeedApplicable: followupDiscoverySeedApplicable || metadataFollowupSeedApplicable
|
||||
});
|
||||
const hasTurnMeaning = Object.keys(cleanTurnMeaning).length > 0;
|
||||
const sourceSignal: AssistantMcpDiscoveryTurnInputSource = assistantTurnMeaning
|
||||
? "assistant_turn_meaning"
|
||||
: followupDiscoverySeedApplicable
|
||||
: followupDiscoverySeedApplicable || metadataFollowupSeedApplicable
|
||||
? "followup_context"
|
||||
: predecomposeContract
|
||||
? "predecompose_contract"
|
||||
|
|
@ -573,7 +599,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
? "raw_text"
|
||||
: valueFlowSignal
|
||||
? "raw_text"
|
||||
: rawMetadataSignal
|
||||
: rawMetadataSignal || metadataFollowupSeedApplicable
|
||||
? "raw_text"
|
||||
: "none";
|
||||
|
||||
|
|
@ -598,6 +624,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
if (followupDiscoverySeedApplicable) {
|
||||
pushReason(reasonCodes, "mcp_discovery_seeded_from_followup_context");
|
||||
}
|
||||
if (metadataFollowupSeedApplicable) {
|
||||
pushReason(reasonCodes, "mcp_discovery_metadata_seeded_from_followup_context");
|
||||
}
|
||||
if (unsupported) {
|
||||
pushReason(reasonCodes, "mcp_discovery_unsupported_but_understood_turn");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,12 +77,35 @@ function periodPartForRecap(scopedDate: string | null): string {
|
|||
return /^\d{2}\.\d{2}\.\d{4}$/.test(scopedDate) ? ` на ${scopedDate}` : ` за период ${scopedDate}`;
|
||||
}
|
||||
|
||||
function readDiscoveryMetadataScope(debug: Record<string, unknown> | null): string | null {
|
||||
const discoveryEntry = toRecordObject(debug?.assistant_mcp_discovery_entry_point_v1);
|
||||
const bridge = toRecordObject(discoveryEntry?.bridge);
|
||||
const pilot = toRecordObject(bridge?.pilot);
|
||||
const surface = toRecordObject(pilot?.derived_metadata_surface);
|
||||
const surfaceScope = toNonEmptyString(surface?.metadata_scope);
|
||||
if (surfaceScope) {
|
||||
return surfaceScope;
|
||||
}
|
||||
const turnInput = toRecordObject(discoveryEntry?.turn_input);
|
||||
const turnMeaningRef = toRecordObject(turnInput?.turn_meaning_ref);
|
||||
const entityCandidates = Array.isArray(turnMeaningRef?.explicit_entity_candidates)
|
||||
? turnMeaningRef.explicit_entity_candidates
|
||||
: [];
|
||||
for (const candidate of entityCandidates) {
|
||||
const text = toNonEmptyString(candidate);
|
||||
if (text) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildDiscoveryRecapFactLine(input: {
|
||||
debug: Record<string, unknown> | null;
|
||||
counterparty: string | null;
|
||||
scopedDate: string | null;
|
||||
}): string | null {
|
||||
if (!input.debug || !input.counterparty) {
|
||||
if (!input.debug) {
|
||||
return null;
|
||||
}
|
||||
const pilotScope = readAssistantMcpDiscoveryPilotScope(input.debug, toNonEmptyString);
|
||||
|
|
@ -90,6 +113,36 @@ function buildDiscoveryRecapFactLine(input: {
|
|||
const bridge = toRecordObject(discoveryEntry?.bridge);
|
||||
const pilot = toRecordObject(bridge?.pilot);
|
||||
const periodPart = periodPartForRecap(input.scopedDate);
|
||||
if (pilotScope === "metadata_inspection_v1") {
|
||||
const metadataScope = readDiscoveryMetadataScope(input.debug);
|
||||
const surface = toRecordObject(pilot?.derived_metadata_surface);
|
||||
const entitySets = Array.isArray(surface?.available_entity_sets)
|
||||
? surface.available_entity_sets
|
||||
.map((item) => toNonEmptyString(item))
|
||||
.filter((item): item is string => Boolean(item))
|
||||
: [];
|
||||
const fields = Array.isArray(surface?.available_fields)
|
||||
? surface.available_fields
|
||||
.map((item) => toNonEmptyString(item))
|
||||
.filter((item): item is string => Boolean(item))
|
||||
: [];
|
||||
const objects = Array.isArray(surface?.matched_objects)
|
||||
? surface.matched_objects
|
||||
.map((item) => toNonEmptyString(item))
|
||||
.filter((item): item is string => Boolean(item))
|
||||
: [];
|
||||
const rows = Number(surface?.matched_rows ?? 0);
|
||||
const scopePart = metadataScope ? ` по области «${metadataScope}»` : "";
|
||||
const objectsPart = objects.length > 0 ? `, нашли объекты ${objects.slice(0, 4).join(", ")}` : "";
|
||||
const entitySetsPart =
|
||||
entitySets.length > 0 ? `, видны типы ${entitySets.slice(0, 4).join(", ")}` : "";
|
||||
const fieldsPart =
|
||||
fields.length > 0 ? `, доступны поля/секции ${fields.slice(0, 5).join(", ")}` : "";
|
||||
return `смотрели metadata-поверхность 1С${scopePart}${periodPart}: ${rows} подтвержденных строк${objectsPart}${entitySetsPart}${fieldsPart}`.trim();
|
||||
}
|
||||
if (!input.counterparty) {
|
||||
return null;
|
||||
}
|
||||
if (pilotScope === "counterparty_lifecycle_query_documents_v1") {
|
||||
const activityPeriod = toRecordObject(pilot?.derived_activity_period);
|
||||
const duration = toNonEmptyString(activityPeriod?.duration_human_ru);
|
||||
|
|
|
|||
|
|
@ -209,6 +209,39 @@ describe("assistant MCP discovery response candidate", () => {
|
|||
expect(candidate.reply_text).not.toContain("broad probe hit the row limit");
|
||||
});
|
||||
|
||||
it("localizes metadata evidence without leaking raw MCP wording", () => {
|
||||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||||
entryPoint({
|
||||
bridge: {
|
||||
bridge_status: "answer_draft_ready",
|
||||
user_facing_response_allowed: true,
|
||||
business_fact_answer_allowed: true,
|
||||
requires_user_clarification: false,
|
||||
answer_draft: {
|
||||
answer_mode: "confirmed_with_bounded_inference",
|
||||
headline: "По данным 1С найдена подтвержденная metadata-поверхность.",
|
||||
confirmed_lines: [
|
||||
'Confirmed 1C metadata surface for scope "НДС": 7 rows and 3 matching objects',
|
||||
"Available metadata object sets: accumulation_register, document",
|
||||
"Available metadata fields/sections: amount, vat_rate, organization"
|
||||
],
|
||||
inference_lines: [],
|
||||
unknown_lines: ['No matching 1C metadata objects were confirmed for scope "Прибыль"'],
|
||||
limitation_lines: ["Detailed metadata fields were not returned by this MCP metadata probe"],
|
||||
next_step_line: null
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
expect(candidate.reply_text).toContain('В 1С подтверждена metadata-поверхность по области "НДС"');
|
||||
expect(candidate.reply_text).toContain("Доступные типы metadata-объектов");
|
||||
expect(candidate.reply_text).toContain("Доступные metadata-поля/секции");
|
||||
expect(candidate.reply_text).toContain('В 1С не подтверждены metadata-объекты по области "Прибыль"');
|
||||
expect(candidate.reply_text).toContain("Эта MCP-проверка metadata не вернула детальный список полей");
|
||||
expect(candidate.reply_text).not.toContain("Confirmed 1C metadata surface");
|
||||
});
|
||||
|
||||
it("returns not applicable when discovery was skipped for an exact supported route", () => {
|
||||
const candidate = buildAssistantMcpDiscoveryResponseCandidate({
|
||||
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||
|
|
|
|||
|
|
@ -203,6 +203,34 @@ describe("assistant MCP discovery turn input adapter", () => {
|
|||
expect(result.reason_codes).toContain("mcp_discovery_date_scope_from_followup_context");
|
||||
});
|
||||
|
||||
it("seeds short metadata follow-up from prior metadata discovery context", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage: "а по регистрам?",
|
||||
followupContext: {
|
||||
previous_discovery_pilot_scope: "metadata_inspection_v1",
|
||||
previous_filters: {
|
||||
counterparty: "НДС"
|
||||
},
|
||||
previous_anchor_type: "counterparty",
|
||||
previous_anchor_value: "НДС"
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.adapter_status).toBe("ready");
|
||||
expect(result.should_run_discovery).toBe(true);
|
||||
expect(result.source_signal).toBe("followup_context");
|
||||
expect(result.semantic_data_need).toBe("1C metadata evidence");
|
||||
expect(result.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "metadata",
|
||||
asked_action_family: "inspect_registers",
|
||||
explicit_entity_candidates: ["НДС"],
|
||||
unsupported_but_understood_family: "1c_metadata_surface",
|
||||
stale_replay_forbidden: true
|
||||
});
|
||||
expect(result.reason_codes).toContain("mcp_discovery_metadata_seeded_from_followup_context");
|
||||
expect(result.reason_codes).toContain("mcp_discovery_counterparty_from_followup_context");
|
||||
});
|
||||
|
||||
it("switches the checked year on a short payout follow-up while keeping prior discovery counterparty", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage: "а теперь за 2021?",
|
||||
|
|
|
|||
|
|
@ -386,6 +386,64 @@ describe("assistantMemoryRecapPolicy", () => {
|
|||
expect(reply).toContain("43 763 351,53 руб.");
|
||||
});
|
||||
|
||||
it("builds deterministic recap summary from grounded MCP metadata discovery context", () => {
|
||||
const sessionItems = [
|
||||
{
|
||||
role: "assistant",
|
||||
debug: {
|
||||
execution_lane: "living_chat",
|
||||
mcp_discovery_response_applied: true,
|
||||
assistant_mcp_discovery_entry_point_v1: {
|
||||
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||
entry_status: "bridge_executed",
|
||||
turn_input: {
|
||||
turn_meaning_ref: {
|
||||
explicit_entity_candidates: ["НДС"]
|
||||
}
|
||||
},
|
||||
bridge: {
|
||||
bridge_status: "answer_draft_ready",
|
||||
business_fact_answer_allowed: true,
|
||||
answer_draft: {
|
||||
answer_mode: "confirmed_with_bounded_inference"
|
||||
},
|
||||
pilot: {
|
||||
pilot_scope: "metadata_inspection_v1",
|
||||
derived_metadata_surface: {
|
||||
metadata_scope: "НДС",
|
||||
matched_rows: 7,
|
||||
matched_objects: ["РегистрНакопления.НДСПокупок"],
|
||||
available_entity_sets: ["accumulation_register", "document"],
|
||||
available_fields: ["amount", "vat_rate", "organization"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
const context = resolveAssistantLivingChatMemoryContext({
|
||||
modeDecisionReason: "memory_recap_followup_detected",
|
||||
sessionItems
|
||||
});
|
||||
|
||||
const reply = buildAddressMemoryRecapReply({
|
||||
organization: null,
|
||||
addressDebug: context.lastMemoryAddressDebug,
|
||||
sessionItems,
|
||||
toNonEmptyString: (value: unknown) => {
|
||||
const text = String(value ?? "").trim();
|
||||
return text.length > 0 ? text : null;
|
||||
}
|
||||
});
|
||||
|
||||
expect(context.contextualMemoryRecapFollowup).toBe(true);
|
||||
expect(reply).toContain("НДС");
|
||||
expect(reply).toContain("metadata-поверхность 1С");
|
||||
expect(reply).toContain("amount");
|
||||
expect(reply).toContain("accumulation_register");
|
||||
});
|
||||
|
||||
it("builds deterministic broad business evaluation summary from recent grounded organization facts", () => {
|
||||
const sessionItems = [
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue