290 lines
16 KiB
JavaScript
290 lines
16 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 headlineFor(mode, pilot) {
|
||
const askedMonthlyBreakdown = pilot.derived_bidirectional_value_flow?.aggregation_axis === "month" ||
|
||
pilot.derived_value_flow?.aggregation_axis === "month";
|
||
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 (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 === "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 (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 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);
|
||
const inferenceLines = derivedInferenceLine
|
||
? [derivedInferenceLine]
|
||
: pilot.evidence.inferred_facts;
|
||
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]
|
||
: 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)
|
||
};
|
||
}
|