NODEDC_1C/llm_normalizer/backend/dist/services/capabilitiesRegistry.js

236 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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: "settlements",
group_title: "Долги и расчёты",
description: "Сальдо, хвосты, незакрытые авансы и аналитика по расчётам",
risk_level: "high",
maturity_status: "production_ready",
supported_operations: ["receivables_confirmed_as_of_date", "open_items_by_counterparty_or_contract"],
unsupported_operations: ["close_period"],
required_entities: ["period_or_date"],
optional_entities: ["organization", "account", "counterparty"],
typical_queries: ["Кто нам должен на дату?", "Хвосты покажи по счёту 60 за период"],
related_routes: [],
safe_alternatives: ["Уточнить период, счёт или организацию"],
one_c_hints: ["РегистрБухгалтерии.Хозрасчетный"]
},
{
group_code: "cash",
group_title: "Деньги",
description: "Остатки и движение по денежным счетам и кассе",
risk_level: "medium",
maturity_status: "production_ready",
supported_operations: ["account_balance_snapshot", "bank_operations_by_counterparty"],
unsupported_operations: ["post_bank_statement"],
required_entities: ["date_or_period"],
optional_entities: ["organization", "account", "counterparty"],
typical_queries: ["Какой остаток по счёту 51 на дату?", "Покажи движение денег за месяц"],
related_routes: [],
safe_alternatives: ["Уточнить счёт или период"],
one_c_hints: ["РегистрБухгалтерии.Хозрасчетный"]
},
{
group_code: "inventory",
group_title: "Склад и товары",
description: "Подтверждённые остатки, происхождение и документы по товарным позициям",
risk_level: "medium",
maturity_status: "production_ready",
supported_operations: [
"inventory_on_hand_as_of_date",
"inventory_purchase_provenance_for_item",
"inventory_purchase_documents_for_item"
],
unsupported_operations: ["write_off_inventory"],
required_entities: ["date_or_period"],
optional_entities: ["organization", "warehouse", "item"],
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.filter((group) => group.group_code !== "boundaries").slice(0, 6);
const groupLines = topGroups.map((group, index) => {
const examples = group.typical_queries
.slice(0, 2)
.map((query) => query.trim())
.filter((query) => query.length > 0)
.join("; ");
return `${index + 1}. ${group.group_title}: ${group.description}${examples ? `. Например: ${examples}` : "."}`;
});
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;
}