387 lines
23 KiB
JavaScript
387 lines
23 KiB
JavaScript
"use strict";
|
||
Object.defineProperty(exports, "__esModule", { value: true });
|
||
exports.ASSISTANT_MCP_DISCOVERY_ANSWER_DRAFT_SCHEMA_VERSION = void 0;
|
||
exports.buildAssistantMcpDiscoveryAnswerDraft = buildAssistantMcpDiscoveryAnswerDraft;
|
||
exports.ASSISTANT_MCP_DISCOVERY_ANSWER_DRAFT_SCHEMA_VERSION = "assistant_mcp_discovery_answer_draft_v1";
|
||
function normalizeReasonCode(value) {
|
||
const normalized = value
|
||
.trim()
|
||
.replace(/[^\p{L}\p{N}_.:-]+/gu, "_")
|
||
.replace(/^_+|_+$/g, "")
|
||
.toLowerCase();
|
||
return normalized.length > 0 ? normalized.slice(0, 120) : null;
|
||
}
|
||
function pushReason(target, value) {
|
||
const normalized = normalizeReasonCode(value);
|
||
if (normalized && !target.includes(normalized)) {
|
||
target.push(normalized);
|
||
}
|
||
}
|
||
function uniqueStrings(values) {
|
||
const result = [];
|
||
for (const value of values) {
|
||
const text = String(value ?? "").trim();
|
||
if (text && !result.includes(text)) {
|
||
result.push(text);
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
function isInternalMechanicsLine(value) {
|
||
const text = value.toLowerCase();
|
||
return (text.includes("primitive") ||
|
||
text.includes("query_documents") ||
|
||
text.includes("query_movements") ||
|
||
text.includes("resolve_entity_reference") ||
|
||
text.includes("probe_coverage") ||
|
||
text.includes("explain_evidence_basis") ||
|
||
text.includes("pilot_only_executes") ||
|
||
text.includes("pilot_") ||
|
||
text.includes("runtime_") ||
|
||
text.includes("planner_") ||
|
||
text.includes("catalog_"));
|
||
}
|
||
function userFacingLimitations(values) {
|
||
return uniqueStrings(values).filter((value) => !isInternalMechanicsLine(value));
|
||
}
|
||
function modeFor(pilot) {
|
||
if (pilot.pilot_status === "blocked") {
|
||
return "blocked";
|
||
}
|
||
if (pilot.pilot_status === "skipped_needs_clarification") {
|
||
return "needs_clarification";
|
||
}
|
||
if (pilot.evidence.answer_permission === "confirmed_answer") {
|
||
return "confirmed_with_bounded_inference";
|
||
}
|
||
if (pilot.evidence.answer_permission === "bounded_inference") {
|
||
return "bounded_inference_only";
|
||
}
|
||
return "checked_sources_only";
|
||
}
|
||
function isValueFlowPilot(pilot) {
|
||
return (pilot.pilot_scope === "counterparty_value_flow_query_movements_v1" ||
|
||
pilot.pilot_scope === "counterparty_supplier_payout_query_movements_v1" ||
|
||
pilot.pilot_scope === "counterparty_bidirectional_value_flow_query_movements_v1");
|
||
}
|
||
function isDocumentPilot(pilot) {
|
||
return pilot.pilot_scope === "counterparty_document_evidence_query_documents_v1";
|
||
}
|
||
function isMovementPilot(pilot) {
|
||
return pilot.pilot_scope === "counterparty_movement_evidence_query_movements_v1";
|
||
}
|
||
function isMetadataPilot(pilot) {
|
||
return pilot.pilot_scope === "metadata_inspection_v1";
|
||
}
|
||
function metadataRouteFamilyLabelRu(routeFamily) {
|
||
if (routeFamily === "document_evidence") {
|
||
return "контур документов";
|
||
}
|
||
if (routeFamily === "movement_evidence") {
|
||
return "контур движений/регистров";
|
||
}
|
||
if (routeFamily === "catalog_drilldown") {
|
||
return "контур справочников и связанных объектов";
|
||
}
|
||
return null;
|
||
}
|
||
function headlineFor(mode, pilot) {
|
||
const askedMonthlyBreakdown = pilot.derived_bidirectional_value_flow?.aggregation_axis === "month" ||
|
||
pilot.derived_value_flow?.aggregation_axis === "month";
|
||
if (isMovementPilot(pilot) && mode === "confirmed_with_bounded_inference") {
|
||
return "По данным 1С найдены строки движений; ответ ограничен проверенным периодом и найденными строками.";
|
||
}
|
||
if (pilot.derived_metadata_surface && mode === "confirmed_with_bounded_inference") {
|
||
if (pilot.derived_metadata_surface.ambiguity_detected) {
|
||
return "По метаданным 1С найдены конкурирующие schema-поверхности; перед следующим шагом нужно удержать неоднозначность явно.";
|
||
}
|
||
if (pilot.derived_metadata_surface.downstream_route_family) {
|
||
return "По метаданным 1С найдена схема и заземлена вероятная поверхность для следующего безопасного шага.";
|
||
}
|
||
return "По метаданным 1С найдена доступная схема для дальнейшего безопасного поиска.";
|
||
}
|
||
if (askedMonthlyBreakdown && pilot.derived_bidirectional_value_flow && mode === "confirmed_with_bounded_inference") {
|
||
return "По данным 1С найдены строки входящих и исходящих денежных движений; нетто и помесячная раскладка могут называться только как расчет по найденным строкам и проверенному периоду.";
|
||
}
|
||
if (askedMonthlyBreakdown && pilot.derived_value_flow && mode === "confirmed_with_bounded_inference") {
|
||
if (pilot.derived_value_flow.value_flow_direction === "outgoing_supplier_payout") {
|
||
return "По данным 1С найдены строки исходящих платежей/списаний; сумму и помесячную раскладку можно называть только в рамках проверенного периода и найденных строк.";
|
||
}
|
||
return "По данным 1С найдены строки денежных движений; сумму и помесячную раскладку можно называть только в рамках проверенного периода и найденных строк.";
|
||
}
|
||
if (pilot.derived_bidirectional_value_flow && mode === "confirmed_with_bounded_inference") {
|
||
return "По данным 1С найдены строки входящих и исходящих денежных движений; нетто можно называть только как расчет по найденным строкам и проверенному периоду.";
|
||
}
|
||
if (pilot.derived_value_flow && mode === "confirmed_with_bounded_inference") {
|
||
if (pilot.derived_value_flow.value_flow_direction === "outgoing_supplier_payout") {
|
||
return "По данным 1С найдены строки исходящих платежей/списаний; сумму можно называть только в рамках проверенного периода и найденных строк.";
|
||
}
|
||
return "По данным 1С найдены строки денежных движений; сумму можно называть только в рамках проверенного периода и найденных строк.";
|
||
}
|
||
if (isDocumentPilot(pilot) && mode === "confirmed_with_bounded_inference") {
|
||
return "По данным 1С найдены строки документов; ответ ограничен проверенным периодом и найденными строками.";
|
||
}
|
||
if (mode === "confirmed_with_bounded_inference") {
|
||
return "По данным 1С есть подтвержденная активность; длительность можно оценивать только как вывод из этих строк.";
|
||
}
|
||
if (mode === "bounded_inference_only") {
|
||
return "Точный факт не подтвержден, но есть ограниченная оценка по найденной активности в 1С.";
|
||
}
|
||
if (mode === "needs_clarification") {
|
||
return "Нужно уточнить контекст перед поиском в 1С.";
|
||
}
|
||
if (mode === "blocked") {
|
||
return "Поиск в 1С заблокирован runtime-политикой до выполнения.";
|
||
}
|
||
return "Я проверил доступный контур, но подтвержденного факта для ответа не получил.";
|
||
}
|
||
function nextStepFor(mode, pilot) {
|
||
if (mode === "needs_clarification") {
|
||
return "Уточните контрагента, период или организацию, и я смогу выполнить проверку по 1С.";
|
||
}
|
||
if (mode === "confirmed_with_bounded_inference" && pilot.derived_metadata_surface) {
|
||
const surface = pilot.derived_metadata_surface;
|
||
if (surface.ambiguity_detected && surface.ambiguity_entity_sets.length > 0) {
|
||
return `Следующим шагом лучше сузить surface до одного семейства: ${surface.ambiguity_entity_sets.join(", ")}.`;
|
||
}
|
||
const routeLabel = metadataRouteFamilyLabelRu(surface.downstream_route_family);
|
||
if (surface.selected_entity_set && routeLabel) {
|
||
return `Следующим шагом могу пойти в ${routeLabel} по surface «${surface.selected_entity_set}» и уже искать подтвержденные данные, а не только схему.`;
|
||
}
|
||
}
|
||
if (mode === "checked_sources_only" && pilot.query_limitations.length > 0) {
|
||
return "Можно повторить проверку после восстановления MCP-доступа или сузить вопрос до конкретного контрагента/периода.";
|
||
}
|
||
if (mode === "blocked") {
|
||
return "Нужно сначала снять policy/blocking причину, иначе данные 1С использовать нельзя.";
|
||
}
|
||
return null;
|
||
}
|
||
function buildMustNotClaim(pilot) {
|
||
const claims = [
|
||
"Do not expose MCP primitive names, query text, debug ids, or internal execution mechanics in the user answer.",
|
||
"Do not claim rows were checked when mcp_execution_performed=false."
|
||
];
|
||
if (pilot.pilot_scope === "counterparty_lifecycle_query_documents_v1") {
|
||
claims.push("Do not claim legal registration age unless a legal registration source is confirmed.");
|
||
claims.push("Do not present inferred activity duration as a formally confirmed legal fact.");
|
||
}
|
||
if (isValueFlowPilot(pilot)) {
|
||
claims.push("Do not claim full all-time turnover unless the checked period and coverage prove it.");
|
||
claims.push("Do not present a derived sum as a legal/accounting final total outside the checked 1C rows.");
|
||
}
|
||
if (isDocumentPilot(pilot)) {
|
||
claims.push("Do not claim full document history outside the checked period.");
|
||
claims.push("Do not present the confirmed document rows as a complete document universe.");
|
||
}
|
||
if (isMovementPilot(pilot)) {
|
||
claims.push("Do not claim full movement history outside the checked period.");
|
||
claims.push("Do not present the confirmed movement rows as a complete movement universe.");
|
||
}
|
||
if (isMetadataPilot(pilot)) {
|
||
claims.push("Do not present metadata surface as confirmed business data rows.");
|
||
claims.push("Do not claim a document/register exists outside the checked metadata probe results.");
|
||
claims.push("Do not present the inferred next checked lane as already executed data retrieval.");
|
||
}
|
||
if (pilot.evidence.confirmed_facts.length === 0) {
|
||
claims.push("Do not claim a confirmed business fact when confirmed_facts is empty.");
|
||
}
|
||
return claims;
|
||
}
|
||
const RU_MONTH_LABELS_SHORT = [
|
||
"янв",
|
||
"фев",
|
||
"мар",
|
||
"апр",
|
||
"май",
|
||
"июн",
|
||
"июл",
|
||
"авг",
|
||
"сен",
|
||
"окт",
|
||
"ноя",
|
||
"дек"
|
||
];
|
||
function monthLabelRu(monthBucket) {
|
||
const match = monthBucket.match(/^(\d{4})-(\d{2})$/);
|
||
if (!match) {
|
||
return monthBucket;
|
||
}
|
||
const monthIndex = Number(match[2]) - 1;
|
||
const label = RU_MONTH_LABELS_SHORT[monthIndex] ?? match[2];
|
||
return `${label} ${match[1]}`;
|
||
}
|
||
function netLabelRu(netDirection) {
|
||
if (netDirection === "net_incoming") {
|
||
return "нетто в нашу сторону";
|
||
}
|
||
if (netDirection === "net_outgoing") {
|
||
return "нетто исходящее";
|
||
}
|
||
return "нетто нулевое";
|
||
}
|
||
function derivedActivityInferenceLine(pilot) {
|
||
const period = pilot.derived_activity_period;
|
||
if (!period) {
|
||
return null;
|
||
}
|
||
return [
|
||
`По подтвержденным строкам активности в 1С период взаимодействия можно оценить примерно как ${period.duration_human_ru}.`,
|
||
`Первая найденная активность: ${period.first_activity_date}; последняя найденная активность: ${period.latest_activity_date}.`,
|
||
"Это вывод по данным 1С, а не юридически подтвержденный возраст регистрации."
|
||
].join(" ");
|
||
}
|
||
function derivedMetadataConfirmedLine(pilot) {
|
||
const surface = pilot.derived_metadata_surface;
|
||
if (!surface) {
|
||
return null;
|
||
}
|
||
const scope = surface.metadata_scope ? ` по области "${surface.metadata_scope}"` : "";
|
||
const entitySets = surface.available_entity_sets.length > 0
|
||
? ` Типы объектов: ${surface.available_entity_sets.join(", ")}.`
|
||
: "";
|
||
const objects = surface.matched_objects.length > 0
|
||
? ` Найденные объекты: ${surface.matched_objects.slice(0, 8).join(", ")}.`
|
||
: "";
|
||
const selectedEntitySet = surface.selected_entity_set ? ` Выбранное family: ${surface.selected_entity_set}.` : "";
|
||
const selectedObjects = surface.selected_surface_objects.length > 0
|
||
? ` Выбранные surface-объекты: ${surface.selected_surface_objects.slice(0, 6).join(", ")}.`
|
||
: "";
|
||
const fields = surface.available_fields.length > 0
|
||
? ` Доступные поля/секции: ${surface.available_fields.slice(0, 12).join(", ")}.`
|
||
: "";
|
||
return `Подтвержденная metadata-поверхность 1С${scope}: ${surface.matched_rows} строк metadata-ответа.${entitySets}${objects}${selectedEntitySet}${selectedObjects}${fields}`.replace(/\s+/g, " ").trim();
|
||
}
|
||
function derivedMetadataInferenceLine(pilot) {
|
||
const surface = pilot.derived_metadata_surface;
|
||
if (!surface) {
|
||
return null;
|
||
}
|
||
if (surface.ambiguity_detected && surface.ambiguity_entity_sets.length > 0) {
|
||
return `По подтвержденной metadata-поверхности видно несколько конкурирующих family: ${surface.ambiguity_entity_sets.join(", ")}. Следующий data-lane пока нельзя выбрать без явного сужения.`;
|
||
}
|
||
const routeLabel = metadataRouteFamilyLabelRu(surface.downstream_route_family);
|
||
if (!surface.selected_entity_set || !routeLabel) {
|
||
return null;
|
||
}
|
||
return `По подтвержденной metadata-поверхности следующий проверяемый шаг можно ограниченно оценить как ${routeLabel} через family «${surface.selected_entity_set}». Это еще не выполненный data-fetch, а только grounded выбор следующего контура.`;
|
||
}
|
||
function derivedValueFlowConfirmedLine(pilot) {
|
||
const flow = pilot.derived_value_flow;
|
||
if (!flow) {
|
||
return null;
|
||
}
|
||
const counterparty = flow.counterparty ? ` по контрагенту ${flow.counterparty}` : "";
|
||
const period = flow.period_scope ? ` за период ${flow.period_scope}` : " в проверенном окне";
|
||
const movementLabel = 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"
|
||
? "Это расчет по найденным строкам 1С, а не подтверждение полного объема платежей вне проверенного окна."
|
||
: "Это расчет по найденным строкам 1С, а не подтверждение полного оборота вне проверенного окна.";
|
||
const dates = flow.first_movement_date && flow.latest_movement_date
|
||
? ` Первая найденная дата движения: ${flow.first_movement_date}; последняя: ${flow.latest_movement_date}.`
|
||
: "";
|
||
const limitCaveat = flow.coverage_limited_by_probe_limit
|
||
? " Лимит строк проверки достигнут; полный запрошенный период может быть покрыт не полностью."
|
||
: "";
|
||
return `По найденным строкам ${movementLabel} в 1С${counterparty}${period} ${totalLabel} ${flow.total_amount_human_ru} Учтено строк с суммой: ${flow.rows_with_amount} из ${flow.rows_matched}.${dates}${limitCaveat} ${caveat}`;
|
||
}
|
||
function derivedValueFlowMonthlyLines(pilot) {
|
||
const flow = pilot.derived_value_flow;
|
||
if (!flow || flow.aggregation_axis !== "month" || flow.monthly_breakdown.length === 0) {
|
||
return [];
|
||
}
|
||
return flow.monthly_breakdown.map((bucket) => {
|
||
const monthLabel = monthLabelRu(bucket.month_bucket);
|
||
if (flow.value_flow_direction === "outgoing_supplier_payout") {
|
||
return `Помесячно: ${monthLabel} — заплатили ${bucket.total_amount_human_ru} по ${bucket.rows_with_amount} строкам с суммой`;
|
||
}
|
||
return `Помесячно: ${monthLabel} — получили ${bucket.total_amount_human_ru} по ${bucket.rows_with_amount} строкам с суммой`;
|
||
});
|
||
}
|
||
function sideDateRange(first, latest) {
|
||
if (first && latest) {
|
||
return ` первая дата ${first}, последняя ${latest}`;
|
||
}
|
||
return " даты движения не выделены";
|
||
}
|
||
function derivedBidirectionalValueFlowConfirmedLine(pilot) {
|
||
const flow = pilot.derived_bidirectional_value_flow;
|
||
if (!flow) {
|
||
return null;
|
||
}
|
||
const counterparty = flow.counterparty ? ` по контрагенту ${flow.counterparty}` : "";
|
||
const period = flow.period_scope ? ` за период ${flow.period_scope}` : " в проверенном окне";
|
||
const incoming = flow.incoming_customer_revenue;
|
||
const outgoing = flow.outgoing_supplier_payout;
|
||
const netLabel = flow.net_direction === "net_incoming"
|
||
? "нетто в нашу сторону"
|
||
: flow.net_direction === "net_outgoing"
|
||
? "нетто исходящий"
|
||
: "нетто нулевое";
|
||
const limitCaveat = flow.coverage_limited_by_probe_limit
|
||
? " Лимит строк проверки достигнут хотя бы по одной стороне; полный запрошенный период может быть покрыт не полностью."
|
||
: "";
|
||
return [
|
||
`По найденным строкам 1С${counterparty}${period}: получили ${incoming.total_amount_human_ru} по входящим движениям, заплатили ${outgoing.total_amount_human_ru} по исходящим платежам/списаниям.`,
|
||
`Расчетное ${netLabel}: ${flow.net_amount_human_ru}`,
|
||
`Входящие строки с суммой: ${incoming.rows_with_amount} из ${incoming.rows_matched};${sideDateRange(incoming.first_movement_date, incoming.latest_movement_date)}.`,
|
||
`Исходящие строки с суммой: ${outgoing.rows_with_amount} из ${outgoing.rows_matched};${sideDateRange(outgoing.first_movement_date, outgoing.latest_movement_date)}.`,
|
||
`${limitCaveat} Это расчет по найденным строкам 1С, а не подтверждение полного сальдо вне проверенного окна.`
|
||
]
|
||
.join(" ")
|
||
.replace(/\s+/g, " ")
|
||
.trim();
|
||
}
|
||
function derivedBidirectionalValueFlowMonthlyLines(pilot) {
|
||
const flow = pilot.derived_bidirectional_value_flow;
|
||
if (!flow || flow.aggregation_axis !== "month" || flow.monthly_breakdown.length === 0) {
|
||
return [];
|
||
}
|
||
return flow.monthly_breakdown.map((bucket) => `Помесячно: ${monthLabelRu(bucket.month_bucket)} — получили ${bucket.incoming_total_amount_human_ru}, заплатили ${bucket.outgoing_total_amount_human_ru}, ${netLabelRu(bucket.net_direction)} ${bucket.net_amount_human_ru}`);
|
||
}
|
||
function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
|
||
const mode = modeFor(pilot);
|
||
const reasonCodes = [...pilot.reason_codes, ...pilot.evidence.reason_codes];
|
||
pushReason(reasonCodes, `answer_mode_${mode}`);
|
||
if (pilot.evidence.unknown_facts.length > 0) {
|
||
pushReason(reasonCodes, "answer_contains_unknown_fact_boundary");
|
||
}
|
||
if (pilot.evidence.inferred_facts.length > 0) {
|
||
pushReason(reasonCodes, "answer_contains_bounded_inference");
|
||
}
|
||
const derivedInferenceLine = derivedActivityInferenceLine(pilot) ?? derivedMetadataInferenceLine(pilot);
|
||
const inferenceLines = derivedInferenceLine
|
||
? [derivedInferenceLine]
|
||
: pilot.evidence.inferred_facts;
|
||
const derivedMetadataLine = derivedMetadataConfirmedLine(pilot);
|
||
const derivedValueLine = derivedBidirectionalValueFlowConfirmedLine(pilot) ?? derivedValueFlowConfirmedLine(pilot);
|
||
const monthlyConfirmedLines = derivedBidirectionalValueFlowMonthlyLines(pilot).length > 0
|
||
? derivedBidirectionalValueFlowMonthlyLines(pilot)
|
||
: derivedValueFlowMonthlyLines(pilot);
|
||
if (monthlyConfirmedLines.length > 0) {
|
||
pushReason(reasonCodes, "answer_contains_monthly_breakdown");
|
||
}
|
||
const confirmedLines = derivedValueLine
|
||
? [...pilot.evidence.confirmed_facts, derivedValueLine, ...monthlyConfirmedLines]
|
||
: derivedMetadataLine
|
||
? [...pilot.evidence.confirmed_facts, derivedMetadataLine]
|
||
: pilot.evidence.confirmed_facts;
|
||
return {
|
||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_ANSWER_DRAFT_SCHEMA_VERSION,
|
||
policy_owner: "assistantMcpDiscoveryAnswerAdapter",
|
||
answer_mode: mode,
|
||
headline: headlineFor(mode, pilot),
|
||
confirmed_lines: uniqueStrings(confirmedLines),
|
||
inference_lines: uniqueStrings(inferenceLines),
|
||
unknown_lines: uniqueStrings(pilot.evidence.unknown_facts),
|
||
limitation_lines: userFacingLimitations([...pilot.query_limitations, ...pilot.evidence.query_limitations]),
|
||
next_step_line: nextStepFor(mode, pilot),
|
||
internal_mechanics_allowed: false,
|
||
must_not_claim: buildMustNotClaim(pilot),
|
||
reason_codes: uniqueStrings(reasonCodes)
|
||
};
|
||
}
|