ARCH: замкнуть grounded entity follow-up на документы и денежный поток
This commit is contained in:
parent
ce48fa83a5
commit
1fd8062dc7
|
|
@ -0,0 +1,99 @@
|
||||||
|
{
|
||||||
|
"schema_version": "domain_truth_harness_spec_v1",
|
||||||
|
"scenario_id": "address_truth_harness_phase26_entity_followup_chain",
|
||||||
|
"domain": "address_phase26_entity_followup_chain",
|
||||||
|
"title": "Phase 26 resolved-entity follow-up chain replay",
|
||||||
|
"description": "Targeted AGENT replay for the next Big Block C slice where an MCP-grounded counterparty must become a reusable dialog anchor for downstream document and movement evidence requests without forcing the user to repeat the resolved 1C name.",
|
||||||
|
"bindings": {},
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step_id": "step_01_resolve_counterparty_alias",
|
||||||
|
"title": "Entity resolution grounds the checked 1C counterparty from a loose alias",
|
||||||
|
"question": "найди в 1С контрагента СВК",
|
||||||
|
"allowed_reply_types": [
|
||||||
|
"factual_with_explanation",
|
||||||
|
"partial_coverage"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"(?i)свк",
|
||||||
|
"(?i)контрагент"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_any": [
|
||||||
|
"(?i)группа\\s+свк",
|
||||||
|
"(?i)каталог",
|
||||||
|
"(?i)найден",
|
||||||
|
"(?i)наиболее вероят"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)получили",
|
||||||
|
"(?i)заплатили",
|
||||||
|
"(?i)нетто",
|
||||||
|
"(?i)оборот",
|
||||||
|
"(?i)выручк",
|
||||||
|
"(?i)сумм(а|ы)"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": [
|
||||||
|
"entity_resolution",
|
||||||
|
"alias_grounding",
|
||||||
|
"followup_anchor"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "step_02_documents_by_resolved_entity_followup",
|
||||||
|
"title": "Short document follow-up reuses the resolved counterparty anchor",
|
||||||
|
"question": "по нему документы за 2020 год",
|
||||||
|
"allowed_reply_types": [
|
||||||
|
"factual_with_explanation",
|
||||||
|
"partial_coverage"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"(?i)документ|счет|накладн|акт",
|
||||||
|
"(?i)2020"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_any": [
|
||||||
|
"(?i)группа\\s+свк",
|
||||||
|
"(?i)свк"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)не найден контрагент",
|
||||||
|
"(?i)уточните, какого контрагента",
|
||||||
|
"(?i)по какому контрагенту"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": [
|
||||||
|
"entity_resolution",
|
||||||
|
"document_evidence",
|
||||||
|
"followup_reuse"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "step_03_movements_by_resolved_entity_followup",
|
||||||
|
"title": "Short movement follow-up keeps the same grounded counterparty anchor",
|
||||||
|
"question": "а теперь по нему движения за 2020 год",
|
||||||
|
"allowed_reply_types": [
|
||||||
|
"factual_with_explanation",
|
||||||
|
"partial_coverage"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"(?i)движени|платеж|операц|проводк",
|
||||||
|
"(?i)2020"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_any": [
|
||||||
|
"(?i)группа\\s+свк",
|
||||||
|
"(?i)свк"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)не найден контрагент",
|
||||||
|
"(?i)уточните, какого контрагента",
|
||||||
|
"(?i)по какому контрагенту"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": [
|
||||||
|
"entity_resolution",
|
||||||
|
"movement_evidence",
|
||||||
|
"followup_reuse"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
{
|
||||||
|
"schema_version": "domain_truth_harness_spec_v1",
|
||||||
|
"scenario_id": "address_truth_harness_phase27_entity_value_followup_chain",
|
||||||
|
"domain": "address_phase27_entity_value_followup_chain",
|
||||||
|
"title": "Phase 27 resolved-entity value-flow follow-up replay",
|
||||||
|
"description": "Targeted AGENT replay for the next Big Block C slice where an MCP-grounded counterparty must become a reusable dialog anchor for downstream value-flow and net-flow questions without forcing the user to restate the resolved 1C name.",
|
||||||
|
"bindings": {},
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step_id": "step_01_resolve_counterparty_alias",
|
||||||
|
"title": "Entity resolution grounds the checked 1C counterparty from a loose alias",
|
||||||
|
"question": "найди в 1С контрагента СВК",
|
||||||
|
"allowed_reply_types": [
|
||||||
|
"factual_with_explanation",
|
||||||
|
"partial_coverage"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"(?i)свк",
|
||||||
|
"(?i)контрагент"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_any": [
|
||||||
|
"(?i)группа\\s+свк",
|
||||||
|
"(?i)каталог",
|
||||||
|
"(?i)найден",
|
||||||
|
"(?i)наиболее вероят"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)получили",
|
||||||
|
"(?i)заплатили",
|
||||||
|
"(?i)нетто",
|
||||||
|
"(?i)оборот",
|
||||||
|
"(?i)выручк",
|
||||||
|
"(?i)сумм(а|ы)"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": [
|
||||||
|
"entity_resolution",
|
||||||
|
"alias_grounding",
|
||||||
|
"followup_anchor"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "step_02_value_flow_by_resolved_entity_followup",
|
||||||
|
"title": "Short turnover follow-up reuses the resolved counterparty anchor",
|
||||||
|
"question": "сколько получили по нему за 2020 год",
|
||||||
|
"allowed_reply_types": [
|
||||||
|
"factual_with_explanation",
|
||||||
|
"partial_coverage"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"(?i)2020",
|
||||||
|
"(?i)получил|входящ|поступ",
|
||||||
|
"(?i)руб"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_any": [
|
||||||
|
"(?i)группа\\s+свк",
|
||||||
|
"(?i)свк"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)не найден контрагент",
|
||||||
|
"(?i)уточните, какого контрагента",
|
||||||
|
"(?i)по какому контрагенту"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": [
|
||||||
|
"entity_resolution",
|
||||||
|
"counterparty_value_flow",
|
||||||
|
"followup_reuse"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "step_03_net_flow_by_resolved_entity_followup",
|
||||||
|
"title": "Short net-flow follow-up keeps the same grounded counterparty anchor",
|
||||||
|
"question": "а какое нетто по нему за 2020 год",
|
||||||
|
"allowed_reply_types": [
|
||||||
|
"factual_with_explanation",
|
||||||
|
"partial_coverage"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"(?i)2020",
|
||||||
|
"(?i)нетто|сальдо|разниц",
|
||||||
|
"(?i)руб"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_any": [
|
||||||
|
"(?i)группа\\s+свк",
|
||||||
|
"(?i)свк"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)не найден контрагент",
|
||||||
|
"(?i)уточните, какого контрагента",
|
||||||
|
"(?i)по какому контрагенту"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": [
|
||||||
|
"entity_resolution",
|
||||||
|
"counterparty_net_value_flow",
|
||||||
|
"followup_reuse"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.readAssistantMcpDiscoveryEntityCandidates = readAssistantMcpDiscoveryEntityCandidates;
|
||||||
exports.readAssistantMcpDiscoveryPilotScope = readAssistantMcpDiscoveryPilotScope;
|
exports.readAssistantMcpDiscoveryPilotScope = readAssistantMcpDiscoveryPilotScope;
|
||||||
exports.readAssistantMcpDiscoveryMetadataRouteFamily = readAssistantMcpDiscoveryMetadataRouteFamily;
|
exports.readAssistantMcpDiscoveryMetadataRouteFamily = readAssistantMcpDiscoveryMetadataRouteFamily;
|
||||||
exports.readAssistantMcpDiscoveryMetadataSelectedEntitySet = readAssistantMcpDiscoveryMetadataSelectedEntitySet;
|
exports.readAssistantMcpDiscoveryMetadataSelectedEntitySet = readAssistantMcpDiscoveryMetadataSelectedEntitySet;
|
||||||
|
|
@ -91,6 +92,39 @@ function readAssistantMcpDiscoveryDerivedMetadataSurface(debug) {
|
||||||
const pilot = toRecordObject(bridge?.pilot);
|
const pilot = toRecordObject(bridge?.pilot);
|
||||||
return toRecordObject(pilot?.derived_metadata_surface);
|
return toRecordObject(pilot?.derived_metadata_surface);
|
||||||
}
|
}
|
||||||
|
function readAssistantMcpDiscoveryDerivedEntityResolution(debug) {
|
||||||
|
const bridge = readAssistantMcpDiscoveryBridge(debug);
|
||||||
|
const pilot = toRecordObject(bridge?.pilot);
|
||||||
|
return toRecordObject(pilot?.derived_entity_resolution);
|
||||||
|
}
|
||||||
|
function collectAssistantMcpDiscoveryEntityCandidates(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
||||||
|
const result = [];
|
||||||
|
const resolution = readAssistantMcpDiscoveryDerivedEntityResolution(debug);
|
||||||
|
const pushCandidate = (value) => {
|
||||||
|
const text = toNonEmptyString(value);
|
||||||
|
if (text && !result.includes(text)) {
|
||||||
|
result.push(text);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
pushCandidate(resolution?.resolved_entity);
|
||||||
|
pushCandidate(resolution?.requested_entity);
|
||||||
|
if (Array.isArray(resolution?.ambiguity_candidates)) {
|
||||||
|
for (const candidate of resolution.ambiguity_candidates) {
|
||||||
|
pushCandidate(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const discoveryMeaning = readAssistantMcpDiscoveryTurnMeaning(debug);
|
||||||
|
const explicitEntities = Array.isArray(discoveryMeaning?.explicit_entity_candidates)
|
||||||
|
? discoveryMeaning.explicit_entity_candidates
|
||||||
|
: [];
|
||||||
|
for (const entity of explicitEntities) {
|
||||||
|
pushCandidate(candidateValue(entity));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
function readAssistantMcpDiscoveryEntityCandidates(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
||||||
|
return collectAssistantMcpDiscoveryEntityCandidates(debug, toNonEmptyString);
|
||||||
|
}
|
||||||
function readAssistantMcpDiscoveryPilotScope(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
function readAssistantMcpDiscoveryPilotScope(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
||||||
const bridge = readAssistantMcpDiscoveryBridge(debug);
|
const bridge = readAssistantMcpDiscoveryBridge(debug);
|
||||||
const pilot = toRecordObject(bridge?.pilot);
|
const pilot = toRecordObject(bridge?.pilot);
|
||||||
|
|
@ -250,12 +284,9 @@ function readAddressDebugCounterparty(debug, toNonEmptyString = fallbackToNonEmp
|
||||||
if (String(debug?.anchor_type ?? "") === "counterparty") {
|
if (String(debug?.anchor_type ?? "") === "counterparty") {
|
||||||
return toNonEmptyString(debug?.anchor_value_resolved) ?? toNonEmptyString(debug?.anchor_value_raw);
|
return toNonEmptyString(debug?.anchor_value_resolved) ?? toNonEmptyString(debug?.anchor_value_raw);
|
||||||
}
|
}
|
||||||
const discoveryMeaning = readAssistantMcpDiscoveryTurnMeaning(debug);
|
const discoveryEntities = collectAssistantMcpDiscoveryEntityCandidates(debug, toNonEmptyString);
|
||||||
const explicitEntities = Array.isArray(discoveryMeaning?.explicit_entity_candidates)
|
for (const entity of discoveryEntities) {
|
||||||
? discoveryMeaning?.explicit_entity_candidates
|
const text = toNonEmptyString(entity);
|
||||||
: [];
|
|
||||||
for (const entity of explicitEntities) {
|
|
||||||
const text = candidateValue(entity);
|
|
||||||
if (text) {
|
if (text) {
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,21 @@ function firstEntityCandidate(pilot) {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
function explicitDateScope(pilot) {
|
||||||
|
const value = pilot.evidence.query_plan.turn_meaning_ref?.explicit_date_scope;
|
||||||
|
if (typeof value !== "string") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const normalized = value.trim();
|
||||||
|
return normalized.length > 0 ? normalized : null;
|
||||||
|
}
|
||||||
|
function documentOrMovementScopeRu(pilot) {
|
||||||
|
const entity = firstEntityCandidate(pilot);
|
||||||
|
const period = explicitDateScope(pilot);
|
||||||
|
const entityPart = entity ? ` по контрагенту ${entity}` : "";
|
||||||
|
const periodPart = period ? ` за ${period}` : " в проверенном окне";
|
||||||
|
return `${entityPart}${periodPart}`;
|
||||||
|
}
|
||||||
function isMovementLaneClarification(pilot) {
|
function isMovementLaneClarification(pilot) {
|
||||||
return (isMovementPilot(pilot) ||
|
return (isMovementPilot(pilot) ||
|
||||||
pilot.reason_codes.includes("planner_selected_movement_recipe") ||
|
pilot.reason_codes.includes("planner_selected_movement_recipe") ||
|
||||||
|
|
@ -190,7 +205,7 @@ function headlineFor(mode, pilot) {
|
||||||
return "По текущему каталожному поиску 1С точный контрагент пока не подтвержден.";
|
return "По текущему каталожному поиску 1С точный контрагент пока не подтвержден.";
|
||||||
}
|
}
|
||||||
if (isMovementPilot(pilot) && mode === "confirmed_with_bounded_inference") {
|
if (isMovementPilot(pilot) && mode === "confirmed_with_bounded_inference") {
|
||||||
return "По данным 1С найдены строки движений; ответ ограничен проверенным периодом и найденными строками.";
|
return `По движениям${documentOrMovementScopeRu(pilot)} в 1С найдены подтвержденные строки; ответ ограничен проверенным окном и найденными строками.`;
|
||||||
}
|
}
|
||||||
if (pilot.derived_metadata_surface && mode === "confirmed_with_bounded_inference") {
|
if (pilot.derived_metadata_surface && mode === "confirmed_with_bounded_inference") {
|
||||||
if (pilot.derived_metadata_surface.ambiguity_detected) {
|
if (pilot.derived_metadata_surface.ambiguity_detected) {
|
||||||
|
|
@ -208,7 +223,7 @@ function headlineFor(mode, pilot) {
|
||||||
if (pilot.derived_value_flow.value_flow_direction === "outgoing_supplier_payout") {
|
if (pilot.derived_value_flow.value_flow_direction === "outgoing_supplier_payout") {
|
||||||
return "По данным 1С найдены строки исходящих платежей/списаний; сумму и помесячную раскладку можно называть только в рамках проверенного периода и найденных строк.";
|
return "По данным 1С найдены строки исходящих платежей/списаний; сумму и помесячную раскладку можно называть только в рамках проверенного периода и найденных строк.";
|
||||||
}
|
}
|
||||||
return "По данным 1С найдены строки денежных движений; сумму и помесячную раскладку можно называть только в рамках проверенного периода и найденных строк.";
|
return "По данным 1С найдены строки входящих денежных поступлений; сумму и помесячную раскладку можно называть только в рамках проверенного периода и найденных строк.";
|
||||||
}
|
}
|
||||||
if (pilot.derived_bidirectional_value_flow && mode === "confirmed_with_bounded_inference") {
|
if (pilot.derived_bidirectional_value_flow && mode === "confirmed_with_bounded_inference") {
|
||||||
return "По данным 1С найдены строки входящих и исходящих денежных движений; нетто можно называть только как расчет по найденным строкам и проверенному периоду.";
|
return "По данным 1С найдены строки входящих и исходящих денежных движений; нетто можно называть только как расчет по найденным строкам и проверенному периоду.";
|
||||||
|
|
@ -217,14 +232,23 @@ function headlineFor(mode, pilot) {
|
||||||
if (pilot.derived_value_flow.value_flow_direction === "outgoing_supplier_payout") {
|
if (pilot.derived_value_flow.value_flow_direction === "outgoing_supplier_payout") {
|
||||||
return "По данным 1С найдены строки исходящих платежей/списаний; сумму можно называть только в рамках проверенного периода и найденных строк.";
|
return "По данным 1С найдены строки исходящих платежей/списаний; сумму можно называть только в рамках проверенного периода и найденных строк.";
|
||||||
}
|
}
|
||||||
return "По данным 1С найдены строки денежных движений; сумму можно называть только в рамках проверенного периода и найденных строк.";
|
return "По данным 1С найдены строки входящих денежных поступлений; сумму можно называть только в рамках проверенного периода и найденных строк.";
|
||||||
}
|
}
|
||||||
if (isDocumentPilot(pilot) && mode === "confirmed_with_bounded_inference") {
|
if (isDocumentPilot(pilot) && mode === "confirmed_with_bounded_inference") {
|
||||||
return "По данным 1С найдены строки документов; ответ ограничен проверенным периодом и найденными строками.";
|
return `По документам${documentOrMovementScopeRu(pilot)} в 1С найдены подтвержденные строки; ответ ограничен проверенным окном и найденными строками.`;
|
||||||
|
}
|
||||||
|
if (isMovementPilot(pilot) && mode === "confirmed_with_bounded_inference") {
|
||||||
|
return `По движениям${documentOrMovementScopeRu(pilot)} в 1С найдены подтвержденные строки; ответ ограничен проверенным окном и найденными строками.`;
|
||||||
}
|
}
|
||||||
if (mode === "confirmed_with_bounded_inference") {
|
if (mode === "confirmed_with_bounded_inference") {
|
||||||
return "По данным 1С есть подтвержденная активность; длительность можно оценивать только как вывод из этих строк.";
|
return "По данным 1С есть подтвержденная активность; длительность можно оценивать только как вывод из этих строк.";
|
||||||
}
|
}
|
||||||
|
if (isDocumentPilot(pilot) && mode === "bounded_inference_only") {
|
||||||
|
return `По документам${documentOrMovementScopeRu(pilot)} полный срез не подтвержден; пока есть только ограниченная граница проверенного окна 1С.`;
|
||||||
|
}
|
||||||
|
if (isMovementPilot(pilot) && mode === "bounded_inference_only") {
|
||||||
|
return `По движениям${documentOrMovementScopeRu(pilot)} полный срез не подтвержден; пока есть только ограниченная граница проверенного окна 1С.`;
|
||||||
|
}
|
||||||
if (mode === "bounded_inference_only") {
|
if (mode === "bounded_inference_only") {
|
||||||
return "Точный факт не подтвержден, но есть ограниченная оценка по найденной активности в 1С.";
|
return "Точный факт не подтвержден, но есть ограниченная оценка по найденной активности в 1С.";
|
||||||
}
|
}
|
||||||
|
|
@ -436,13 +460,15 @@ function derivedValueFlowConfirmedLine(pilot) {
|
||||||
}
|
}
|
||||||
const counterparty = flow.counterparty ? ` по контрагенту ${flow.counterparty}` : "";
|
const counterparty = flow.counterparty ? ` по контрагенту ${flow.counterparty}` : "";
|
||||||
const period = flow.period_scope ? ` за период ${flow.period_scope}` : " в проверенном окне";
|
const period = flow.period_scope ? ` за период ${flow.period_scope}` : " в проверенном окне";
|
||||||
const movementLabel = flow.value_flow_direction === "outgoing_supplier_payout" ? "исходящих платежей/списаний" : "денежных движений";
|
const movementLabel = flow.value_flow_direction === "outgoing_supplier_payout"
|
||||||
|
? "исходящих платежей/списаний"
|
||||||
|
: "входящих денежных поступлений";
|
||||||
const totalLabel = flow.value_flow_direction === "outgoing_supplier_payout"
|
const totalLabel = flow.value_flow_direction === "outgoing_supplier_payout"
|
||||||
? "сумма исходящих платежей/списаний составляет"
|
? "сумма исходящих платежей/списаний составляет"
|
||||||
: "сумма составляет";
|
: "сумма входящих денежных поступлений составляет";
|
||||||
const caveat = flow.value_flow_direction === "outgoing_supplier_payout"
|
const caveat = flow.value_flow_direction === "outgoing_supplier_payout"
|
||||||
? "Это расчет по найденным строкам 1С, а не подтверждение полного объема платежей вне проверенного окна."
|
? "Это расчет по найденным строкам 1С, а не подтверждение полного объема платежей вне проверенного окна."
|
||||||
: "Это расчет по найденным строкам 1С, а не подтверждение полного оборота вне проверенного окна.";
|
: "Это расчет по найденным строкам 1С, а не подтверждение полного объема поступлений вне проверенного окна.";
|
||||||
const dates = flow.first_movement_date && flow.latest_movement_date
|
const dates = flow.first_movement_date && flow.latest_movement_date
|
||||||
? ` Первая найденная дата движения: ${flow.first_movement_date}; последняя: ${flow.latest_movement_date}.`
|
? ` Первая найденная дата движения: ${flow.first_movement_date}; последняя: ${flow.latest_movement_date}.`
|
||||||
: "";
|
: "";
|
||||||
|
|
|
||||||
|
|
@ -1267,25 +1267,26 @@ function buildLifecycleConfirmedFacts(result, counterparty) {
|
||||||
: "1C activity rows were found for the requested counterparty scope"
|
: "1C activity rows were found for the requested counterparty scope"
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
function buildDocumentConfirmedFacts(result, counterparty) {
|
function checkedCounterpartySuffixRu(counterparty) {
|
||||||
|
return counterparty ? ` по контрагенту ${counterparty}` : "";
|
||||||
|
}
|
||||||
|
function checkedPeriodSuffixRu(periodScope) {
|
||||||
|
return periodScope ? ` за ${periodScope}` : " в проверенном окне";
|
||||||
|
}
|
||||||
|
function uncheckedPeriodBoundaryRu(periodScope) {
|
||||||
|
return periodScope ? ` вне периода ${periodScope}` : " без явно проверенного периода";
|
||||||
|
}
|
||||||
|
function buildDocumentConfirmedFacts(result, counterparty, periodScope) {
|
||||||
if (result.error || result.matched_rows <= 0) {
|
if (result.error || result.matched_rows <= 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return [
|
return [`В 1С найдены строки документов${checkedCounterpartySuffixRu(counterparty)}${checkedPeriodSuffixRu(periodScope)}.`];
|
||||||
counterparty
|
|
||||||
? `1C document rows were found for counterparty ${counterparty}`
|
|
||||||
: "1C document rows were found for the requested scope"
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
function buildMovementConfirmedFacts(result, counterparty) {
|
function buildMovementConfirmedFacts(result, counterparty, periodScope) {
|
||||||
if (result.error || result.matched_rows <= 0) {
|
if (result.error || result.matched_rows <= 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return [
|
return [`В 1С найдены строки движений${checkedCounterpartySuffixRu(counterparty)}${checkedPeriodSuffixRu(periodScope)}.`];
|
||||||
counterparty
|
|
||||||
? `1C movement rows were found for counterparty ${counterparty}`
|
|
||||||
: "1C movement rows were found for the requested scope"
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
function buildValueFlowConfirmedFacts(result, counterparty, direction) {
|
function buildValueFlowConfirmedFacts(result, counterparty, direction) {
|
||||||
if (result.error || result.matched_rows <= 0) {
|
if (result.error || result.matched_rows <= 0) {
|
||||||
|
|
@ -1325,17 +1326,31 @@ function buildLifecycleInferredFacts(result) {
|
||||||
}
|
}
|
||||||
return ["Business activity duration may be inferred from first and latest confirmed 1C activity rows"];
|
return ["Business activity duration may be inferred from first and latest confirmed 1C activity rows"];
|
||||||
}
|
}
|
||||||
function buildDocumentInferredFacts(result) {
|
function buildDocumentInferredFacts(result, counterparty, periodScope) {
|
||||||
if (result.error || result.fetched_rows <= 0) {
|
if (result.error || result.fetched_rows <= 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return ["Counterparty document evidence is limited to confirmed 1C document rows in the checked scope"];
|
if (result.matched_rows <= 0) {
|
||||||
|
return [
|
||||||
|
`По документам${checkedCounterpartySuffixRu(counterparty)}${checkedPeriodSuffixRu(periodScope)} удалось проверить только ограниченный срез 1С; подтвержденных строк документов этим поиском не найдено.`
|
||||||
|
];
|
||||||
}
|
}
|
||||||
function buildMovementInferredFacts(result) {
|
return [
|
||||||
|
`Срез документов${checkedCounterpartySuffixRu(counterparty)}${checkedPeriodSuffixRu(periodScope)} ограничен только подтвержденными строками документов, найденными этим поиском.`
|
||||||
|
];
|
||||||
|
}
|
||||||
|
function buildMovementInferredFacts(result, counterparty, periodScope) {
|
||||||
if (result.error || result.fetched_rows <= 0) {
|
if (result.error || result.fetched_rows <= 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return ["Counterparty movement evidence is limited to confirmed 1C movement rows in the checked scope"];
|
if (result.matched_rows <= 0) {
|
||||||
|
return [
|
||||||
|
`По движениям${checkedCounterpartySuffixRu(counterparty)}${checkedPeriodSuffixRu(periodScope)} удалось проверить только ограниченный срез 1С; подтвержденных строк движений этим поиском не найдено.`
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
`Срез движений${checkedCounterpartySuffixRu(counterparty)}${checkedPeriodSuffixRu(periodScope)} ограничен только подтвержденными строками движений, найденными этим поиском.`
|
||||||
|
];
|
||||||
}
|
}
|
||||||
function buildValueFlowInferredFacts(derived) {
|
function buildValueFlowInferredFacts(derived) {
|
||||||
if (!derived) {
|
if (!derived) {
|
||||||
|
|
@ -1372,18 +1387,14 @@ function buildBidirectionalValueFlowInferredFacts(derived) {
|
||||||
function buildLifecycleUnknownFacts() {
|
function buildLifecycleUnknownFacts() {
|
||||||
return ["Legal registration date is not proven by this MCP discovery pilot"];
|
return ["Legal registration date is not proven by this MCP discovery pilot"];
|
||||||
}
|
}
|
||||||
function buildDocumentUnknownFacts(periodScope) {
|
function buildDocumentUnknownFacts(periodScope, counterparty) {
|
||||||
return [
|
return [
|
||||||
periodScope
|
`Полный исторический срез документов${checkedCounterpartySuffixRu(counterparty)}${uncheckedPeriodBoundaryRu(periodScope)} этим поиском не подтвержден.`
|
||||||
? "Full document history outside the checked period is not proven by this MCP discovery pilot"
|
|
||||||
: "Full document history is not proven without an explicit checked period"
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
function buildMovementUnknownFacts(periodScope) {
|
function buildMovementUnknownFacts(periodScope, counterparty) {
|
||||||
return [
|
return [
|
||||||
periodScope
|
`Полный исторический срез движений${checkedCounterpartySuffixRu(counterparty)}${uncheckedPeriodBoundaryRu(periodScope)} этим поиском не подтвержден.`
|
||||||
? "Full movement history outside the checked period is not proven by this MCP discovery pilot"
|
|
||||||
: "Full movement history is not proven without an explicit checked period"
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
function buildValueFlowUnknownFacts(periodScope, direction, derived) {
|
function buildValueFlowUnknownFacts(periodScope, direction, derived) {
|
||||||
|
|
@ -1749,9 +1760,9 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
|
||||||
const evidence = (0, assistantMcpDiscoveryPolicy_1.resolveAssistantMcpDiscoveryEvidence)({
|
const evidence = (0, assistantMcpDiscoveryPolicy_1.resolveAssistantMcpDiscoveryEvidence)({
|
||||||
plan: planner.discovery_plan,
|
plan: planner.discovery_plan,
|
||||||
probeResults,
|
probeResults,
|
||||||
confirmedFacts: queryResult ? buildDocumentConfirmedFacts(queryResult, counterparty) : [],
|
confirmedFacts: queryResult ? buildDocumentConfirmedFacts(queryResult, counterparty, dateScope) : [],
|
||||||
inferredFacts: queryResult ? buildDocumentInferredFacts(queryResult) : [],
|
inferredFacts: queryResult ? buildDocumentInferredFacts(queryResult, counterparty, dateScope) : [],
|
||||||
unknownFacts: buildDocumentUnknownFacts(dateScope),
|
unknownFacts: buildDocumentUnknownFacts(dateScope, counterparty),
|
||||||
sourceRowsSummary,
|
sourceRowsSummary,
|
||||||
queryLimitations,
|
queryLimitations,
|
||||||
recommendedNextProbe: "explain_evidence_basis"
|
recommendedNextProbe: "explain_evidence_basis"
|
||||||
|
|
@ -1831,9 +1842,9 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
|
||||||
const evidence = (0, assistantMcpDiscoveryPolicy_1.resolveAssistantMcpDiscoveryEvidence)({
|
const evidence = (0, assistantMcpDiscoveryPolicy_1.resolveAssistantMcpDiscoveryEvidence)({
|
||||||
plan: planner.discovery_plan,
|
plan: planner.discovery_plan,
|
||||||
probeResults,
|
probeResults,
|
||||||
confirmedFacts: queryResult ? buildMovementConfirmedFacts(queryResult, counterparty) : [],
|
confirmedFacts: queryResult ? buildMovementConfirmedFacts(queryResult, counterparty, dateScope) : [],
|
||||||
inferredFacts: queryResult ? buildMovementInferredFacts(queryResult) : [],
|
inferredFacts: queryResult ? buildMovementInferredFacts(queryResult, counterparty, dateScope) : [],
|
||||||
unknownFacts: buildMovementUnknownFacts(dateScope),
|
unknownFacts: buildMovementUnknownFacts(dateScope, counterparty),
|
||||||
sourceRowsSummary,
|
sourceRowsSummary,
|
||||||
queryLimitations,
|
queryLimitations,
|
||||||
recommendedNextProbe: "explain_evidence_basis"
|
recommendedNextProbe: "explain_evidence_basis"
|
||||||
|
|
|
||||||
|
|
@ -70,10 +70,10 @@ function localizeLine(value) {
|
||||||
}
|
}
|
||||||
const valueFlowMatch = value.match(/^1C value-flow rows were found for counterparty\s+(.+)$/i);
|
const valueFlowMatch = value.match(/^1C value-flow rows were found for counterparty\s+(.+)$/i);
|
||||||
if (valueFlowMatch) {
|
if (valueFlowMatch) {
|
||||||
return `В 1С найдены строки денежных движений по контрагенту ${valueFlowMatch[1]}.`;
|
return `В 1С найдены строки входящих денежных поступлений по контрагенту ${valueFlowMatch[1]}.`;
|
||||||
}
|
}
|
||||||
if (/^1C value-flow rows were found for the requested counterparty scope$/i.test(value)) {
|
if (/^1C value-flow rows were found for the requested counterparty scope$/i.test(value)) {
|
||||||
return "В 1С найдены строки денежных движений по запрошенному контрагентскому контуру.";
|
return "В 1С найдены строки входящих денежных поступлений по запрошенному контрагентскому контуру.";
|
||||||
}
|
}
|
||||||
const documentRowsMatch = value.match(/^1C document rows were found for counterparty\s+(.+)$/i);
|
const documentRowsMatch = value.match(/^1C document rows were found for counterparty\s+(.+)$/i);
|
||||||
if (documentRowsMatch) {
|
if (documentRowsMatch) {
|
||||||
|
|
@ -118,10 +118,10 @@ function localizeLine(value) {
|
||||||
return "Срез движений ограничен только подтвержденными строками движений в проверенном окне.";
|
return "Срез движений ограничен только подтвержденными строками движений в проверенном окне.";
|
||||||
}
|
}
|
||||||
if (/^Counterparty value-flow total was calculated from confirmed 1C movement rows$/i.test(value)) {
|
if (/^Counterparty value-flow total was calculated from confirmed 1C movement rows$/i.test(value)) {
|
||||||
return "Сумма рассчитана только по подтвержденным строкам денежных движений в 1С.";
|
return "Сумма входящих поступлений рассчитана только по подтвержденным строкам поступлений в 1С.";
|
||||||
}
|
}
|
||||||
if (/^Counterparty monthly value-flow breakdown was grouped by month over confirmed 1C movement rows$/i.test(value)) {
|
if (/^Counterparty monthly value-flow breakdown was grouped by month over confirmed 1C movement rows$/i.test(value)) {
|
||||||
return "Помесячная раскладка денежного потока сгруппирована только по подтвержденным строкам движений 1С.";
|
return "Помесячная раскладка входящих поступлений построена только по подтвержденным строкам поступлений в 1С.";
|
||||||
}
|
}
|
||||||
if (/^Counterparty supplier-payout total was calculated from confirmed 1C outgoing payment rows$/i.test(value)) {
|
if (/^Counterparty supplier-payout total was calculated from confirmed 1C outgoing payment rows$/i.test(value)) {
|
||||||
return "Сумма исходящих платежей рассчитана только по подтвержденным строкам списаний в 1С.";
|
return "Сумма исходящих платежей рассчитана только по подтвержденным строкам списаний в 1С.";
|
||||||
|
|
@ -186,10 +186,10 @@ function localizeLine(value) {
|
||||||
return "Полное покрытие запрошенного периода по двустороннему денежному потоку не подтверждено: хотя бы одна сторона проверки достигла лимита найденных строк.";
|
return "Полное покрытие запрошенного периода по двустороннему денежному потоку не подтверждено: хотя бы одна сторона проверки достигла лимита найденных строк.";
|
||||||
}
|
}
|
||||||
if (/^Full turnover outside the checked period is not proven by this MCP discovery pilot$/i.test(value)) {
|
if (/^Full turnover outside the checked period is not proven by this MCP discovery pilot$/i.test(value)) {
|
||||||
return "Полный оборот вне проверенного периода этим поиском не подтвержден.";
|
return "Полный объем входящих поступлений вне проверенного периода этим поиском не подтвержден.";
|
||||||
}
|
}
|
||||||
if (/^Full all-time turnover is not proven without an explicit checked period$/i.test(value)) {
|
if (/^Full all-time turnover is not proven without an explicit checked period$/i.test(value)) {
|
||||||
return "Полный оборот за все время без явно проверенного периода не подтвержден.";
|
return "Полный объем входящих поступлений за все время без явно проверенного периода не подтвержден.";
|
||||||
}
|
}
|
||||||
if (/^Full document history outside the checked period is not proven by this MCP discovery pilot$/i.test(value)) {
|
if (/^Full document history outside the checked period is not proven by this MCP discovery pilot$/i.test(value)) {
|
||||||
return "Полный исторический срез документов вне проверенного периода этим поиском не подтвержден.";
|
return "Полный исторический срез документов вне проверенного периода этим поиском не подтвержден.";
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,19 @@ function pushUnique(target, value) {
|
||||||
target.push(text);
|
target.push(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function isReferentialEntityPlaceholder(value) {
|
||||||
|
return /^(?:\u043d\u0435\u043c\u0443|\u043d\u0435\u0439|\u043d\u0438\u043c|\u043d\u0438\u043c\u0438|\u0435\u0433\u043e|\u0435\u0435|\u0435\u0451|\u0438\u0445|\u044d\u0442\u043e\u043c\u0443|\u044d\u0442\u043e\u0439|\u044d\u0442\u0438\u043c|\u044d\u0442\u0438\u043c\u0438|\u044d\u0442\u043e\u043c)$/iu.test(value.trim());
|
||||||
|
}
|
||||||
|
function pushScopedEntityCandidate(target, value, groundedFollowupEntity) {
|
||||||
|
const text = candidateValue(value);
|
||||||
|
if (!text) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (groundedFollowupEntity && isReferentialEntityPlaceholder(text)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pushUnique(target, text);
|
||||||
|
}
|
||||||
function canonicalizeEntityResolutionCandidate(value) {
|
function canonicalizeEntityResolutionCandidate(value) {
|
||||||
return normalizeEntityResolutionCandidate(value)
|
return normalizeEntityResolutionCandidate(value)
|
||||||
.replace(/^(?:\u0441\s+\u043d\u0430\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u0435\u043c\s+)/iu, "")
|
.replace(/^(?:\u0441\s+\u043d\u0430\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u0435\u043c\s+)/iu, "")
|
||||||
|
|
@ -143,6 +156,13 @@ function mapPilotScopeToFollowupMeaning(pilotScope) {
|
||||||
unsupported: "movement_evidence"
|
unsupported: "movement_evidence"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (pilotScope === "counterparty_document_evidence_query_documents_v1") {
|
||||||
|
return {
|
||||||
|
domain: "documents",
|
||||||
|
action: "list_documents",
|
||||||
|
unsupported: "document_evidence"
|
||||||
|
};
|
||||||
|
}
|
||||||
if (pilotScope === "counterparty_supplier_payout_query_movements_v1") {
|
if (pilotScope === "counterparty_supplier_payout_query_movements_v1") {
|
||||||
return {
|
return {
|
||||||
domain: "counterparty_value",
|
domain: "counterparty_value",
|
||||||
|
|
@ -225,7 +245,8 @@ function collectFollowupDiscoverySeed(followupContext) {
|
||||||
toNonEmptyString(rootFilters?.counterparty) ??
|
toNonEmptyString(rootFilters?.counterparty) ??
|
||||||
(toNonEmptyString(followupContext?.previous_anchor_type) === "counterparty"
|
(toNonEmptyString(followupContext?.previous_anchor_type) === "counterparty"
|
||||||
? toNonEmptyString(followupContext?.previous_anchor_value)
|
? toNonEmptyString(followupContext?.previous_anchor_value)
|
||||||
: null);
|
: null) ??
|
||||||
|
(discoveryEntities[0] ?? null);
|
||||||
const organization = toNonEmptyString(previousFilters?.organization) ??
|
const organization = toNonEmptyString(previousFilters?.organization) ??
|
||||||
toNonEmptyString(rootFilters?.organization) ??
|
toNonEmptyString(rootFilters?.organization) ??
|
||||||
(toNonEmptyString(followupContext?.previous_anchor_type) === "organization"
|
(toNonEmptyString(followupContext?.previous_anchor_type) === "organization"
|
||||||
|
|
@ -264,7 +285,7 @@ function hasLifecycleSignal(text) {
|
||||||
return /(?:сколько\s+лет|как\s+давно|давно\s+ли|возраст|перв(?:ая|ый)\s+актив|когда\s+начал|когда\s+появ|lifecycle|activity\s+duration|business\s+age|how\s+long)/iu.test(text);
|
return /(?:сколько\s+лет|как\s+давно|давно\s+ли|возраст|перв(?:ая|ый)\s+актив|когда\s+начал|когда\s+появ|lifecycle|activity\s+duration|business\s+age|how\s+long)/iu.test(text);
|
||||||
}
|
}
|
||||||
function hasValueFlowSignal(text) {
|
function hasValueFlowSignal(text) {
|
||||||
return /(?:оборот|выручк|оплат|плат[её]ж|заплат|перечисл|списан|расход|исходящ|поступлен|денежн[а-яёa-z0-9_-]*\s+поток|supplier|value[-\s]?flow|turnover|revenue|payment|payout|outflow|cash\s+flow)/iu.test(text);
|
return /(?:оборот|выручк|оплат|плат[её]ж|заплат|перечисл|списан|расход|исходящ|входящ|получ(?:ил|ено|ен)|поступил|поступлен|денежн[а-яёa-z0-9_-]*\s+поток|supplier|value[-\s]?flow|turnover|revenue|payment|payout|outflow|cash\s+flow)/iu.test(text);
|
||||||
}
|
}
|
||||||
function hasPayoutSignal(text) {
|
function hasPayoutSignal(text) {
|
||||||
return /(?:\bмы\s+(?:за)?плат|(?:за)?платил|оплатил|перечисл|списан|расход|поставщик|исходящ|supplier|payout|outflow|paid\s+to|payment\s+to)/iu.test(text);
|
return /(?:\bмы\s+(?:за)?плат|(?:за)?платил|оплатил|перечисл|списан|расход|поставщик|исходящ|supplier|payout|outflow|paid\s+to|payment\s+to)/iu.test(text);
|
||||||
|
|
@ -291,6 +312,12 @@ function hasDocumentEvidenceFollowupSignal(text) {
|
||||||
function hasMovementEvidenceFollowupSignal(text) {
|
function hasMovementEvidenceFollowupSignal(text) {
|
||||||
return /(?:\u043f\u043e\s+\u0434\u0432\u0438\u0436\u0435\u043d(?:\u0438\u044f\u043c|\u0438\u044f)?|\u0434\u0430\u0432\u0430\u0439\s+\u0434\u0432\u0438\u0436\u0435\u043d(?:\u0438\u044f|\u0438\u0435)|\u0438\u0449\u0438\s+\u0434\u0432\u0438\u0436\u0435\u043d(?:\u0438\u044f|\u0438\u0435)|\u043f\u043e\u043a\u0430\u0436\u0438\s+\u0434\u0432\u0438\u0436\u0435\u043d(?:\u0438\u044f|\u0438\u0435)|\u0431\u0430\u043d\u043a\u043e\u0432\u0441\u043a(?:\u0438\u0435|\u0438\u0439)\s+\u0434\u0432\u0438\u0436\u0435\u043d(?:\u0438\u044f|\u0438\u0435)|(?:\u043f\u043e\u043a\u0430\u0436\u0438|\u043a\u0430\u043a\u0438\u0435|\u0441\u043f\u0438\u0441\u043e\u043a|\u0434\u0430\u0439|\u0438\u0449\u0438)\s+(?:\u043f\u043b\u0430\u0442[еe]\u0436(?:\u0438|\u0438)?|\u043e\u043f\u0435\u0440\u0430\u0446(?:\u0438\u0438|\u0438\u044e)|\u043f\u0440\u043e\u0432\u043e\u0434\u043a(?:\u0438|\u0430)|\u0441\u043f\u0438\u0441\u0430\u043d(?:\u0438\u044f|\u0438\u0435)|\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d(?:\u0438\u044f|\u0438\u0435)|payment(?:s)?|transaction(?:s)?|operation(?:s)?|posting(?:s)?|bank\s+operation(?:s)?)|movement(?:s)?\s+(?:then|next)?|(?:then|next)\s+movements?|go\s+to\s+movements?)/iu.test(text);
|
return /(?:\u043f\u043e\s+\u0434\u0432\u0438\u0436\u0435\u043d(?:\u0438\u044f\u043c|\u0438\u044f)?|\u0434\u0430\u0432\u0430\u0439\s+\u0434\u0432\u0438\u0436\u0435\u043d(?:\u0438\u044f|\u0438\u0435)|\u0438\u0449\u0438\s+\u0434\u0432\u0438\u0436\u0435\u043d(?:\u0438\u044f|\u0438\u0435)|\u043f\u043e\u043a\u0430\u0436\u0438\s+\u0434\u0432\u0438\u0436\u0435\u043d(?:\u0438\u044f|\u0438\u0435)|\u0431\u0430\u043d\u043a\u043e\u0432\u0441\u043a(?:\u0438\u0435|\u0438\u0439)\s+\u0434\u0432\u0438\u0436\u0435\u043d(?:\u0438\u044f|\u0438\u0435)|(?:\u043f\u043e\u043a\u0430\u0436\u0438|\u043a\u0430\u043a\u0438\u0435|\u0441\u043f\u0438\u0441\u043e\u043a|\u0434\u0430\u0439|\u0438\u0449\u0438)\s+(?:\u043f\u043b\u0430\u0442[еe]\u0436(?:\u0438|\u0438)?|\u043e\u043f\u0435\u0440\u0430\u0446(?:\u0438\u0438|\u0438\u044e)|\u043f\u0440\u043e\u0432\u043e\u0434\u043a(?:\u0438|\u0430)|\u0441\u043f\u0438\u0441\u0430\u043d(?:\u0438\u044f|\u0438\u0435)|\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d(?:\u0438\u044f|\u0438\u0435)|payment(?:s)?|transaction(?:s)?|operation(?:s)?|posting(?:s)?|bank\s+operation(?:s)?)|movement(?:s)?\s+(?:then|next)?|(?:then|next)\s+movements?|go\s+to\s+movements?)/iu.test(text);
|
||||||
}
|
}
|
||||||
|
function hasPronounDocumentEvidenceFollowupSignal(text) {
|
||||||
|
return /(?:\u043f\u043e\s+(?:\u043d\u0435\u043c\u0443|\u043d\u0435\u0439|\u044d\u0442\u043e\u043c\u0443|\u044d\u0442\u043e\u0439)\s+(?:\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442(?:\u0430\u043c|\u044b)?|\u0441\u0447(?:[Рµe]С‚|\u0435\u0442)[-\u2011 ]?\u0444\u0430\u043a\u0442\u0443\u0440(?:\u044b|\u0430)?|\u043d\u0430\u043a\u043b\u0430\u0434\u043d(?:\u044b\u0435|\u0430\u044f)?|\u0430\u043a\u0442(?:\u044b)?))/iu.test(text);
|
||||||
|
}
|
||||||
|
function hasPronounMovementEvidenceFollowupSignal(text) {
|
||||||
|
return /(?:\u043f\u043e\s+(?:\u043d\u0435\u043c\u0443|\u043d\u0435\u0439|\u044d\u0442\u043e\u043c\u0443|\u044d\u0442\u043e\u0439)\s+(?:\u0434\u0432\u0438\u0436\u0435\u043d(?:\u0438\u044f\u043c|\u0438\u044f)?|\u043f\u043b\u0430\u0442[Рµe]\u0436(?:\u0430\u043c|\u0438)?|\u043e\u043f\u0435\u0440\u0430\u0446(?:\u0438\u044f\u043c|\u0438\u0438|\u0438\u044e)|\u043f\u0440\u043e\u0432\u043e\u0434\u043a(?:\u0430\u043c|\u0438)|\u0441\u043f\u0438\u0441\u0430\u043d(?:\u0438\u044f\u043c|\u0438\u0435)|\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d(?:\u0438\u044f\u043c|\u0438\u0435)))/iu.test(text);
|
||||||
|
}
|
||||||
function hasMetadataDownstreamContinuationSignal(text) {
|
function hasMetadataDownstreamContinuationSignal(text) {
|
||||||
return /(?:\u0434\u0430\u0432\u0430\u0439\s+\u0434\u0430\u043b\u044c\u0448\u0435|\u0438\u0434(?:\u0435|\u0451)\u043c\s+\u0434\u0430\u043b\u044c\u0448\u0435|\u043f\u043e\u0448\u043b(?:\u0438|\u0451\u043c)\s+\u0434\u0430\u043b\u044c\u0448\u0435|\u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0439|\u0438\u0449\u0438\s+\u0434\u0430\u043b\u044c\u0448\u0435|\u0438\u0449\u0438\s+\u0434\u0430\u043d\u043d\u044b\u0435|\u043f\u043e\u043a\u0430\u0436\u0438\s+\u0434\u0430\u043d\u043d\u044b\u0435|\u043f\u043e\u043a\u0430\u0436\u0438\s+\u0441\u0442\u0440\u043e\u043a\u0438|\u0433\u043b\u0443\u0431\u0436\u0435|\u0447\u0442\u043e\s+\u0434\u0430\u043b\u044c\u0448\u0435|continue|go\s+ahead|go\s+deeper|look\s+deeper|drill\s+down|show\s+(?:data|rows))/iu.test(text);
|
return /(?:\u0434\u0430\u0432\u0430\u0439\s+\u0434\u0430\u043b\u044c\u0448\u0435|\u0438\u0434(?:\u0435|\u0451)\u043c\s+\u0434\u0430\u043b\u044c\u0448\u0435|\u043f\u043e\u0448\u043b(?:\u0438|\u0451\u043c)\s+\u0434\u0430\u043b\u044c\u0448\u0435|\u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0439|\u0438\u0449\u0438\s+\u0434\u0430\u043b\u044c\u0448\u0435|\u0438\u0449\u0438\s+\u0434\u0430\u043d\u043d\u044b\u0435|\u043f\u043e\u043a\u0430\u0436\u0438\s+\u0434\u0430\u043d\u043d\u044b\u0435|\u043f\u043e\u043a\u0430\u0436\u0438\s+\u0441\u0442\u0440\u043e\u043a\u0438|\u0433\u043b\u0443\u0431\u0436\u0435|\u0447\u0442\u043e\s+\u0434\u0430\u043b\u044c\u0448\u0435|continue|go\s+ahead|go\s+deeper|look\s+deeper|drill\s+down|show\s+(?:data|rows))/iu.test(text);
|
||||||
}
|
}
|
||||||
|
|
@ -395,6 +422,9 @@ function semanticNeedFor(input) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
function shouldRunDiscovery(input) {
|
function shouldRunDiscovery(input) {
|
||||||
|
if (input.forceDiscoveryOverExplicitIntent && input.semanticDataNeed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (input.lifecycleSignal || input.unsupported) {
|
if (input.lifecycleSignal || input.unsupported) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -439,8 +469,8 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
const rawMetadataScopeHint = rawMetadataSignal ? metadataScopeHintFromRawText(rawText) : null;
|
const rawMetadataScopeHint = rawMetadataSignal ? metadataScopeHintFromRawText(rawText) : null;
|
||||||
const rawEntityCandidate = rawEntityResolutionSignal ? rawEntityResolutionCandidate(rawEntitySourceText) : null;
|
const rawEntityCandidate = rawEntityResolutionSignal ? rawEntityResolutionCandidate(rawEntitySourceText) : null;
|
||||||
const entityResolutionSignal = rawEntityResolutionSignal || Boolean(rawEntityCandidate);
|
const entityResolutionSignal = rawEntityResolutionSignal || Boolean(rawEntityCandidate);
|
||||||
const metadataDocumentHintSignal = hasDocumentEvidenceFollowupSignal(rawText);
|
const metadataDocumentHintSignal = hasDocumentEvidenceFollowupSignal(rawText) || hasPronounDocumentEvidenceFollowupSignal(rawText);
|
||||||
const metadataMovementHintSignal = hasMovementEvidenceFollowupSignal(rawText);
|
const metadataMovementHintSignal = hasMovementEvidenceFollowupSignal(rawText) || hasPronounMovementEvidenceFollowupSignal(rawText);
|
||||||
const rawDomain = toNonEmptyString(assistantTurnMeaning?.asked_domain_family);
|
const rawDomain = toNonEmptyString(assistantTurnMeaning?.asked_domain_family);
|
||||||
const rawAction = toNonEmptyString(assistantTurnMeaning?.asked_action_family);
|
const rawAction = toNonEmptyString(assistantTurnMeaning?.asked_action_family);
|
||||||
const rawAggregationAxis = toNonEmptyString(assistantTurnMeaning?.asked_aggregation_axis);
|
const rawAggregationAxis = toNonEmptyString(assistantTurnMeaning?.asked_aggregation_axis);
|
||||||
|
|
@ -485,6 +515,37 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
followupSeed.counterparty &&
|
followupSeed.counterparty &&
|
||||||
!rawLifecycleSignal &&
|
!rawLifecycleSignal &&
|
||||||
metadataMovementHintSignal);
|
metadataMovementHintSignal);
|
||||||
|
const entityResolutionGroundedDocumentFollowupApplicable = Boolean(followupSeed.pilotScope === "entity_resolution_search_v1" &&
|
||||||
|
(followupSeed.counterparty || followupSeed.discoveryEntity) &&
|
||||||
|
!rawLifecycleSignal &&
|
||||||
|
!rawValueFlowSignal &&
|
||||||
|
!rawMetadataSignal &&
|
||||||
|
metadataDocumentHintSignal);
|
||||||
|
const entityResolutionGroundedMovementFollowupApplicable = Boolean(followupSeed.pilotScope === "entity_resolution_search_v1" &&
|
||||||
|
(followupSeed.counterparty || followupSeed.discoveryEntity) &&
|
||||||
|
!rawLifecycleSignal &&
|
||||||
|
!rawValueFlowSignal &&
|
||||||
|
!rawMetadataSignal &&
|
||||||
|
metadataMovementHintSignal);
|
||||||
|
const groundedValueFlowFollowupApplicable = Boolean(rawValueFlowSignal &&
|
||||||
|
!rawLifecycleSignal &&
|
||||||
|
!rawMetadataSignal &&
|
||||||
|
(followupSeed.counterparty || followupSeed.discoveryEntity) &&
|
||||||
|
(followupSeed.pilotScope === "entity_resolution_search_v1" ||
|
||||||
|
followupSeed.pilotScope === "counterparty_document_evidence_query_documents_v1" ||
|
||||||
|
followupSeed.pilotScope === "counterparty_movement_evidence_query_movements_v1"));
|
||||||
|
const documentEvidenceGroundedMovementFollowupApplicable = Boolean(followupSeed.pilotScope === "counterparty_document_evidence_query_documents_v1" &&
|
||||||
|
(followupSeed.counterparty || followupSeed.discoveryEntity) &&
|
||||||
|
!rawLifecycleSignal &&
|
||||||
|
!rawValueFlowSignal &&
|
||||||
|
!rawMetadataSignal &&
|
||||||
|
metadataMovementHintSignal);
|
||||||
|
const movementEvidenceGroundedDocumentFollowupApplicable = Boolean(followupSeed.pilotScope === "counterparty_movement_evidence_query_movements_v1" &&
|
||||||
|
(followupSeed.counterparty || followupSeed.discoveryEntity) &&
|
||||||
|
!rawLifecycleSignal &&
|
||||||
|
!rawValueFlowSignal &&
|
||||||
|
!rawMetadataSignal &&
|
||||||
|
metadataDocumentHintSignal);
|
||||||
const metadataGroundedLaneContinuationApplicable = Boolean(followupSeed.pilotScope === "metadata_inspection_v1" &&
|
const metadataGroundedLaneContinuationApplicable = Boolean(followupSeed.pilotScope === "metadata_inspection_v1" &&
|
||||||
(followupSeed.metadataRouteFamily === "document_evidence" ||
|
(followupSeed.metadataRouteFamily === "document_evidence" ||
|
||||||
followupSeed.metadataRouteFamily === "movement_evidence") &&
|
followupSeed.metadataRouteFamily === "movement_evidence") &&
|
||||||
|
|
@ -529,10 +590,14 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
hasMetadataDownstreamContinuationSignal(rawText));
|
hasMetadataDownstreamContinuationSignal(rawText));
|
||||||
const metadataGroundedDocumentLaneApplicable = metadataGroundedDocumentFollowupApplicable ||
|
const metadataGroundedDocumentLaneApplicable = metadataGroundedDocumentFollowupApplicable ||
|
||||||
metadataAmbiguityResolvedDocumentFollowupApplicable ||
|
metadataAmbiguityResolvedDocumentFollowupApplicable ||
|
||||||
|
entityResolutionGroundedDocumentFollowupApplicable ||
|
||||||
|
movementEvidenceGroundedDocumentFollowupApplicable ||
|
||||||
(metadataGroundedLaneContinuationApplicable && followupSeed.metadataRouteFamily === "document_evidence") ||
|
(metadataGroundedLaneContinuationApplicable && followupSeed.metadataRouteFamily === "document_evidence") ||
|
||||||
metadataAmbiguityCollapsedDocumentLaneContinuationApplicable;
|
metadataAmbiguityCollapsedDocumentLaneContinuationApplicable;
|
||||||
const metadataGroundedMovementLaneApplicable = metadataGroundedMovementFollowupApplicable ||
|
const metadataGroundedMovementLaneApplicable = metadataGroundedMovementFollowupApplicable ||
|
||||||
metadataAmbiguityResolvedMovementFollowupApplicable ||
|
metadataAmbiguityResolvedMovementFollowupApplicable ||
|
||||||
|
entityResolutionGroundedMovementFollowupApplicable ||
|
||||||
|
documentEvidenceGroundedMovementFollowupApplicable ||
|
||||||
(metadataGroundedLaneContinuationApplicable && followupSeed.metadataRouteFamily === "movement_evidence") ||
|
(metadataGroundedLaneContinuationApplicable && followupSeed.metadataRouteFamily === "movement_evidence") ||
|
||||||
metadataAmbiguityCollapsedMovementLaneContinuationApplicable;
|
metadataAmbiguityCollapsedMovementLaneContinuationApplicable;
|
||||||
const effectiveMetadataFollowupSeedApplicable = metadataFollowupSeedApplicable &&
|
const effectiveMetadataFollowupSeedApplicable = metadataFollowupSeedApplicable &&
|
||||||
|
|
@ -586,7 +651,8 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
metadataSignal: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable,
|
metadataSignal: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable,
|
||||||
entityResolutionSignal
|
entityResolutionSignal
|
||||||
});
|
});
|
||||||
const entityCandidates = entityResolutionSignal ? [] : collectEntityCandidates(assistantTurnMeaning?.explicit_entity_candidates);
|
const groundedFollowupEntity = followupSeed.counterparty ?? followupSeed.discoveryEntity;
|
||||||
|
const entityCandidates = entityResolutionSignal ? [] : [];
|
||||||
if (entityResolutionSignal) {
|
if (entityResolutionSignal) {
|
||||||
pushNormalizedEntityResolutionCandidate(entityCandidates, rawEntityCandidate);
|
pushNormalizedEntityResolutionCandidate(entityCandidates, rawEntityCandidate);
|
||||||
for (const candidate of collectEntityCandidates(assistantTurnMeaning?.explicit_entity_candidates)) {
|
for (const candidate of collectEntityCandidates(assistantTurnMeaning?.explicit_entity_candidates)) {
|
||||||
|
|
@ -596,11 +662,20 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
pushNormalizedEntityResolutionCandidate(entityCandidates, followupSeed.counterparty);
|
pushNormalizedEntityResolutionCandidate(entityCandidates, followupSeed.counterparty);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
pushUnique(entityCandidates, predecomposeEntities.counterparty);
|
if (groundedFollowupEntity) {
|
||||||
pushUnique(entityCandidates, followupSeed.counterparty);
|
pushUnique(entityCandidates, groundedFollowupEntity);
|
||||||
pushUnique(entityCandidates, rawEntityCandidate);
|
|
||||||
}
|
}
|
||||||
if ((rawMetadataSignal || metadataFollowupSeedApplicable) && !followupSeed.counterparty) {
|
for (const candidate of collectEntityCandidates(assistantTurnMeaning?.explicit_entity_candidates)) {
|
||||||
|
pushScopedEntityCandidate(entityCandidates, candidate, groundedFollowupEntity);
|
||||||
|
}
|
||||||
|
pushScopedEntityCandidate(entityCandidates, predecomposeEntities.counterparty, groundedFollowupEntity);
|
||||||
|
if (!groundedFollowupEntity) {
|
||||||
|
pushScopedEntityCandidate(entityCandidates, followupSeed.counterparty, null);
|
||||||
|
pushScopedEntityCandidate(entityCandidates, followupSeed.discoveryEntity, null);
|
||||||
|
}
|
||||||
|
pushScopedEntityCandidate(entityCandidates, rawEntityCandidate, groundedFollowupEntity);
|
||||||
|
}
|
||||||
|
if ((rawMetadataSignal || metadataFollowupSeedApplicable) && !groundedFollowupEntity) {
|
||||||
pushUnique(entityCandidates, followupSeed.discoveryEntity);
|
pushUnique(entityCandidates, followupSeed.discoveryEntity);
|
||||||
pushUnique(entityCandidates, rawMetadataScopeHint);
|
pushUnique(entityCandidates, rawMetadataScopeHint);
|
||||||
}
|
}
|
||||||
|
|
@ -724,7 +799,12 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
effectiveMetadataFollowupSeedApplicable ||
|
effectiveMetadataFollowupSeedApplicable ||
|
||||||
metadataAmbiguityLaneClarificationApplicable ||
|
metadataAmbiguityLaneClarificationApplicable ||
|
||||||
metadataGroundedMovementLaneApplicable ||
|
metadataGroundedMovementLaneApplicable ||
|
||||||
metadataGroundedDocumentLaneApplicable
|
metadataGroundedDocumentLaneApplicable ||
|
||||||
|
groundedValueFlowFollowupApplicable,
|
||||||
|
forceDiscoveryOverExplicitIntent: metadataAmbiguityLaneClarificationApplicable ||
|
||||||
|
metadataGroundedMovementLaneApplicable ||
|
||||||
|
metadataGroundedDocumentLaneApplicable ||
|
||||||
|
groundedValueFlowFollowupApplicable
|
||||||
});
|
});
|
||||||
const hasTurnMeaning = Object.keys(cleanTurnMeaning).length > 0;
|
const hasTurnMeaning = Object.keys(cleanTurnMeaning).length > 0;
|
||||||
const sourceSignal = assistantTurnMeaning
|
const sourceSignal = assistantTurnMeaning
|
||||||
|
|
@ -791,6 +871,21 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
if (metadataAmbiguityResolvedMovementFollowupApplicable) {
|
if (metadataAmbiguityResolvedMovementFollowupApplicable) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_metadata_ambiguity_resolved_to_movement_lane");
|
pushReason(reasonCodes, "mcp_discovery_metadata_ambiguity_resolved_to_movement_lane");
|
||||||
}
|
}
|
||||||
|
if (entityResolutionGroundedDocumentFollowupApplicable) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_entity_resolution_grounded_document_followup");
|
||||||
|
}
|
||||||
|
if (entityResolutionGroundedMovementFollowupApplicable) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_entity_resolution_grounded_movement_followup");
|
||||||
|
}
|
||||||
|
if (groundedValueFlowFollowupApplicable) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_grounded_value_flow_followup");
|
||||||
|
}
|
||||||
|
if (documentEvidenceGroundedMovementFollowupApplicable) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_document_evidence_grounded_movement_followup");
|
||||||
|
}
|
||||||
|
if (movementEvidenceGroundedDocumentFollowupApplicable) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_movement_evidence_grounded_document_followup");
|
||||||
|
}
|
||||||
if (metadataGroundedLaneContinuationApplicable) {
|
if (metadataGroundedLaneContinuationApplicable) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_metadata_grounded_lane_continuation");
|
pushReason(reasonCodes, "mcp_discovery_metadata_grounded_lane_continuation");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -467,6 +467,7 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
const sourceDiscoveryMetadataSelectedEntitySet = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryMetadataSelectedEntitySet)(carryoverSourceDebug, deps.toNonEmptyString);
|
const sourceDiscoveryMetadataSelectedEntitySet = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryMetadataSelectedEntitySet)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||||
const sourceDiscoveryMetadataAmbiguityDetected = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryMetadataAmbiguityDetected)(carryoverSourceDebug);
|
const sourceDiscoveryMetadataAmbiguityDetected = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryMetadataAmbiguityDetected)(carryoverSourceDebug);
|
||||||
const sourceDiscoveryMetadataAmbiguityEntitySets = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryMetadataAmbiguityEntitySets)(carryoverSourceDebug, deps.toNonEmptyString);
|
const sourceDiscoveryMetadataAmbiguityEntitySets = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryMetadataAmbiguityEntitySets)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||||
|
const sourceDiscoveryEntityCandidates = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryEntityCandidates)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||||
const llmExplicitIntent = deps.toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
const llmExplicitIntent = deps.toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
||||||
const llmSelectedObjectScopeDetected = llmPreDecomposeMeta?.predecomposeContract?.semantics?.selected_object_scope_detected === true;
|
const llmSelectedObjectScopeDetected = llmPreDecomposeMeta?.predecomposeContract?.semantics?.selected_object_scope_detected === true;
|
||||||
const resolvedPrimaryIntent = deps.resolveAddressIntent(deps.repairAddressMojibake(String(userMessage ?? ""))).intent;
|
const resolvedPrimaryIntent = deps.resolveAddressIntent(deps.repairAddressMojibake(String(userMessage ?? ""))).intent;
|
||||||
|
|
@ -697,6 +698,7 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
previous_anchor_type: previousAnchorType ?? undefined,
|
previous_anchor_type: previousAnchorType ?? undefined,
|
||||||
previous_anchor_value: previousAnchor,
|
previous_anchor_value: previousAnchor,
|
||||||
previous_discovery_pilot_scope: sourceDiscoveryPilotScope ?? undefined,
|
previous_discovery_pilot_scope: sourceDiscoveryPilotScope ?? undefined,
|
||||||
|
previous_discovery_entity_candidates: sourceDiscoveryEntityCandidates.length > 0 ? sourceDiscoveryEntityCandidates : undefined,
|
||||||
previous_discovery_metadata_route_family: sourceDiscoveryMetadataRouteFamily ?? undefined,
|
previous_discovery_metadata_route_family: sourceDiscoveryMetadataRouteFamily ?? undefined,
|
||||||
previous_discovery_metadata_selected_entity_set: sourceDiscoveryMetadataSelectedEntitySet ?? undefined,
|
previous_discovery_metadata_selected_entity_set: sourceDiscoveryMetadataSelectedEntitySet ?? undefined,
|
||||||
previous_discovery_metadata_ambiguity_detected: sourceDiscoveryMetadataAmbiguityDetected || undefined,
|
previous_discovery_metadata_ambiguity_detected: sourceDiscoveryMetadataAmbiguityDetected || undefined,
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,53 @@ function readAssistantMcpDiscoveryDerivedMetadataSurface(
|
||||||
return toRecordObject(pilot?.derived_metadata_surface);
|
return toRecordObject(pilot?.derived_metadata_surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readAssistantMcpDiscoveryDerivedEntityResolution(
|
||||||
|
debug: Record<string, unknown> | null
|
||||||
|
): Record<string, unknown> | null {
|
||||||
|
const bridge = readAssistantMcpDiscoveryBridge(debug);
|
||||||
|
const pilot = toRecordObject(bridge?.pilot);
|
||||||
|
return toRecordObject(pilot?.derived_entity_resolution);
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectAssistantMcpDiscoveryEntityCandidates(
|
||||||
|
debug: Record<string, unknown> | null,
|
||||||
|
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||||
|
): string[] {
|
||||||
|
const result: string[] = [];
|
||||||
|
const resolution = readAssistantMcpDiscoveryDerivedEntityResolution(debug);
|
||||||
|
const pushCandidate = (value: unknown): void => {
|
||||||
|
const text = toNonEmptyString(value);
|
||||||
|
if (text && !result.includes(text)) {
|
||||||
|
result.push(text);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pushCandidate(resolution?.resolved_entity);
|
||||||
|
pushCandidate(resolution?.requested_entity);
|
||||||
|
if (Array.isArray(resolution?.ambiguity_candidates)) {
|
||||||
|
for (const candidate of resolution.ambiguity_candidates) {
|
||||||
|
pushCandidate(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const discoveryMeaning = readAssistantMcpDiscoveryTurnMeaning(debug);
|
||||||
|
const explicitEntities = Array.isArray(discoveryMeaning?.explicit_entity_candidates)
|
||||||
|
? discoveryMeaning.explicit_entity_candidates
|
||||||
|
: [];
|
||||||
|
for (const entity of explicitEntities) {
|
||||||
|
pushCandidate(candidateValue(entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readAssistantMcpDiscoveryEntityCandidates(
|
||||||
|
debug: Record<string, unknown> | null,
|
||||||
|
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||||
|
): string[] {
|
||||||
|
return collectAssistantMcpDiscoveryEntityCandidates(debug, toNonEmptyString);
|
||||||
|
}
|
||||||
|
|
||||||
export function readAssistantMcpDiscoveryPilotScope(
|
export function readAssistantMcpDiscoveryPilotScope(
|
||||||
debug: Record<string, unknown> | null,
|
debug: Record<string, unknown> | null,
|
||||||
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||||
|
|
@ -386,12 +433,9 @@ export function readAddressDebugCounterparty(
|
||||||
if (String(debug?.anchor_type ?? "") === "counterparty") {
|
if (String(debug?.anchor_type ?? "") === "counterparty") {
|
||||||
return toNonEmptyString(debug?.anchor_value_resolved) ?? toNonEmptyString(debug?.anchor_value_raw);
|
return toNonEmptyString(debug?.anchor_value_resolved) ?? toNonEmptyString(debug?.anchor_value_raw);
|
||||||
}
|
}
|
||||||
const discoveryMeaning = readAssistantMcpDiscoveryTurnMeaning(debug);
|
const discoveryEntities = collectAssistantMcpDiscoveryEntityCandidates(debug, toNonEmptyString);
|
||||||
const explicitEntities = Array.isArray(discoveryMeaning?.explicit_entity_candidates)
|
for (const entity of discoveryEntities) {
|
||||||
? discoveryMeaning?.explicit_entity_candidates
|
const text = toNonEmptyString(entity);
|
||||||
: [];
|
|
||||||
for (const entity of explicitEntities) {
|
|
||||||
const text = candidateValue(entity);
|
|
||||||
if (text) {
|
if (text) {
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -158,6 +158,23 @@ function firstEntityCandidate(pilot: AssistantMcpDiscoveryPilotExecutionContract
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function explicitDateScope(pilot: AssistantMcpDiscoveryPilotExecutionContract): string | null {
|
||||||
|
const value = pilot.evidence.query_plan.turn_meaning_ref?.explicit_date_scope;
|
||||||
|
if (typeof value !== "string") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const normalized = value.trim();
|
||||||
|
return normalized.length > 0 ? normalized : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function documentOrMovementScopeRu(pilot: AssistantMcpDiscoveryPilotExecutionContract): string {
|
||||||
|
const entity = firstEntityCandidate(pilot);
|
||||||
|
const period = explicitDateScope(pilot);
|
||||||
|
const entityPart = entity ? ` по контрагенту ${entity}` : "";
|
||||||
|
const periodPart = period ? ` за ${period}` : " в проверенном окне";
|
||||||
|
return `${entityPart}${periodPart}`;
|
||||||
|
}
|
||||||
|
|
||||||
function isMovementLaneClarification(pilot: AssistantMcpDiscoveryPilotExecutionContract): boolean {
|
function isMovementLaneClarification(pilot: AssistantMcpDiscoveryPilotExecutionContract): boolean {
|
||||||
return (
|
return (
|
||||||
isMovementPilot(pilot) ||
|
isMovementPilot(pilot) ||
|
||||||
|
|
@ -258,7 +275,7 @@ function headlineFor(mode: AssistantMcpDiscoveryAnswerMode, pilot: AssistantMcpD
|
||||||
return "По текущему каталожному поиску 1С точный контрагент пока не подтвержден.";
|
return "По текущему каталожному поиску 1С точный контрагент пока не подтвержден.";
|
||||||
}
|
}
|
||||||
if (isMovementPilot(pilot) && mode === "confirmed_with_bounded_inference") {
|
if (isMovementPilot(pilot) && mode === "confirmed_with_bounded_inference") {
|
||||||
return "По данным 1С найдены строки движений; ответ ограничен проверенным периодом и найденными строками.";
|
return `По движениям${documentOrMovementScopeRu(pilot)} в 1С найдены подтвержденные строки; ответ ограничен проверенным окном и найденными строками.`;
|
||||||
}
|
}
|
||||||
if (pilot.derived_metadata_surface && mode === "confirmed_with_bounded_inference") {
|
if (pilot.derived_metadata_surface && mode === "confirmed_with_bounded_inference") {
|
||||||
if (pilot.derived_metadata_surface.ambiguity_detected) {
|
if (pilot.derived_metadata_surface.ambiguity_detected) {
|
||||||
|
|
@ -276,7 +293,7 @@ function headlineFor(mode: AssistantMcpDiscoveryAnswerMode, pilot: AssistantMcpD
|
||||||
if (pilot.derived_value_flow.value_flow_direction === "outgoing_supplier_payout") {
|
if (pilot.derived_value_flow.value_flow_direction === "outgoing_supplier_payout") {
|
||||||
return "По данным 1С найдены строки исходящих платежей/списаний; сумму и помесячную раскладку можно называть только в рамках проверенного периода и найденных строк.";
|
return "По данным 1С найдены строки исходящих платежей/списаний; сумму и помесячную раскладку можно называть только в рамках проверенного периода и найденных строк.";
|
||||||
}
|
}
|
||||||
return "По данным 1С найдены строки денежных движений; сумму и помесячную раскладку можно называть только в рамках проверенного периода и найденных строк.";
|
return "По данным 1С найдены строки входящих денежных поступлений; сумму и помесячную раскладку можно называть только в рамках проверенного периода и найденных строк.";
|
||||||
}
|
}
|
||||||
if (pilot.derived_bidirectional_value_flow && mode === "confirmed_with_bounded_inference") {
|
if (pilot.derived_bidirectional_value_flow && mode === "confirmed_with_bounded_inference") {
|
||||||
return "По данным 1С найдены строки входящих и исходящих денежных движений; нетто можно называть только как расчет по найденным строкам и проверенному периоду.";
|
return "По данным 1С найдены строки входящих и исходящих денежных движений; нетто можно называть только как расчет по найденным строкам и проверенному периоду.";
|
||||||
|
|
@ -285,14 +302,23 @@ function headlineFor(mode: AssistantMcpDiscoveryAnswerMode, pilot: AssistantMcpD
|
||||||
if (pilot.derived_value_flow.value_flow_direction === "outgoing_supplier_payout") {
|
if (pilot.derived_value_flow.value_flow_direction === "outgoing_supplier_payout") {
|
||||||
return "По данным 1С найдены строки исходящих платежей/списаний; сумму можно называть только в рамках проверенного периода и найденных строк.";
|
return "По данным 1С найдены строки исходящих платежей/списаний; сумму можно называть только в рамках проверенного периода и найденных строк.";
|
||||||
}
|
}
|
||||||
return "По данным 1С найдены строки денежных движений; сумму можно называть только в рамках проверенного периода и найденных строк.";
|
return "По данным 1С найдены строки входящих денежных поступлений; сумму можно называть только в рамках проверенного периода и найденных строк.";
|
||||||
}
|
}
|
||||||
if (isDocumentPilot(pilot) && mode === "confirmed_with_bounded_inference") {
|
if (isDocumentPilot(pilot) && mode === "confirmed_with_bounded_inference") {
|
||||||
return "По данным 1С найдены строки документов; ответ ограничен проверенным периодом и найденными строками.";
|
return `По документам${documentOrMovementScopeRu(pilot)} в 1С найдены подтвержденные строки; ответ ограничен проверенным окном и найденными строками.`;
|
||||||
|
}
|
||||||
|
if (isMovementPilot(pilot) && mode === "confirmed_with_bounded_inference") {
|
||||||
|
return `По движениям${documentOrMovementScopeRu(pilot)} в 1С найдены подтвержденные строки; ответ ограничен проверенным окном и найденными строками.`;
|
||||||
}
|
}
|
||||||
if (mode === "confirmed_with_bounded_inference") {
|
if (mode === "confirmed_with_bounded_inference") {
|
||||||
return "По данным 1С есть подтвержденная активность; длительность можно оценивать только как вывод из этих строк.";
|
return "По данным 1С есть подтвержденная активность; длительность можно оценивать только как вывод из этих строк.";
|
||||||
}
|
}
|
||||||
|
if (isDocumentPilot(pilot) && mode === "bounded_inference_only") {
|
||||||
|
return `По документам${documentOrMovementScopeRu(pilot)} полный срез не подтвержден; пока есть только ограниченная граница проверенного окна 1С.`;
|
||||||
|
}
|
||||||
|
if (isMovementPilot(pilot) && mode === "bounded_inference_only") {
|
||||||
|
return `По движениям${documentOrMovementScopeRu(pilot)} полный срез не подтвержден; пока есть только ограниченная граница проверенного окна 1С.`;
|
||||||
|
}
|
||||||
if (mode === "bounded_inference_only") {
|
if (mode === "bounded_inference_only") {
|
||||||
return "Точный факт не подтвержден, но есть ограниченная оценка по найденной активности в 1С.";
|
return "Точный факт не подтвержден, но есть ограниченная оценка по найденной активности в 1С.";
|
||||||
}
|
}
|
||||||
|
|
@ -523,15 +549,17 @@ function derivedValueFlowConfirmedLine(pilot: AssistantMcpDiscoveryPilotExecutio
|
||||||
const counterparty = flow.counterparty ? ` по контрагенту ${flow.counterparty}` : "";
|
const counterparty = flow.counterparty ? ` по контрагенту ${flow.counterparty}` : "";
|
||||||
const period = flow.period_scope ? ` за период ${flow.period_scope}` : " в проверенном окне";
|
const period = flow.period_scope ? ` за период ${flow.period_scope}` : " в проверенном окне";
|
||||||
const movementLabel =
|
const movementLabel =
|
||||||
flow.value_flow_direction === "outgoing_supplier_payout" ? "исходящих платежей/списаний" : "денежных движений";
|
flow.value_flow_direction === "outgoing_supplier_payout"
|
||||||
|
? "исходящих платежей/списаний"
|
||||||
|
: "входящих денежных поступлений";
|
||||||
const totalLabel =
|
const totalLabel =
|
||||||
flow.value_flow_direction === "outgoing_supplier_payout"
|
flow.value_flow_direction === "outgoing_supplier_payout"
|
||||||
? "сумма исходящих платежей/списаний составляет"
|
? "сумма исходящих платежей/списаний составляет"
|
||||||
: "сумма составляет";
|
: "сумма входящих денежных поступлений составляет";
|
||||||
const caveat =
|
const caveat =
|
||||||
flow.value_flow_direction === "outgoing_supplier_payout"
|
flow.value_flow_direction === "outgoing_supplier_payout"
|
||||||
? "Это расчет по найденным строкам 1С, а не подтверждение полного объема платежей вне проверенного окна."
|
? "Это расчет по найденным строкам 1С, а не подтверждение полного объема платежей вне проверенного окна."
|
||||||
: "Это расчет по найденным строкам 1С, а не подтверждение полного оборота вне проверенного окна.";
|
: "Это расчет по найденным строкам 1С, а не подтверждение полного объема поступлений вне проверенного окна.";
|
||||||
const dates =
|
const dates =
|
||||||
flow.first_movement_date && flow.latest_movement_date
|
flow.first_movement_date && flow.latest_movement_date
|
||||||
? ` Первая найденная дата движения: ${flow.first_movement_date}; последняя: ${flow.latest_movement_date}.`
|
? ` Первая найденная дата движения: ${flow.first_movement_date}; последняя: ${flow.latest_movement_date}.`
|
||||||
|
|
|
||||||
|
|
@ -1694,26 +1694,38 @@ function buildLifecycleConfirmedFacts(result: AddressMcpQueryExecutorResult, cou
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildDocumentConfirmedFacts(result: AddressMcpQueryExecutorResult, counterparty: string | null): string[] {
|
function checkedCounterpartySuffixRu(counterparty: string | null): string {
|
||||||
if (result.error || result.matched_rows <= 0) {
|
return counterparty ? ` по контрагенту ${counterparty}` : "";
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return [
|
|
||||||
counterparty
|
|
||||||
? `1C document rows were found for counterparty ${counterparty}`
|
|
||||||
: "1C document rows were found for the requested scope"
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildMovementConfirmedFacts(result: AddressMcpQueryExecutorResult, counterparty: string | null): string[] {
|
function checkedPeriodSuffixRu(periodScope: string | null): string {
|
||||||
|
return periodScope ? ` за ${periodScope}` : " в проверенном окне";
|
||||||
|
}
|
||||||
|
|
||||||
|
function uncheckedPeriodBoundaryRu(periodScope: string | null): string {
|
||||||
|
return periodScope ? ` вне периода ${periodScope}` : " без явно проверенного периода";
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDocumentConfirmedFacts(
|
||||||
|
result: AddressMcpQueryExecutorResult,
|
||||||
|
counterparty: string | null,
|
||||||
|
periodScope: string | null
|
||||||
|
): string[] {
|
||||||
if (result.error || result.matched_rows <= 0) {
|
if (result.error || result.matched_rows <= 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return [
|
return [`В 1С найдены строки документов${checkedCounterpartySuffixRu(counterparty)}${checkedPeriodSuffixRu(periodScope)}.`];
|
||||||
counterparty
|
}
|
||||||
? `1C movement rows were found for counterparty ${counterparty}`
|
|
||||||
: "1C movement rows were found for the requested scope"
|
function buildMovementConfirmedFacts(
|
||||||
];
|
result: AddressMcpQueryExecutorResult,
|
||||||
|
counterparty: string | null,
|
||||||
|
periodScope: string | null
|
||||||
|
): string[] {
|
||||||
|
if (result.error || result.matched_rows <= 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [`В 1С найдены строки движений${checkedCounterpartySuffixRu(counterparty)}${checkedPeriodSuffixRu(periodScope)}.`];
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildValueFlowConfirmedFacts(
|
function buildValueFlowConfirmedFacts(
|
||||||
|
|
@ -1763,18 +1775,40 @@ function buildLifecycleInferredFacts(result: AddressMcpQueryExecutorResult): str
|
||||||
return ["Business activity duration may be inferred from first and latest confirmed 1C activity rows"];
|
return ["Business activity duration may be inferred from first and latest confirmed 1C activity rows"];
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildDocumentInferredFacts(result: AddressMcpQueryExecutorResult): string[] {
|
function buildDocumentInferredFacts(
|
||||||
|
result: AddressMcpQueryExecutorResult,
|
||||||
|
counterparty: string | null,
|
||||||
|
periodScope: string | null
|
||||||
|
): string[] {
|
||||||
if (result.error || result.fetched_rows <= 0) {
|
if (result.error || result.fetched_rows <= 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return ["Counterparty document evidence is limited to confirmed 1C document rows in the checked scope"];
|
if (result.matched_rows <= 0) {
|
||||||
|
return [
|
||||||
|
`По документам${checkedCounterpartySuffixRu(counterparty)}${checkedPeriodSuffixRu(periodScope)} удалось проверить только ограниченный срез 1С; подтвержденных строк документов этим поиском не найдено.`
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
`Срез документов${checkedCounterpartySuffixRu(counterparty)}${checkedPeriodSuffixRu(periodScope)} ограничен только подтвержденными строками документов, найденными этим поиском.`
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildMovementInferredFacts(result: AddressMcpQueryExecutorResult): string[] {
|
function buildMovementInferredFacts(
|
||||||
|
result: AddressMcpQueryExecutorResult,
|
||||||
|
counterparty: string | null,
|
||||||
|
periodScope: string | null
|
||||||
|
): string[] {
|
||||||
if (result.error || result.fetched_rows <= 0) {
|
if (result.error || result.fetched_rows <= 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return ["Counterparty movement evidence is limited to confirmed 1C movement rows in the checked scope"];
|
if (result.matched_rows <= 0) {
|
||||||
|
return [
|
||||||
|
`По движениям${checkedCounterpartySuffixRu(counterparty)}${checkedPeriodSuffixRu(periodScope)} удалось проверить только ограниченный срез 1С; подтвержденных строк движений этим поиском не найдено.`
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
`Срез движений${checkedCounterpartySuffixRu(counterparty)}${checkedPeriodSuffixRu(periodScope)} ограничен только подтвержденными строками движений, найденными этим поиском.`
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildValueFlowInferredFacts(derived: AssistantMcpDiscoveryDerivedValueFlow | null): string[] {
|
function buildValueFlowInferredFacts(derived: AssistantMcpDiscoveryDerivedValueFlow | null): string[] {
|
||||||
|
|
@ -1820,19 +1854,15 @@ function buildLifecycleUnknownFacts(): string[] {
|
||||||
return ["Legal registration date is not proven by this MCP discovery pilot"];
|
return ["Legal registration date is not proven by this MCP discovery pilot"];
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildDocumentUnknownFacts(periodScope: string | null): string[] {
|
function buildDocumentUnknownFacts(periodScope: string | null, counterparty: string | null): string[] {
|
||||||
return [
|
return [
|
||||||
periodScope
|
`Полный исторический срез документов${checkedCounterpartySuffixRu(counterparty)}${uncheckedPeriodBoundaryRu(periodScope)} этим поиском не подтвержден.`
|
||||||
? "Full document history outside the checked period is not proven by this MCP discovery pilot"
|
|
||||||
: "Full document history is not proven without an explicit checked period"
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildMovementUnknownFacts(periodScope: string | null): string[] {
|
function buildMovementUnknownFacts(periodScope: string | null, counterparty: string | null): string[] {
|
||||||
return [
|
return [
|
||||||
periodScope
|
`Полный исторический срез движений${checkedCounterpartySuffixRu(counterparty)}${uncheckedPeriodBoundaryRu(periodScope)} этим поиском не подтвержден.`
|
||||||
? "Full movement history outside the checked period is not proven by this MCP discovery pilot"
|
|
||||||
: "Full movement history is not proven without an explicit checked period"
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2245,9 +2275,9 @@ export async function executeAssistantMcpDiscoveryPilot(
|
||||||
const evidence = resolveAssistantMcpDiscoveryEvidence({
|
const evidence = resolveAssistantMcpDiscoveryEvidence({
|
||||||
plan: planner.discovery_plan,
|
plan: planner.discovery_plan,
|
||||||
probeResults,
|
probeResults,
|
||||||
confirmedFacts: queryResult ? buildDocumentConfirmedFacts(queryResult, counterparty) : [],
|
confirmedFacts: queryResult ? buildDocumentConfirmedFacts(queryResult, counterparty, dateScope) : [],
|
||||||
inferredFacts: queryResult ? buildDocumentInferredFacts(queryResult) : [],
|
inferredFacts: queryResult ? buildDocumentInferredFacts(queryResult, counterparty, dateScope) : [],
|
||||||
unknownFacts: buildDocumentUnknownFacts(dateScope),
|
unknownFacts: buildDocumentUnknownFacts(dateScope, counterparty),
|
||||||
sourceRowsSummary,
|
sourceRowsSummary,
|
||||||
queryLimitations,
|
queryLimitations,
|
||||||
recommendedNextProbe: "explain_evidence_basis"
|
recommendedNextProbe: "explain_evidence_basis"
|
||||||
|
|
@ -2330,9 +2360,9 @@ export async function executeAssistantMcpDiscoveryPilot(
|
||||||
const evidence = resolveAssistantMcpDiscoveryEvidence({
|
const evidence = resolveAssistantMcpDiscoveryEvidence({
|
||||||
plan: planner.discovery_plan,
|
plan: planner.discovery_plan,
|
||||||
probeResults,
|
probeResults,
|
||||||
confirmedFacts: queryResult ? buildMovementConfirmedFacts(queryResult, counterparty) : [],
|
confirmedFacts: queryResult ? buildMovementConfirmedFacts(queryResult, counterparty, dateScope) : [],
|
||||||
inferredFacts: queryResult ? buildMovementInferredFacts(queryResult) : [],
|
inferredFacts: queryResult ? buildMovementInferredFacts(queryResult, counterparty, dateScope) : [],
|
||||||
unknownFacts: buildMovementUnknownFacts(dateScope),
|
unknownFacts: buildMovementUnknownFacts(dateScope, counterparty),
|
||||||
sourceRowsSummary,
|
sourceRowsSummary,
|
||||||
queryLimitations,
|
queryLimitations,
|
||||||
recommendedNextProbe: "explain_evidence_basis"
|
recommendedNextProbe: "explain_evidence_basis"
|
||||||
|
|
|
||||||
|
|
@ -100,10 +100,10 @@ function localizeLine(value: string): string {
|
||||||
}
|
}
|
||||||
const valueFlowMatch = value.match(/^1C value-flow rows were found for counterparty\s+(.+)$/i);
|
const valueFlowMatch = value.match(/^1C value-flow rows were found for counterparty\s+(.+)$/i);
|
||||||
if (valueFlowMatch) {
|
if (valueFlowMatch) {
|
||||||
return `В 1С найдены строки денежных движений по контрагенту ${valueFlowMatch[1]}.`;
|
return `В 1С найдены строки входящих денежных поступлений по контрагенту ${valueFlowMatch[1]}.`;
|
||||||
}
|
}
|
||||||
if (/^1C value-flow rows were found for the requested counterparty scope$/i.test(value)) {
|
if (/^1C value-flow rows were found for the requested counterparty scope$/i.test(value)) {
|
||||||
return "В 1С найдены строки денежных движений по запрошенному контрагентскому контуру.";
|
return "В 1С найдены строки входящих денежных поступлений по запрошенному контрагентскому контуру.";
|
||||||
}
|
}
|
||||||
const documentRowsMatch = value.match(/^1C document rows were found for counterparty\s+(.+)$/i);
|
const documentRowsMatch = value.match(/^1C document rows were found for counterparty\s+(.+)$/i);
|
||||||
if (documentRowsMatch) {
|
if (documentRowsMatch) {
|
||||||
|
|
@ -152,10 +152,10 @@ function localizeLine(value: string): string {
|
||||||
return "Срез движений ограничен только подтвержденными строками движений в проверенном окне.";
|
return "Срез движений ограничен только подтвержденными строками движений в проверенном окне.";
|
||||||
}
|
}
|
||||||
if (/^Counterparty value-flow total was calculated from confirmed 1C movement rows$/i.test(value)) {
|
if (/^Counterparty value-flow total was calculated from confirmed 1C movement rows$/i.test(value)) {
|
||||||
return "Сумма рассчитана только по подтвержденным строкам денежных движений в 1С.";
|
return "Сумма входящих поступлений рассчитана только по подтвержденным строкам поступлений в 1С.";
|
||||||
}
|
}
|
||||||
if (/^Counterparty monthly value-flow breakdown was grouped by month over confirmed 1C movement rows$/i.test(value)) {
|
if (/^Counterparty monthly value-flow breakdown was grouped by month over confirmed 1C movement rows$/i.test(value)) {
|
||||||
return "Помесячная раскладка денежного потока сгруппирована только по подтвержденным строкам движений 1С.";
|
return "Помесячная раскладка входящих поступлений построена только по подтвержденным строкам поступлений в 1С.";
|
||||||
}
|
}
|
||||||
if (/^Counterparty supplier-payout total was calculated from confirmed 1C outgoing payment rows$/i.test(value)) {
|
if (/^Counterparty supplier-payout total was calculated from confirmed 1C outgoing payment rows$/i.test(value)) {
|
||||||
return "Сумма исходящих платежей рассчитана только по подтвержденным строкам списаний в 1С.";
|
return "Сумма исходящих платежей рассчитана только по подтвержденным строкам списаний в 1С.";
|
||||||
|
|
@ -225,10 +225,10 @@ function localizeLine(value: string): string {
|
||||||
return "Полное покрытие запрошенного периода по двустороннему денежному потоку не подтверждено: хотя бы одна сторона проверки достигла лимита найденных строк.";
|
return "Полное покрытие запрошенного периода по двустороннему денежному потоку не подтверждено: хотя бы одна сторона проверки достигла лимита найденных строк.";
|
||||||
}
|
}
|
||||||
if (/^Full turnover outside the checked period is not proven by this MCP discovery pilot$/i.test(value)) {
|
if (/^Full turnover outside the checked period is not proven by this MCP discovery pilot$/i.test(value)) {
|
||||||
return "Полный оборот вне проверенного периода этим поиском не подтвержден.";
|
return "Полный объем входящих поступлений вне проверенного периода этим поиском не подтвержден.";
|
||||||
}
|
}
|
||||||
if (/^Full all-time turnover is not proven without an explicit checked period$/i.test(value)) {
|
if (/^Full all-time turnover is not proven without an explicit checked period$/i.test(value)) {
|
||||||
return "Полный оборот за все время без явно проверенного периода не подтвержден.";
|
return "Полный объем входящих поступлений за все время без явно проверенного периода не подтвержден.";
|
||||||
}
|
}
|
||||||
if (/^Full document history outside the checked period is not proven by this MCP discovery pilot$/i.test(value)) {
|
if (/^Full document history outside the checked period is not proven by this MCP discovery pilot$/i.test(value)) {
|
||||||
return "Полный исторический срез документов вне проверенного периода этим поиском не подтвержден.";
|
return "Полный исторический срез документов вне проверенного периода этим поиском не подтвержден.";
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,27 @@ function pushUnique(target: string[], value: unknown): void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isReferentialEntityPlaceholder(value: string): boolean {
|
||||||
|
return /^(?:\u043d\u0435\u043c\u0443|\u043d\u0435\u0439|\u043d\u0438\u043c|\u043d\u0438\u043c\u0438|\u0435\u0433\u043e|\u0435\u0435|\u0435\u0451|\u0438\u0445|\u044d\u0442\u043e\u043c\u0443|\u044d\u0442\u043e\u0439|\u044d\u0442\u0438\u043c|\u044d\u0442\u0438\u043c\u0438|\u044d\u0442\u043e\u043c)$/iu.test(
|
||||||
|
value.trim()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushScopedEntityCandidate(
|
||||||
|
target: string[],
|
||||||
|
value: unknown,
|
||||||
|
groundedFollowupEntity: string | null
|
||||||
|
): void {
|
||||||
|
const text = candidateValue(value);
|
||||||
|
if (!text) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (groundedFollowupEntity && isReferentialEntityPlaceholder(text)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pushUnique(target, text);
|
||||||
|
}
|
||||||
|
|
||||||
function canonicalizeEntityResolutionCandidate(value: string): string {
|
function canonicalizeEntityResolutionCandidate(value: string): string {
|
||||||
return normalizeEntityResolutionCandidate(value)
|
return normalizeEntityResolutionCandidate(value)
|
||||||
.replace(/^(?:\u0441\s+\u043d\u0430\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u0435\u043c\s+)/iu, "")
|
.replace(/^(?:\u0441\s+\u043d\u0430\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u0435\u043c\s+)/iu, "")
|
||||||
|
|
@ -194,6 +215,13 @@ function mapPilotScopeToFollowupMeaning(
|
||||||
unsupported: "movement_evidence"
|
unsupported: "movement_evidence"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (pilotScope === "counterparty_document_evidence_query_documents_v1") {
|
||||||
|
return {
|
||||||
|
domain: "documents",
|
||||||
|
action: "list_documents",
|
||||||
|
unsupported: "document_evidence"
|
||||||
|
};
|
||||||
|
}
|
||||||
if (pilotScope === "counterparty_supplier_payout_query_movements_v1") {
|
if (pilotScope === "counterparty_supplier_payout_query_movements_v1") {
|
||||||
return {
|
return {
|
||||||
domain: "counterparty_value",
|
domain: "counterparty_value",
|
||||||
|
|
@ -300,7 +328,8 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
|
||||||
toNonEmptyString(rootFilters?.counterparty) ??
|
toNonEmptyString(rootFilters?.counterparty) ??
|
||||||
(toNonEmptyString(followupContext?.previous_anchor_type) === "counterparty"
|
(toNonEmptyString(followupContext?.previous_anchor_type) === "counterparty"
|
||||||
? toNonEmptyString(followupContext?.previous_anchor_value)
|
? toNonEmptyString(followupContext?.previous_anchor_value)
|
||||||
: null);
|
: null) ??
|
||||||
|
(discoveryEntities[0] ?? null);
|
||||||
const organization =
|
const organization =
|
||||||
toNonEmptyString(previousFilters?.organization) ??
|
toNonEmptyString(previousFilters?.organization) ??
|
||||||
toNonEmptyString(rootFilters?.organization) ??
|
toNonEmptyString(rootFilters?.organization) ??
|
||||||
|
|
@ -351,7 +380,7 @@ function hasLifecycleSignal(text: string): boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasValueFlowSignal(text: string): boolean {
|
function hasValueFlowSignal(text: string): boolean {
|
||||||
return /(?:оборот|выручк|оплат|плат[её]ж|заплат|перечисл|списан|расход|исходящ|поступлен|денежн[а-яёa-z0-9_-]*\s+поток|supplier|value[-\s]?flow|turnover|revenue|payment|payout|outflow|cash\s+flow)/iu.test(
|
return /(?:оборот|выручк|оплат|плат[её]ж|заплат|перечисл|списан|расход|исходящ|входящ|получ(?:ил|ено|ен)|поступил|поступлен|денежн[а-яёa-z0-9_-]*\s+поток|supplier|value[-\s]?flow|turnover|revenue|payment|payout|outflow|cash\s+flow)/iu.test(
|
||||||
text
|
text
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -410,6 +439,18 @@ function hasMovementEvidenceFollowupSignal(text: string): boolean {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasPronounDocumentEvidenceFollowupSignal(text: string): boolean {
|
||||||
|
return /(?:\u043f\u043e\s+(?:\u043d\u0435\u043c\u0443|\u043d\u0435\u0439|\u044d\u0442\u043e\u043c\u0443|\u044d\u0442\u043e\u0439)\s+(?:\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442(?:\u0430\u043c|\u044b)?|\u0441\u0447(?:[Рµe]С‚|\u0435\u0442)[-\u2011 ]?\u0444\u0430\u043a\u0442\u0443\u0440(?:\u044b|\u0430)?|\u043d\u0430\u043a\u043b\u0430\u0434\u043d(?:\u044b\u0435|\u0430\u044f)?|\u0430\u043a\u0442(?:\u044b)?))/iu.test(
|
||||||
|
text
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasPronounMovementEvidenceFollowupSignal(text: string): boolean {
|
||||||
|
return /(?:\u043f\u043e\s+(?:\u043d\u0435\u043c\u0443|\u043d\u0435\u0439|\u044d\u0442\u043e\u043c\u0443|\u044d\u0442\u043e\u0439)\s+(?:\u0434\u0432\u0438\u0436\u0435\u043d(?:\u0438\u044f\u043c|\u0438\u044f)?|\u043f\u043b\u0430\u0442[Рµe]\u0436(?:\u0430\u043c|\u0438)?|\u043e\u043f\u0435\u0440\u0430\u0446(?:\u0438\u044f\u043c|\u0438\u0438|\u0438\u044e)|\u043f\u0440\u043e\u0432\u043e\u0434\u043a(?:\u0430\u043c|\u0438)|\u0441\u043f\u0438\u0441\u0430\u043d(?:\u0438\u044f\u043c|\u0438\u0435)|\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d(?:\u0438\u044f\u043c|\u0438\u0435)))/iu.test(
|
||||||
|
text
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function hasMetadataDownstreamContinuationSignal(text: string): boolean {
|
function hasMetadataDownstreamContinuationSignal(text: string): boolean {
|
||||||
return /(?:\u0434\u0430\u0432\u0430\u0439\s+\u0434\u0430\u043b\u044c\u0448\u0435|\u0438\u0434(?:\u0435|\u0451)\u043c\s+\u0434\u0430\u043b\u044c\u0448\u0435|\u043f\u043e\u0448\u043b(?:\u0438|\u0451\u043c)\s+\u0434\u0430\u043b\u044c\u0448\u0435|\u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0439|\u0438\u0449\u0438\s+\u0434\u0430\u043b\u044c\u0448\u0435|\u0438\u0449\u0438\s+\u0434\u0430\u043d\u043d\u044b\u0435|\u043f\u043e\u043a\u0430\u0436\u0438\s+\u0434\u0430\u043d\u043d\u044b\u0435|\u043f\u043e\u043a\u0430\u0436\u0438\s+\u0441\u0442\u0440\u043e\u043a\u0438|\u0433\u043b\u0443\u0431\u0436\u0435|\u0447\u0442\u043e\s+\u0434\u0430\u043b\u044c\u0448\u0435|continue|go\s+ahead|go\s+deeper|look\s+deeper|drill\s+down|show\s+(?:data|rows))/iu.test(
|
return /(?:\u0434\u0430\u0432\u0430\u0439\s+\u0434\u0430\u043b\u044c\u0448\u0435|\u0438\u0434(?:\u0435|\u0451)\u043c\s+\u0434\u0430\u043b\u044c\u0448\u0435|\u043f\u043e\u0448\u043b(?:\u0438|\u0451\u043c)\s+\u0434\u0430\u043b\u044c\u0448\u0435|\u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0439|\u0438\u0449\u0438\s+\u0434\u0430\u043b\u044c\u0448\u0435|\u0438\u0449\u0438\s+\u0434\u0430\u043d\u043d\u044b\u0435|\u043f\u043e\u043a\u0430\u0436\u0438\s+\u0434\u0430\u043d\u043d\u044b\u0435|\u043f\u043e\u043a\u0430\u0436\u0438\s+\u0441\u0442\u0440\u043e\u043a\u0438|\u0433\u043b\u0443\u0431\u0436\u0435|\u0447\u0442\u043e\s+\u0434\u0430\u043b\u044c\u0448\u0435|continue|go\s+ahead|go\s+deeper|look\s+deeper|drill\s+down|show\s+(?:data|rows))/iu.test(
|
||||||
text
|
text
|
||||||
|
|
@ -548,7 +589,11 @@ function shouldRunDiscovery(input: {
|
||||||
semanticDataNeed: string | null;
|
semanticDataNeed: string | null;
|
||||||
explicitIntentCandidate: string | null;
|
explicitIntentCandidate: string | null;
|
||||||
followupDiscoverySeedApplicable: boolean;
|
followupDiscoverySeedApplicable: boolean;
|
||||||
|
forceDiscoveryOverExplicitIntent: boolean;
|
||||||
}): boolean {
|
}): boolean {
|
||||||
|
if (input.forceDiscoveryOverExplicitIntent && input.semanticDataNeed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (input.lifecycleSignal || input.unsupported) {
|
if (input.lifecycleSignal || input.unsupported) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -598,8 +643,10 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
const rawMetadataScopeHint = rawMetadataSignal ? metadataScopeHintFromRawText(rawText) : null;
|
const rawMetadataScopeHint = rawMetadataSignal ? metadataScopeHintFromRawText(rawText) : null;
|
||||||
const rawEntityCandidate = rawEntityResolutionSignal ? rawEntityResolutionCandidate(rawEntitySourceText) : null;
|
const rawEntityCandidate = rawEntityResolutionSignal ? rawEntityResolutionCandidate(rawEntitySourceText) : null;
|
||||||
const entityResolutionSignal = rawEntityResolutionSignal || Boolean(rawEntityCandidate);
|
const entityResolutionSignal = rawEntityResolutionSignal || Boolean(rawEntityCandidate);
|
||||||
const metadataDocumentHintSignal = hasDocumentEvidenceFollowupSignal(rawText);
|
const metadataDocumentHintSignal =
|
||||||
const metadataMovementHintSignal = hasMovementEvidenceFollowupSignal(rawText);
|
hasDocumentEvidenceFollowupSignal(rawText) || hasPronounDocumentEvidenceFollowupSignal(rawText);
|
||||||
|
const metadataMovementHintSignal =
|
||||||
|
hasMovementEvidenceFollowupSignal(rawText) || hasPronounMovementEvidenceFollowupSignal(rawText);
|
||||||
|
|
||||||
const rawDomain = toNonEmptyString(assistantTurnMeaning?.asked_domain_family);
|
const rawDomain = toNonEmptyString(assistantTurnMeaning?.asked_domain_family);
|
||||||
const rawAction = toNonEmptyString(assistantTurnMeaning?.asked_action_family);
|
const rawAction = toNonEmptyString(assistantTurnMeaning?.asked_action_family);
|
||||||
|
|
@ -657,6 +704,47 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
!rawLifecycleSignal &&
|
!rawLifecycleSignal &&
|
||||||
metadataMovementHintSignal
|
metadataMovementHintSignal
|
||||||
);
|
);
|
||||||
|
const entityResolutionGroundedDocumentFollowupApplicable = Boolean(
|
||||||
|
followupSeed.pilotScope === "entity_resolution_search_v1" &&
|
||||||
|
(followupSeed.counterparty || followupSeed.discoveryEntity) &&
|
||||||
|
!rawLifecycleSignal &&
|
||||||
|
!rawValueFlowSignal &&
|
||||||
|
!rawMetadataSignal &&
|
||||||
|
metadataDocumentHintSignal
|
||||||
|
);
|
||||||
|
const entityResolutionGroundedMovementFollowupApplicable = Boolean(
|
||||||
|
followupSeed.pilotScope === "entity_resolution_search_v1" &&
|
||||||
|
(followupSeed.counterparty || followupSeed.discoveryEntity) &&
|
||||||
|
!rawLifecycleSignal &&
|
||||||
|
!rawValueFlowSignal &&
|
||||||
|
!rawMetadataSignal &&
|
||||||
|
metadataMovementHintSignal
|
||||||
|
);
|
||||||
|
const groundedValueFlowFollowupApplicable = Boolean(
|
||||||
|
rawValueFlowSignal &&
|
||||||
|
!rawLifecycleSignal &&
|
||||||
|
!rawMetadataSignal &&
|
||||||
|
(followupSeed.counterparty || followupSeed.discoveryEntity) &&
|
||||||
|
(followupSeed.pilotScope === "entity_resolution_search_v1" ||
|
||||||
|
followupSeed.pilotScope === "counterparty_document_evidence_query_documents_v1" ||
|
||||||
|
followupSeed.pilotScope === "counterparty_movement_evidence_query_movements_v1")
|
||||||
|
);
|
||||||
|
const documentEvidenceGroundedMovementFollowupApplicable = Boolean(
|
||||||
|
followupSeed.pilotScope === "counterparty_document_evidence_query_documents_v1" &&
|
||||||
|
(followupSeed.counterparty || followupSeed.discoveryEntity) &&
|
||||||
|
!rawLifecycleSignal &&
|
||||||
|
!rawValueFlowSignal &&
|
||||||
|
!rawMetadataSignal &&
|
||||||
|
metadataMovementHintSignal
|
||||||
|
);
|
||||||
|
const movementEvidenceGroundedDocumentFollowupApplicable = Boolean(
|
||||||
|
followupSeed.pilotScope === "counterparty_movement_evidence_query_movements_v1" &&
|
||||||
|
(followupSeed.counterparty || followupSeed.discoveryEntity) &&
|
||||||
|
!rawLifecycleSignal &&
|
||||||
|
!rawValueFlowSignal &&
|
||||||
|
!rawMetadataSignal &&
|
||||||
|
metadataDocumentHintSignal
|
||||||
|
);
|
||||||
const metadataGroundedLaneContinuationApplicable = Boolean(
|
const metadataGroundedLaneContinuationApplicable = Boolean(
|
||||||
followupSeed.pilotScope === "metadata_inspection_v1" &&
|
followupSeed.pilotScope === "metadata_inspection_v1" &&
|
||||||
(followupSeed.metadataRouteFamily === "document_evidence" ||
|
(followupSeed.metadataRouteFamily === "document_evidence" ||
|
||||||
|
|
@ -710,11 +798,15 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
const metadataGroundedDocumentLaneApplicable =
|
const metadataGroundedDocumentLaneApplicable =
|
||||||
metadataGroundedDocumentFollowupApplicable ||
|
metadataGroundedDocumentFollowupApplicable ||
|
||||||
metadataAmbiguityResolvedDocumentFollowupApplicable ||
|
metadataAmbiguityResolvedDocumentFollowupApplicable ||
|
||||||
|
entityResolutionGroundedDocumentFollowupApplicable ||
|
||||||
|
movementEvidenceGroundedDocumentFollowupApplicable ||
|
||||||
(metadataGroundedLaneContinuationApplicable && followupSeed.metadataRouteFamily === "document_evidence") ||
|
(metadataGroundedLaneContinuationApplicable && followupSeed.metadataRouteFamily === "document_evidence") ||
|
||||||
metadataAmbiguityCollapsedDocumentLaneContinuationApplicable;
|
metadataAmbiguityCollapsedDocumentLaneContinuationApplicable;
|
||||||
const metadataGroundedMovementLaneApplicable =
|
const metadataGroundedMovementLaneApplicable =
|
||||||
metadataGroundedMovementFollowupApplicable ||
|
metadataGroundedMovementFollowupApplicable ||
|
||||||
metadataAmbiguityResolvedMovementFollowupApplicable ||
|
metadataAmbiguityResolvedMovementFollowupApplicable ||
|
||||||
|
entityResolutionGroundedMovementFollowupApplicable ||
|
||||||
|
documentEvidenceGroundedMovementFollowupApplicable ||
|
||||||
(metadataGroundedLaneContinuationApplicable && followupSeed.metadataRouteFamily === "movement_evidence") ||
|
(metadataGroundedLaneContinuationApplicable && followupSeed.metadataRouteFamily === "movement_evidence") ||
|
||||||
metadataAmbiguityCollapsedMovementLaneContinuationApplicable;
|
metadataAmbiguityCollapsedMovementLaneContinuationApplicable;
|
||||||
const effectiveMetadataFollowupSeedApplicable =
|
const effectiveMetadataFollowupSeedApplicable =
|
||||||
|
|
@ -773,7 +865,8 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
metadataSignal: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable,
|
metadataSignal: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable,
|
||||||
entityResolutionSignal
|
entityResolutionSignal
|
||||||
});
|
});
|
||||||
const entityCandidates = entityResolutionSignal ? [] : collectEntityCandidates(assistantTurnMeaning?.explicit_entity_candidates);
|
const groundedFollowupEntity = followupSeed.counterparty ?? followupSeed.discoveryEntity;
|
||||||
|
const entityCandidates = entityResolutionSignal ? [] : [];
|
||||||
if (entityResolutionSignal) {
|
if (entityResolutionSignal) {
|
||||||
pushNormalizedEntityResolutionCandidate(entityCandidates, rawEntityCandidate);
|
pushNormalizedEntityResolutionCandidate(entityCandidates, rawEntityCandidate);
|
||||||
for (const candidate of collectEntityCandidates(assistantTurnMeaning?.explicit_entity_candidates)) {
|
for (const candidate of collectEntityCandidates(assistantTurnMeaning?.explicit_entity_candidates)) {
|
||||||
|
|
@ -782,11 +875,20 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
pushNormalizedEntityResolutionCandidate(entityCandidates, predecomposeEntities.counterparty);
|
pushNormalizedEntityResolutionCandidate(entityCandidates, predecomposeEntities.counterparty);
|
||||||
pushNormalizedEntityResolutionCandidate(entityCandidates, followupSeed.counterparty);
|
pushNormalizedEntityResolutionCandidate(entityCandidates, followupSeed.counterparty);
|
||||||
} else {
|
} else {
|
||||||
pushUnique(entityCandidates, predecomposeEntities.counterparty);
|
if (groundedFollowupEntity) {
|
||||||
pushUnique(entityCandidates, followupSeed.counterparty);
|
pushUnique(entityCandidates, groundedFollowupEntity);
|
||||||
pushUnique(entityCandidates, rawEntityCandidate);
|
|
||||||
}
|
}
|
||||||
if ((rawMetadataSignal || metadataFollowupSeedApplicable) && !followupSeed.counterparty) {
|
for (const candidate of collectEntityCandidates(assistantTurnMeaning?.explicit_entity_candidates)) {
|
||||||
|
pushScopedEntityCandidate(entityCandidates, candidate, groundedFollowupEntity);
|
||||||
|
}
|
||||||
|
pushScopedEntityCandidate(entityCandidates, predecomposeEntities.counterparty, groundedFollowupEntity);
|
||||||
|
if (!groundedFollowupEntity) {
|
||||||
|
pushScopedEntityCandidate(entityCandidates, followupSeed.counterparty, null);
|
||||||
|
pushScopedEntityCandidate(entityCandidates, followupSeed.discoveryEntity, null);
|
||||||
|
}
|
||||||
|
pushScopedEntityCandidate(entityCandidates, rawEntityCandidate, groundedFollowupEntity);
|
||||||
|
}
|
||||||
|
if ((rawMetadataSignal || metadataFollowupSeedApplicable) && !groundedFollowupEntity) {
|
||||||
pushUnique(entityCandidates, followupSeed.discoveryEntity);
|
pushUnique(entityCandidates, followupSeed.discoveryEntity);
|
||||||
pushUnique(entityCandidates, rawMetadataScopeHint);
|
pushUnique(entityCandidates, rawMetadataScopeHint);
|
||||||
}
|
}
|
||||||
|
|
@ -920,7 +1022,13 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
effectiveMetadataFollowupSeedApplicable ||
|
effectiveMetadataFollowupSeedApplicable ||
|
||||||
metadataAmbiguityLaneClarificationApplicable ||
|
metadataAmbiguityLaneClarificationApplicable ||
|
||||||
metadataGroundedMovementLaneApplicable ||
|
metadataGroundedMovementLaneApplicable ||
|
||||||
metadataGroundedDocumentLaneApplicable
|
metadataGroundedDocumentLaneApplicable ||
|
||||||
|
groundedValueFlowFollowupApplicable,
|
||||||
|
forceDiscoveryOverExplicitIntent:
|
||||||
|
metadataAmbiguityLaneClarificationApplicable ||
|
||||||
|
metadataGroundedMovementLaneApplicable ||
|
||||||
|
metadataGroundedDocumentLaneApplicable ||
|
||||||
|
groundedValueFlowFollowupApplicable
|
||||||
});
|
});
|
||||||
const hasTurnMeaning = Object.keys(cleanTurnMeaning).length > 0;
|
const hasTurnMeaning = Object.keys(cleanTurnMeaning).length > 0;
|
||||||
const sourceSignal: AssistantMcpDiscoveryTurnInputSource = assistantTurnMeaning
|
const sourceSignal: AssistantMcpDiscoveryTurnInputSource = assistantTurnMeaning
|
||||||
|
|
@ -988,6 +1096,21 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
if (metadataAmbiguityResolvedMovementFollowupApplicable) {
|
if (metadataAmbiguityResolvedMovementFollowupApplicable) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_metadata_ambiguity_resolved_to_movement_lane");
|
pushReason(reasonCodes, "mcp_discovery_metadata_ambiguity_resolved_to_movement_lane");
|
||||||
}
|
}
|
||||||
|
if (entityResolutionGroundedDocumentFollowupApplicable) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_entity_resolution_grounded_document_followup");
|
||||||
|
}
|
||||||
|
if (entityResolutionGroundedMovementFollowupApplicable) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_entity_resolution_grounded_movement_followup");
|
||||||
|
}
|
||||||
|
if (groundedValueFlowFollowupApplicable) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_grounded_value_flow_followup");
|
||||||
|
}
|
||||||
|
if (documentEvidenceGroundedMovementFollowupApplicable) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_document_evidence_grounded_movement_followup");
|
||||||
|
}
|
||||||
|
if (movementEvidenceGroundedDocumentFollowupApplicable) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_movement_evidence_grounded_document_followup");
|
||||||
|
}
|
||||||
if (metadataGroundedLaneContinuationApplicable) {
|
if (metadataGroundedLaneContinuationApplicable) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_metadata_grounded_lane_continuation");
|
pushReason(reasonCodes, "mcp_discovery_metadata_grounded_lane_continuation");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import {
|
||||||
readAddressDebugItem,
|
readAddressDebugItem,
|
||||||
readAssistantMcpDiscoveryMetadataAmbiguityDetected,
|
readAssistantMcpDiscoveryMetadataAmbiguityDetected,
|
||||||
readAssistantMcpDiscoveryMetadataAmbiguityEntitySets,
|
readAssistantMcpDiscoveryMetadataAmbiguityEntitySets,
|
||||||
|
readAssistantMcpDiscoveryEntityCandidates,
|
||||||
readAssistantMcpDiscoveryMetadataRouteFamily,
|
readAssistantMcpDiscoveryMetadataRouteFamily,
|
||||||
readAssistantMcpDiscoveryMetadataSelectedEntitySet,
|
readAssistantMcpDiscoveryMetadataSelectedEntitySet,
|
||||||
readAddressDebugTemporalScope,
|
readAddressDebugTemporalScope,
|
||||||
|
|
@ -626,6 +627,10 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
carryoverSourceDebug,
|
carryoverSourceDebug,
|
||||||
deps.toNonEmptyString
|
deps.toNonEmptyString
|
||||||
);
|
);
|
||||||
|
const sourceDiscoveryEntityCandidates = readAssistantMcpDiscoveryEntityCandidates(
|
||||||
|
carryoverSourceDebug,
|
||||||
|
deps.toNonEmptyString
|
||||||
|
);
|
||||||
const llmExplicitIntent = deps.toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
const llmExplicitIntent = deps.toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
||||||
const llmSelectedObjectScopeDetected =
|
const llmSelectedObjectScopeDetected =
|
||||||
llmPreDecomposeMeta?.predecomposeContract?.semantics?.selected_object_scope_detected === true;
|
llmPreDecomposeMeta?.predecomposeContract?.semantics?.selected_object_scope_detected === true;
|
||||||
|
|
@ -958,6 +963,8 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
previous_anchor_type: previousAnchorType ?? undefined,
|
previous_anchor_type: previousAnchorType ?? undefined,
|
||||||
previous_anchor_value: previousAnchor,
|
previous_anchor_value: previousAnchor,
|
||||||
previous_discovery_pilot_scope: sourceDiscoveryPilotScope ?? undefined,
|
previous_discovery_pilot_scope: sourceDiscoveryPilotScope ?? undefined,
|
||||||
|
previous_discovery_entity_candidates:
|
||||||
|
sourceDiscoveryEntityCandidates.length > 0 ? sourceDiscoveryEntityCandidates : undefined,
|
||||||
previous_discovery_metadata_route_family: sourceDiscoveryMetadataRouteFamily ?? undefined,
|
previous_discovery_metadata_route_family: sourceDiscoveryMetadataRouteFamily ?? undefined,
|
||||||
previous_discovery_metadata_selected_entity_set: sourceDiscoveryMetadataSelectedEntitySet ?? undefined,
|
previous_discovery_metadata_selected_entity_set: sourceDiscoveryMetadataSelectedEntitySet ?? undefined,
|
||||||
previous_discovery_metadata_ambiguity_detected: sourceDiscoveryMetadataAmbiguityDetected || undefined,
|
previous_discovery_metadata_ambiguity_detected: sourceDiscoveryMetadataAmbiguityDetected || undefined,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
applyTemporalCarryoverFilters,
|
applyTemporalCarryoverFilters,
|
||||||
buildRootScopedCarryoverFilters,
|
buildRootScopedCarryoverFilters,
|
||||||
hydrateInventoryRootFrameState,
|
hydrateInventoryRootFrameState,
|
||||||
|
readAddressDebugCounterparty,
|
||||||
readAddressDebugIntent,
|
readAddressDebugIntent,
|
||||||
readAddressDebugTemporalScope,
|
readAddressDebugTemporalScope,
|
||||||
resolveNavigationSessionContextState,
|
resolveNavigationSessionContextState,
|
||||||
|
|
@ -191,6 +192,49 @@ describe("assistantContinuityPolicy organization authority", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("prefers the resolved entity from grounded entity-resolution discovery for counterparty carryover", () => {
|
||||||
|
const 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: {
|
||||||
|
asked_domain_family: "entity_resolution",
|
||||||
|
asked_action_family: "search_business_entity",
|
||||||
|
explicit_entity_candidates: ["СВК"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bridge: {
|
||||||
|
bridge_status: "answer_draft_ready",
|
||||||
|
business_fact_answer_allowed: true,
|
||||||
|
pilot: {
|
||||||
|
pilot_scope: "entity_resolution_search_v1",
|
||||||
|
derived_entity_resolution: {
|
||||||
|
requested_entity: "СВК",
|
||||||
|
resolution_status: "resolved",
|
||||||
|
resolved_entity: "Группа СВК",
|
||||||
|
ambiguity_candidates: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
answer_draft: {
|
||||||
|
answer_mode: "confirmed_with_bounded_inference"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(readAddressDebugCounterparty(debug)).toBe("Группа СВК");
|
||||||
|
expect(resolveAddressDebugCarryoverFilters(debug)).toEqual({
|
||||||
|
counterparty: "Группа СВК"
|
||||||
|
});
|
||||||
|
expect(resolveAddressDebugAnchorContext(debug)).toEqual({
|
||||||
|
anchorType: "counterparty",
|
||||||
|
anchorValue: "Группа СВК"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("resolves navigation session context through one shared helper", () => {
|
it("resolves navigation session context through one shared helper", () => {
|
||||||
const state = resolveNavigationSessionContextState({
|
const state = resolveNavigationSessionContextState({
|
||||||
session_context: {
|
session_context: {
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,24 @@ function buildSequentialDeps(results: Array<{ rows: Array<Record<string, unknown
|
||||||
return { executeAddressMcpQuery };
|
return { executeAddressMcpQuery };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildCustomQueryDeps(result: {
|
||||||
|
fetched_rows: number;
|
||||||
|
matched_rows: number;
|
||||||
|
rows: Array<Record<string, unknown>>;
|
||||||
|
raw_rows?: Array<Record<string, unknown>>;
|
||||||
|
error?: string | null;
|
||||||
|
}) {
|
||||||
|
return {
|
||||||
|
executeAddressMcpQuery: vi.fn(async () => ({
|
||||||
|
fetched_rows: result.fetched_rows,
|
||||||
|
matched_rows: result.matched_rows,
|
||||||
|
rows: result.rows,
|
||||||
|
raw_rows: result.raw_rows ?? result.rows,
|
||||||
|
error: result.error ?? null
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function buildMetadataDeps(rows: Array<Record<string, unknown>>, error: string | null = null) {
|
function buildMetadataDeps(rows: Array<Record<string, unknown>>, error: string | null = null) {
|
||||||
return {
|
return {
|
||||||
executeAddressMcpMetadata: vi.fn(async () => ({
|
executeAddressMcpMetadata: vi.fn(async () => ({
|
||||||
|
|
@ -108,11 +126,15 @@ describe("assistant MCP discovery answer adapter", () => {
|
||||||
|
|
||||||
expect(draft.answer_mode).toBe("confirmed_with_bounded_inference");
|
expect(draft.answer_mode).toBe("confirmed_with_bounded_inference");
|
||||||
expect(draft.headline).toContain("документ");
|
expect(draft.headline).toContain("документ");
|
||||||
expect(draft.confirmed_lines).toContain("1C document rows were found for counterparty SVK");
|
expect(draft.headline).toContain("2020");
|
||||||
|
expect(draft.headline).toContain("SVK");
|
||||||
|
expect(draft.confirmed_lines).toContain("В 1С найдены строки документов по контрагенту SVK за 2020.");
|
||||||
expect(draft.inference_lines).toContain(
|
expect(draft.inference_lines).toContain(
|
||||||
"Counterparty document evidence is limited to confirmed 1C document rows in the checked scope"
|
"Срез документов по контрагенту SVK за 2020 ограничен только подтвержденными строками документов, найденными этим поиском."
|
||||||
|
);
|
||||||
|
expect(draft.unknown_lines).toContain(
|
||||||
|
"Полный исторический срез документов по контрагенту SVK вне периода 2020 этим поиском не подтвержден."
|
||||||
);
|
);
|
||||||
expect(draft.unknown_lines).toContain("Full document history outside the checked period is not proven by this MCP discovery pilot");
|
|
||||||
expect(draft.must_not_claim).toContain("Do not claim full document history outside the checked period.");
|
expect(draft.must_not_claim).toContain("Do not claim full document history outside the checked period.");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -134,16 +156,54 @@ describe("assistant MCP discovery answer adapter", () => {
|
||||||
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
|
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
|
||||||
|
|
||||||
expect(draft.answer_mode).toBe("confirmed_with_bounded_inference");
|
expect(draft.answer_mode).toBe("confirmed_with_bounded_inference");
|
||||||
expect(draft.headline).toContain("движений");
|
expect(draft.headline).toContain("движени");
|
||||||
expect(draft.confirmed_lines).toContain("1C movement rows were found for counterparty SVK");
|
expect(draft.headline).toContain("2020");
|
||||||
|
expect(draft.headline).toContain("SVK");
|
||||||
|
expect(draft.confirmed_lines).toContain("В 1С найдены строки движений по контрагенту SVK за 2020.");
|
||||||
expect(draft.inference_lines).toContain(
|
expect(draft.inference_lines).toContain(
|
||||||
"Counterparty movement evidence is limited to confirmed 1C movement rows in the checked scope"
|
"Срез движений по контрагенту SVK за 2020 ограничен только подтвержденными строками движений, найденными этим поиском."
|
||||||
|
);
|
||||||
|
expect(draft.unknown_lines).toContain(
|
||||||
|
"Полный исторический срез движений по контрагенту SVK вне периода 2020 этим поиском не подтвержден."
|
||||||
);
|
);
|
||||||
expect(draft.unknown_lines).toContain("Full movement history outside the checked period is not proven by this MCP discovery pilot");
|
|
||||||
expect(draft.must_not_claim).toContain("Do not claim full movement history outside the checked period.");
|
expect(draft.must_not_claim).toContain("Do not claim full movement history outside the checked period.");
|
||||||
expect(draft.must_not_claim).toContain("Do not present the confirmed movement rows as a complete movement universe.");
|
expect(draft.must_not_claim).toContain("Do not present the confirmed movement rows as a complete movement universe.");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps bounded-only movement answers tied to the resolved entity and checked period", async () => {
|
||||||
|
const planner = planAssistantMcpDiscovery({
|
||||||
|
turnMeaning: {
|
||||||
|
asked_domain_family: "movements",
|
||||||
|
asked_action_family: "list_movements",
|
||||||
|
explicit_entity_candidates: ["Группа СВК"],
|
||||||
|
explicit_date_scope: "2020",
|
||||||
|
unsupported_but_understood_family: "movement_evidence"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const pilot = await executeAssistantMcpDiscoveryPilot(
|
||||||
|
planner,
|
||||||
|
buildCustomQueryDeps({
|
||||||
|
fetched_rows: 100,
|
||||||
|
matched_rows: 0,
|
||||||
|
rows: [],
|
||||||
|
raw_rows: [{ Period: "2020-06-30T00:00:00", Counterparty: "Группа СВК", Registrar: "Move1" }]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
|
||||||
|
|
||||||
|
expect(draft.answer_mode).toBe("bounded_inference_only");
|
||||||
|
expect(draft.headline).toContain("движени");
|
||||||
|
expect(draft.headline).toContain("Группа СВК");
|
||||||
|
expect(draft.headline).toContain("2020");
|
||||||
|
expect(draft.inference_lines).toContain(
|
||||||
|
"По движениям по контрагенту Группа СВК за 2020 удалось проверить только ограниченный срез 1С; подтвержденных строк движений этим поиском не найдено."
|
||||||
|
);
|
||||||
|
expect(draft.unknown_lines).toContain(
|
||||||
|
"Полный исторический срез движений по контрагенту Группа СВК вне периода 2020 этим поиском не подтвержден."
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("asks for clarification when discovery did not execute due to missing scope", async () => {
|
it("asks for clarification when discovery did not execute due to missing scope", async () => {
|
||||||
const planner = planAssistantMcpDiscovery({
|
const planner = planAssistantMcpDiscovery({
|
||||||
turnMeaning: {
|
turnMeaning: {
|
||||||
|
|
@ -372,8 +432,9 @@ describe("assistant MCP discovery answer adapter", () => {
|
||||||
const confirmedText = draft.confirmed_lines.join("\n");
|
const confirmedText = draft.confirmed_lines.join("\n");
|
||||||
|
|
||||||
expect(draft.answer_mode).toBe("confirmed_with_bounded_inference");
|
expect(draft.answer_mode).toBe("confirmed_with_bounded_inference");
|
||||||
expect(draft.headline).toContain("денежных движений");
|
expect(draft.headline).toContain("входящих денежных поступлений");
|
||||||
expect(confirmedText).toContain("3 750,50 руб.");
|
expect(confirmedText).toContain("3 750,50 руб.");
|
||||||
|
expect(confirmedText).toContain("входящих денежных поступлений");
|
||||||
expect(confirmedText).toContain("2020-01-15");
|
expect(confirmedText).toContain("2020-01-15");
|
||||||
expect(confirmedText).toContain("2020-02-20");
|
expect(confirmedText).toContain("2020-02-20");
|
||||||
expect(draft.unknown_lines).toContain("Full turnover outside the checked period is not proven by this MCP discovery pilot");
|
expect(draft.unknown_lines).toContain("Full turnover outside the checked period is not proven by this MCP discovery pilot");
|
||||||
|
|
|
||||||
|
|
@ -148,12 +148,12 @@ describe("assistant MCP discovery pilot executor", () => {
|
||||||
expect(result.pilot_status).toBe("executed");
|
expect(result.pilot_status).toBe("executed");
|
||||||
expect(result.pilot_scope).toBe("counterparty_document_evidence_query_documents_v1");
|
expect(result.pilot_scope).toBe("counterparty_document_evidence_query_documents_v1");
|
||||||
expect(result.executed_primitives).toEqual(["query_documents"]);
|
expect(result.executed_primitives).toEqual(["query_documents"]);
|
||||||
expect(result.evidence.confirmed_facts).toContain("1C document rows were found for counterparty SVK");
|
expect(result.evidence.confirmed_facts).toContain("В 1С найдены строки документов по контрагенту SVK за 2020.");
|
||||||
expect(result.evidence.inferred_facts).toContain(
|
expect(result.evidence.inferred_facts).toContain(
|
||||||
"Counterparty document evidence is limited to confirmed 1C document rows in the checked scope"
|
"Срез документов по контрагенту SVK за 2020 ограничен только подтвержденными строками документов, найденными этим поиском."
|
||||||
);
|
);
|
||||||
expect(result.evidence.unknown_facts).toContain(
|
expect(result.evidence.unknown_facts).toContain(
|
||||||
"Full document history outside the checked period is not proven by this MCP discovery pilot"
|
"Полный исторический срез документов по контрагенту SVK вне периода 2020 этим поиском не подтвержден."
|
||||||
);
|
);
|
||||||
expect(result.source_rows_summary).toBe("2 MCP document rows fetched, 2 matched document scope");
|
expect(result.source_rows_summary).toBe("2 MCP document rows fetched, 2 matched document scope");
|
||||||
});
|
});
|
||||||
|
|
@ -180,12 +180,12 @@ describe("assistant MCP discovery pilot executor", () => {
|
||||||
expect(result.executed_primitives).toEqual(["query_movements"]);
|
expect(result.executed_primitives).toEqual(["query_movements"]);
|
||||||
expect(result.derived_value_flow).toBeNull();
|
expect(result.derived_value_flow).toBeNull();
|
||||||
expect(result.derived_bidirectional_value_flow).toBeNull();
|
expect(result.derived_bidirectional_value_flow).toBeNull();
|
||||||
expect(result.evidence.confirmed_facts).toContain("1C movement rows were found for counterparty SVK");
|
expect(result.evidence.confirmed_facts).toContain("В 1С найдены строки движений по контрагенту SVK за 2020.");
|
||||||
expect(result.evidence.inferred_facts).toContain(
|
expect(result.evidence.inferred_facts).toContain(
|
||||||
"Counterparty movement evidence is limited to confirmed 1C movement rows in the checked scope"
|
"Срез движений по контрагенту SVK за 2020 ограничен только подтвержденными строками движений, найденными этим поиском."
|
||||||
);
|
);
|
||||||
expect(result.evidence.unknown_facts).toContain(
|
expect(result.evidence.unknown_facts).toContain(
|
||||||
"Full movement history outside the checked period is not proven by this MCP discovery pilot"
|
"Полный исторический срез движений по контрагенту SVK вне периода 2020 этим поиском не подтвержден."
|
||||||
);
|
);
|
||||||
expect(result.source_rows_summary).toBe("2 MCP movement rows fetched, 2 matched movement scope");
|
expect(result.source_rows_summary).toBe("2 MCP movement rows fetched, 2 matched movement scope");
|
||||||
|
|
||||||
|
|
@ -762,6 +762,56 @@ describe("assistant MCP discovery pilot executor", () => {
|
||||||
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(1);
|
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps document and movement evidence scoped to the resolved entity and checked period", async () => {
|
||||||
|
const documentPlanner = planAssistantMcpDiscovery({
|
||||||
|
turnMeaning: {
|
||||||
|
asked_domain_family: "documents",
|
||||||
|
asked_action_family: "list_documents",
|
||||||
|
explicit_entity_candidates: ["Группа СВК"],
|
||||||
|
explicit_date_scope: "2020",
|
||||||
|
unsupported_but_understood_family: "document_evidence"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const documentResult = await executeAssistantMcpDiscoveryPilot(
|
||||||
|
documentPlanner,
|
||||||
|
buildDeps([{ Period: "2020-01-15T00:00:00", Counterparty: "Группа СВК", Registrar: "Doc1" }])
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(documentResult.evidence.confirmed_facts).toContain(
|
||||||
|
"В 1С найдены строки документов по контрагенту Группа СВК за 2020."
|
||||||
|
);
|
||||||
|
expect(documentResult.evidence.inferred_facts).toContain(
|
||||||
|
"Срез документов по контрагенту Группа СВК за 2020 ограничен только подтвержденными строками документов, найденными этим поиском."
|
||||||
|
);
|
||||||
|
expect(documentResult.evidence.unknown_facts).toContain(
|
||||||
|
"Полный исторический срез документов по контрагенту Группа СВК вне периода 2020 этим поиском не подтвержден."
|
||||||
|
);
|
||||||
|
|
||||||
|
const movementPlanner = planAssistantMcpDiscovery({
|
||||||
|
turnMeaning: {
|
||||||
|
asked_domain_family: "movements",
|
||||||
|
asked_action_family: "list_movements",
|
||||||
|
explicit_entity_candidates: ["Группа СВК"],
|
||||||
|
explicit_date_scope: "2020",
|
||||||
|
unsupported_but_understood_family: "movement_evidence"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const movementResult = await executeAssistantMcpDiscoveryPilot(
|
||||||
|
movementPlanner,
|
||||||
|
buildDeps([{ Period: "2020-01-15T00:00:00", Counterparty: "Группа СВК", Registrar: "Move1" }])
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(movementResult.evidence.confirmed_facts).toContain(
|
||||||
|
"В 1С найдены строки движений по контрагенту Группа СВК за 2020."
|
||||||
|
);
|
||||||
|
expect(movementResult.evidence.inferred_facts).toContain(
|
||||||
|
"Срез движений по контрагенту Группа СВК за 2020 ограничен только подтвержденными строками движений, найденными этим поиском."
|
||||||
|
);
|
||||||
|
expect(movementResult.evidence.unknown_facts).toContain(
|
||||||
|
"Полный исторический срез движений по контрагенту Группа СВК вне периода 2020 этим поиском не подтвержден."
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("records MCP errors as limitations without converting them into facts", async () => {
|
it("records MCP errors as limitations without converting them into facts", async () => {
|
||||||
const planner = planAssistantMcpDiscovery({
|
const planner = planAssistantMcpDiscovery({
|
||||||
turnMeaning: {
|
turnMeaning: {
|
||||||
|
|
|
||||||
|
|
@ -54,10 +54,10 @@ describe("assistant MCP discovery response candidate", () => {
|
||||||
requires_user_clarification: false,
|
requires_user_clarification: false,
|
||||||
answer_draft: {
|
answer_draft: {
|
||||||
answer_mode: "confirmed_with_bounded_inference",
|
answer_mode: "confirmed_with_bounded_inference",
|
||||||
headline: "По данным 1С найдены строки денежных движений; сумму можно называть только в рамках проверенного периода и найденных строк.",
|
headline: "По данным 1С найдены строки входящих денежных поступлений; сумму можно называть только в рамках проверенного периода и найденных строк.",
|
||||||
confirmed_lines: [
|
confirmed_lines: [
|
||||||
"1C value-flow rows were found for counterparty SVK",
|
"1C value-flow rows were found for counterparty SVK",
|
||||||
"По найденным строкам денежных движений в 1С по контрагенту SVK за период 2020 сумма составляет 3 750 руб."
|
"По найденным строкам входящих денежных поступлений в 1С по контрагенту SVK за период 2020 сумма входящих денежных поступлений составляет 3 750 руб."
|
||||||
],
|
],
|
||||||
inference_lines: ["Counterparty value-flow total was calculated from confirmed 1C movement rows"],
|
inference_lines: ["Counterparty value-flow total was calculated from confirmed 1C movement rows"],
|
||||||
unknown_lines: ["Full turnover outside the checked period is not proven by this MCP discovery pilot"],
|
unknown_lines: ["Full turnover outside the checked period is not proven by this MCP discovery pilot"],
|
||||||
|
|
@ -69,9 +69,9 @@ describe("assistant MCP discovery response candidate", () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(candidate.candidate_status).toBe("ready_for_guarded_use");
|
expect(candidate.candidate_status).toBe("ready_for_guarded_use");
|
||||||
expect(candidate.reply_text).toContain("В 1С найдены строки денежных движений по контрагенту SVK.");
|
expect(candidate.reply_text).toContain("В 1С найдены строки входящих денежных поступлений по контрагенту SVK.");
|
||||||
expect(candidate.reply_text).toContain("3 750 руб.");
|
expect(candidate.reply_text).toContain("3 750 руб.");
|
||||||
expect(candidate.reply_text).toContain("Полный оборот вне проверенного периода этим поиском не подтвержден.");
|
expect(candidate.reply_text).toContain("Полный объем входящих поступлений вне проверенного периода этим поиском не подтвержден.");
|
||||||
expect(candidate.reply_text).not.toContain("pilot_");
|
expect(candidate.reply_text).not.toContain("pilot_");
|
||||||
expect(candidate.reply_text).not.toContain("query_movements");
|
expect(candidate.reply_text).not.toContain("query_movements");
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -209,6 +209,145 @@ describe("assistant MCP discovery turn input adapter", () => {
|
||||||
expect(result.reason_codes).toContain("mcp_discovery_entity_scope_from_raw_entity_search");
|
expect(result.reason_codes).toContain("mcp_discovery_entity_scope_from_raw_entity_search");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("seeds document evidence follow-up from prior entity-resolution grounding", () => {
|
||||||
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
|
userMessage: "по нему документы за 2020 год",
|
||||||
|
followupContext: {
|
||||||
|
previous_discovery_pilot_scope: "entity_resolution_search_v1",
|
||||||
|
previous_anchor_type: "counterparty",
|
||||||
|
previous_anchor_value: "Группа СВК",
|
||||||
|
previous_discovery_entity_candidates: ["Группа СВК", "СВК"]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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("document evidence");
|
||||||
|
expect(result.turn_meaning_ref).toMatchObject({
|
||||||
|
asked_domain_family: "documents",
|
||||||
|
asked_action_family: "list_documents",
|
||||||
|
explicit_entity_candidates: ["Группа СВК"],
|
||||||
|
explicit_date_scope: "2020",
|
||||||
|
unsupported_but_understood_family: "document_evidence",
|
||||||
|
stale_replay_forbidden: true
|
||||||
|
});
|
||||||
|
expect(result.reason_codes).toContain("mcp_discovery_entity_resolution_grounded_document_followup");
|
||||||
|
expect(result.reason_codes).toContain("mcp_discovery_counterparty_from_followup_context");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("seeds movement evidence follow-up from prior entity-resolution grounding", () => {
|
||||||
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
|
userMessage: "а теперь по нему движения за 2020 год",
|
||||||
|
followupContext: {
|
||||||
|
previous_discovery_pilot_scope: "entity_resolution_search_v1",
|
||||||
|
previous_discovery_entity_candidates: ["Группа СВК", "СВК"]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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("movement evidence");
|
||||||
|
expect(result.turn_meaning_ref).toMatchObject({
|
||||||
|
asked_domain_family: "movements",
|
||||||
|
asked_action_family: "list_movements",
|
||||||
|
explicit_entity_candidates: ["Группа СВК"],
|
||||||
|
explicit_date_scope: "2020",
|
||||||
|
unsupported_but_understood_family: "movement_evidence",
|
||||||
|
stale_replay_forbidden: true
|
||||||
|
});
|
||||||
|
expect(result.reason_codes).toContain("mcp_discovery_entity_resolution_grounded_movement_followup");
|
||||||
|
expect(result.reason_codes).toContain("mcp_discovery_counterparty_from_followup_context");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("overrides a wrong exact document intent when a grounded document follow-up asks to switch into movements", () => {
|
||||||
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
|
userMessage: "а теперь по нему движения за 2020 год",
|
||||||
|
assistantTurnMeaning: {
|
||||||
|
asked_domain_family: "inventory",
|
||||||
|
asked_action_family: "purchase_documents",
|
||||||
|
explicit_intent_candidate: "inventory_purchase_documents_for_item",
|
||||||
|
explicit_entity_candidates: [{ value: "нему" }]
|
||||||
|
},
|
||||||
|
predecomposeContract: {
|
||||||
|
entities: {
|
||||||
|
counterparty: "нему"
|
||||||
|
},
|
||||||
|
period: {
|
||||||
|
period_from: "2020-01-01",
|
||||||
|
period_to: "2020-12-31"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
followupContext: {
|
||||||
|
previous_discovery_pilot_scope: "counterparty_document_evidence_query_documents_v1",
|
||||||
|
previous_filters: {
|
||||||
|
counterparty: "Группа СВК",
|
||||||
|
period_from: "2020-01-01",
|
||||||
|
period_to: "2020-12-31"
|
||||||
|
},
|
||||||
|
previous_discovery_entity_candidates: ["Группа СВК", "СВК"]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.adapter_status).toBe("ready");
|
||||||
|
expect(result.should_run_discovery).toBe(true);
|
||||||
|
expect(result.source_signal).toBe("assistant_turn_meaning");
|
||||||
|
expect(result.semantic_data_need).toBe("movement evidence");
|
||||||
|
expect(result.turn_meaning_ref).toMatchObject({
|
||||||
|
asked_domain_family: "movements",
|
||||||
|
asked_action_family: "list_movements",
|
||||||
|
explicit_entity_candidates: ["Группа СВК"],
|
||||||
|
explicit_date_scope: "2020",
|
||||||
|
unsupported_but_understood_family: "movement_evidence",
|
||||||
|
stale_replay_forbidden: true
|
||||||
|
});
|
||||||
|
expect(result.reason_codes).toContain("mcp_discovery_document_evidence_grounded_movement_followup");
|
||||||
|
expect(result.reason_codes).not.toContain("mcp_discovery_not_applicable_for_supported_exact_turn");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("overrides a supported exact turnover intent when a grounded entity follow-up asks about incoming money", () => {
|
||||||
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
|
userMessage: "сколько получили по нему за 2020 год",
|
||||||
|
assistantTurnMeaning: {
|
||||||
|
asked_domain_family: "counterparty",
|
||||||
|
asked_action_family: "turnover",
|
||||||
|
explicit_intent_candidate: "customer_revenue_and_payments",
|
||||||
|
explicit_entity_candidates: [{ value: "нему" }]
|
||||||
|
},
|
||||||
|
predecomposeContract: {
|
||||||
|
entities: {
|
||||||
|
counterparty: "нему"
|
||||||
|
},
|
||||||
|
period: {
|
||||||
|
period_from: "2020-01-01",
|
||||||
|
period_to: "2020-12-31"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
followupContext: {
|
||||||
|
previous_discovery_pilot_scope: "entity_resolution_search_v1",
|
||||||
|
previous_filters: {
|
||||||
|
counterparty: "Группа СВК"
|
||||||
|
},
|
||||||
|
previous_discovery_entity_candidates: ["Группа СВК", "СВК"]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.adapter_status).toBe("ready");
|
||||||
|
expect(result.should_run_discovery).toBe(true);
|
||||||
|
expect(result.semantic_data_need).toBe("counterparty value-flow evidence");
|
||||||
|
expect(result.turn_meaning_ref).toMatchObject({
|
||||||
|
asked_domain_family: "counterparty_value",
|
||||||
|
asked_action_family: "turnover",
|
||||||
|
explicit_entity_candidates: ["Группа СВК"],
|
||||||
|
explicit_date_scope: "2020",
|
||||||
|
unsupported_but_understood_family: "counterparty_value_or_turnover",
|
||||||
|
stale_replay_forbidden: true
|
||||||
|
});
|
||||||
|
expect(result.reason_codes).toContain("mcp_discovery_grounded_value_flow_followup");
|
||||||
|
expect(result.reason_codes).not.toContain("mcp_discovery_not_applicable_for_supported_exact_turn");
|
||||||
|
});
|
||||||
|
|
||||||
it("seeds short monthly follow-up from prior bidirectional discovery context", () => {
|
it("seeds short monthly follow-up from prior bidirectional discovery context", () => {
|
||||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
userMessage: "а по месяцам?",
|
userMessage: "а по месяцам?",
|
||||||
|
|
|
||||||
|
|
@ -1114,6 +1114,65 @@ describe("assistantTransitionPolicy", () => {
|
||||||
period_to: "2020-12-31"
|
period_to: "2020-12-31"
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it("carries resolved entity candidates from grounded entity-resolution discovery into followup context", () => {
|
||||||
|
const policy = buildPolicy({
|
||||||
|
findLastAddressAssistantItem: () => null,
|
||||||
|
hasAddressFollowupContextSignal: () => true
|
||||||
|
});
|
||||||
|
|
||||||
|
const carryover = policy.resolveAddressFollowupCarryoverContext(
|
||||||
|
"по нему документы за 2020 год",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
text: "В текущем каталожном срезе 1С по запросу \"СВК\" найден контрагент \"Группа СВК\".",
|
||||||
|
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: {
|
||||||
|
asked_domain_family: "entity_resolution",
|
||||||
|
asked_action_family: "search_business_entity",
|
||||||
|
explicit_entity_candidates: ["СВК"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bridge: {
|
||||||
|
bridge_status: "answer_draft_ready",
|
||||||
|
business_fact_answer_allowed: true,
|
||||||
|
pilot: {
|
||||||
|
pilot_scope: "entity_resolution_search_v1",
|
||||||
|
derived_entity_resolution: {
|
||||||
|
requested_entity: "СВК",
|
||||||
|
resolution_status: "resolved",
|
||||||
|
resolved_entity: "Группа СВК",
|
||||||
|
ambiguity_candidates: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
answer_draft: {
|
||||||
|
answer_mode: "confirmed_with_bounded_inference"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(carryover?.followupContext?.previous_discovery_pilot_scope).toBe("entity_resolution_search_v1");
|
||||||
|
expect(carryover?.followupContext?.previous_discovery_entity_candidates).toEqual(["Группа СВК", "СВК"]);
|
||||||
|
expect(carryover?.followupContext?.previous_anchor_type).toBe("counterparty");
|
||||||
|
expect(carryover?.followupContext?.previous_anchor_value).toBe("Группа СВК");
|
||||||
|
expect(carryover?.followupContext?.previous_filters).toMatchObject({
|
||||||
|
counterparty: "Группа СВК"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("carries grounded metadata downstream route hints into followup context", () => {
|
it("carries grounded metadata downstream route hints into followup context", () => {
|
||||||
const policy = buildPolicy({
|
const policy = buildPolicy({
|
||||||
findLastAddressAssistantItem: () => null,
|
findLastAddressAssistantItem: () => null,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue