183 lines
8.3 KiB
JavaScript
183 lines
8.3 KiB
JavaScript
"use strict";
|
||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||
};
|
||
Object.defineProperty(exports, "__esModule", { value: true });
|
||
exports.loadCapabilitiesRegistry = loadCapabilitiesRegistry;
|
||
exports.buildCapabilityContractReplyFromRegistry = buildCapabilityContractReplyFromRegistry;
|
||
exports.resolveNearestCapabilityGroup = resolveNearestCapabilityGroup;
|
||
const fs_1 = __importDefault(require("fs"));
|
||
const config_1 = require("../config");
|
||
const FALLBACK_REGISTRY = {
|
||
schema_version: "capabilities_registry_fallback_v1",
|
||
updated_at: "2026-04-09T00:00:00.000Z",
|
||
assistant_mode: "read_only",
|
||
groups: [
|
||
{
|
||
group_code: "vat",
|
||
group_title: "НДС",
|
||
description: "Срезы и расчеты НДС на базе данных 1С.",
|
||
risk_level: "high",
|
||
maturity_status: "partial",
|
||
supported_operations: ["vat_period_snapshot", "vat_payable_forecast"],
|
||
unsupported_operations: ["submit_tax_declaration"],
|
||
required_entities: ["period", "organization"],
|
||
optional_entities: ["counterparty"],
|
||
typical_queries: ["Сколько НДС к уплате за период?"],
|
||
related_routes: [],
|
||
safe_alternatives: ["Показать движения по 68/19 за период"],
|
||
one_c_hints: ["РегистрБухгалтерии.Хозрасчетный"]
|
||
},
|
||
{
|
||
group_code: "counterparties",
|
||
group_title: "Контрагенты",
|
||
description: "Документы, операции, договоры и срезы по контрагентам.",
|
||
risk_level: "medium",
|
||
maturity_status: "production_ready",
|
||
supported_operations: ["list_documents_by_counterparty", "list_contracts_by_counterparty"],
|
||
unsupported_operations: ["edit_counterparty_card"],
|
||
required_entities: ["counterparty_scope_or_contract"],
|
||
optional_entities: ["period", "organization"],
|
||
typical_queries: ["Покажи документы по контрагенту"],
|
||
related_routes: [],
|
||
safe_alternatives: ["Уточнить ИНН/наименование контрагента"],
|
||
one_c_hints: ["Справочник.Контрагенты"]
|
||
},
|
||
{
|
||
group_code: "boundaries",
|
||
group_title: "Ограничения",
|
||
description: "Операции, которые ассистент не выполняет.",
|
||
risk_level: "high",
|
||
maturity_status: "production_ready",
|
||
supported_operations: ["explain_boundary", "suggest_safe_next_step"],
|
||
unsupported_operations: ["configure_1c", "admin_server_actions", "create_or_post_documents"],
|
||
required_entities: [],
|
||
optional_entities: [],
|
||
typical_queries: ["Можешь настроить 1С?"],
|
||
related_routes: [],
|
||
safe_alternatives: ["Сформировать план диагностики для 1С/ИТ-админа"],
|
||
one_c_hints: []
|
||
}
|
||
]
|
||
};
|
||
let cache = null;
|
||
function toRecord(value) {
|
||
if (!value || typeof value !== "object" || Array.isArray(value))
|
||
return null;
|
||
return value;
|
||
}
|
||
function toStringSafe(value) {
|
||
if (typeof value !== "string")
|
||
return null;
|
||
const trimmed = value.trim();
|
||
return trimmed.length > 0 ? trimmed : null;
|
||
}
|
||
function toArray(value) {
|
||
return Array.isArray(value) ? value : [];
|
||
}
|
||
function readRegistryFromFile() {
|
||
if (!fs_1.default.existsSync(config_1.ASSISTANT_CAPABILITIES_REGISTRY_FILE))
|
||
return null;
|
||
try {
|
||
const raw = fs_1.default.readFileSync(config_1.ASSISTANT_CAPABILITIES_REGISTRY_FILE, "utf-8");
|
||
const parsed = JSON.parse(raw);
|
||
const root = toRecord(parsed);
|
||
if (!root)
|
||
return null;
|
||
const groups = toArray(root.groups)
|
||
.map((item) => toRecord(item))
|
||
.filter((item) => item !== null)
|
||
.map((item) => ({
|
||
group_code: toStringSafe(item.group_code) ?? "unknown_group",
|
||
group_title: toStringSafe(item.group_title) ?? "Группа",
|
||
description: toStringSafe(item.description) ?? "",
|
||
risk_level: toStringSafe(item.risk_level) ?? "medium",
|
||
maturity_status: toStringSafe(item.maturity_status) ??
|
||
"partial",
|
||
supported_operations: toArray(item.supported_operations)
|
||
.map((v) => toStringSafe(v))
|
||
.filter((v) => v !== null),
|
||
unsupported_operations: toArray(item.unsupported_operations)
|
||
.map((v) => toStringSafe(v))
|
||
.filter((v) => v !== null),
|
||
required_entities: toArray(item.required_entities)
|
||
.map((v) => toStringSafe(v))
|
||
.filter((v) => v !== null),
|
||
optional_entities: toArray(item.optional_entities)
|
||
.map((v) => toStringSafe(v))
|
||
.filter((v) => v !== null),
|
||
typical_queries: toArray(item.typical_queries)
|
||
.map((v) => toStringSafe(v))
|
||
.filter((v) => v !== null),
|
||
related_routes: toArray(item.related_routes)
|
||
.map((v) => toStringSafe(v))
|
||
.filter((v) => v !== null),
|
||
safe_alternatives: toArray(item.safe_alternatives)
|
||
.map((v) => toStringSafe(v))
|
||
.filter((v) => v !== null),
|
||
one_c_hints: toArray(item.one_c_hints)
|
||
.map((v) => toStringSafe(v))
|
||
.filter((v) => v !== null)
|
||
}));
|
||
if (groups.length === 0)
|
||
return null;
|
||
return {
|
||
schema_version: toStringSafe(root.schema_version) ?? "capabilities_registry_v1",
|
||
updated_at: toStringSafe(root.updated_at) ?? new Date().toISOString(),
|
||
assistant_mode: toStringSafe(root.assistant_mode) ?? "read_only",
|
||
groups
|
||
};
|
||
}
|
||
catch {
|
||
return null;
|
||
}
|
||
}
|
||
function loadCapabilitiesRegistry() {
|
||
try {
|
||
const mtimeMs = fs_1.default.existsSync(config_1.ASSISTANT_CAPABILITIES_REGISTRY_FILE)
|
||
? fs_1.default.statSync(config_1.ASSISTANT_CAPABILITIES_REGISTRY_FILE).mtimeMs
|
||
: -1;
|
||
if (cache && cache.mtimeMs === mtimeMs) {
|
||
return cache.value;
|
||
}
|
||
const value = readRegistryFromFile() ?? FALLBACK_REGISTRY;
|
||
cache = { mtimeMs, value };
|
||
return value;
|
||
}
|
||
catch {
|
||
return cache?.value ?? FALLBACK_REGISTRY;
|
||
}
|
||
}
|
||
function buildCapabilityContractReplyFromRegistry() {
|
||
const registry = loadCapabilitiesRegistry();
|
||
const topGroups = registry.groups.slice(0, 6);
|
||
const groupLines = topGroups.map((group, index) => {
|
||
const ops = group.supported_operations.slice(0, 3).join(", ");
|
||
return `${index + 1}. ${group.group_title}: ${group.description}${ops ? ` (например: ${ops})` : ""}.`;
|
||
});
|
||
return [
|
||
"Я ассистент по анализу данных 1С в режиме чтения.",
|
||
"Что умею по группам:",
|
||
...groupLines,
|
||
"Если хотите, раскрою любую группу точечно и дам готовую формулировку запроса.",
|
||
"Что не делаю: не настраиваю 1С, не меняю конфигурацию, не создаю и не провожу документы, не выполняю админ-действия на сервере."
|
||
].join("\n");
|
||
}
|
||
function resolveNearestCapabilityGroup(input) {
|
||
const registry = loadCapabilitiesRegistry();
|
||
const haystack = `${String(input.domain ?? "")} ${String(input.queryClass ?? "")}`.toLowerCase();
|
||
if (!haystack.trim())
|
||
return null;
|
||
const scoring = registry.groups.map((group) => {
|
||
let score = 0;
|
||
const bucket = `${group.group_code} ${group.group_title} ${group.description} ${group.supported_operations.join(" ")}`.toLowerCase();
|
||
for (const token of haystack.split(/[\s._/-]+/g).filter(Boolean)) {
|
||
if (bucket.includes(token))
|
||
score += 1;
|
||
}
|
||
return { group, score };
|
||
});
|
||
scoring.sort((a, b) => b.score - a.score);
|
||
return scoring[0] && scoring[0].score > 0 ? scoring[0].group : null;
|
||
}
|