v1.7.0: refactor max_bot to flat structure, add VCF+UserModel+NLP history context, portal pages and proxy fixes
- Refactored max_bot from nested packages to flat module structure - Q2: Extended BotUser model (patronymic, email, org, address, vcf_raw, contact_hash, phone_verified, email_verified, last_interaction, total_conversations, total_tickets) - Q2: VCF parser (FN, N, TEL, EMAIL, ORG, ADR), upsert on re-contact, NLP history context (_get_user_history_context -> YandexGPT) - Q1: Broadcast preview modal with 10s confirmation timer - Q3: CSS var(--white)->var(--bg-card), var(--text)->var(--text-primary) - Q4: bot_settings showNotification(), editable max_bot_id - Q5: Webhook secret passthrough via X-Max-Bot-Api-Secret - Masking sensitive keys, dialog_cleared handler, migrate via _add_column_if_not_exists() - Rate limit (asyncio.sleep 0.5 per 10), dead code removed, conv.intent context in contact.py - Portal pages: bot_consent, bot_kb (edit), bot_settings, bot_test, bot_tickets, portal_settings - Tests: 21/21 passing, added test_yandex_gpt.py, test_email_sender.py - Deploy: deploy_full.sh, schema.sql, seed_knowledge_base.sql
This commit is contained in:
@@ -1,205 +0,0 @@
|
||||
-- Bot tables for AegisOne Engineering Max Bot "София"
|
||||
-- Run against the shared aegisone database
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
max_user_id BIGINT UNIQUE NOT NULL,
|
||||
first_name VARCHAR(255) DEFAULT '',
|
||||
last_name VARCHAR(255) DEFAULT '',
|
||||
username VARCHAR(255) DEFAULT '',
|
||||
phone VARCHAR(50) DEFAULT '',
|
||||
email VARCHAR(255) DEFAULT '',
|
||||
consent_given BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
consent_timestamp TIMESTAMP NULL,
|
||||
consent_method VARCHAR(50) DEFAULT '',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
last_active_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_categories (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
description VARCHAR(500) DEFAULT '',
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
sort_order INTEGER DEFAULT 0,
|
||||
icon_emoji VARCHAR(10) DEFAULT '📋',
|
||||
keywords JSONB DEFAULT '[]'::jsonb
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_conversations (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES bot_users(id) ON DELETE CASCADE,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'open' CHECK (status IN ('open','closed','handoff','1c_sent','awaiting_rating')),
|
||||
inquiry_type INTEGER REFERENCES bot_categories(id) ON DELETE SET NULL,
|
||||
inquiry_text TEXT DEFAULT '',
|
||||
priority VARCHAR(20) DEFAULT 'normal' CHECK (priority IN ('low','normal','high','urgent')),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
closed_at TIMESTAMP NULL,
|
||||
assigned_to VARCHAR(255) DEFAULT '',
|
||||
has_attachment BOOLEAN DEFAULT FALSE,
|
||||
attachment_type VARCHAR(50) DEFAULT '',
|
||||
attachment_path VARCHAR(500) DEFAULT ''
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_messages (
|
||||
id SERIAL PRIMARY KEY,
|
||||
conversation_id INTEGER REFERENCES bot_conversations(id) ON DELETE CASCADE,
|
||||
direction VARCHAR(3) NOT NULL CHECK (direction IN ('in','out')),
|
||||
text TEXT DEFAULT '',
|
||||
has_attachment BOOLEAN DEFAULT FALSE,
|
||||
attachment_type VARCHAR(50) DEFAULT '',
|
||||
attachment_path VARCHAR(500) DEFAULT '',
|
||||
max_message_id BIGINT NULL,
|
||||
timestamp TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_consent_logs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES bot_users(id) ON DELETE CASCADE,
|
||||
action VARCHAR(20) NOT NULL CHECK (action IN ('given','revoked')),
|
||||
timestamp TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
method VARCHAR(50) DEFAULT '',
|
||||
ip_address VARCHAR(45) DEFAULT ''
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_1c_sync_log (
|
||||
id SERIAL PRIMARY KEY,
|
||||
conversation_id INTEGER REFERENCES bot_conversations(id) ON DELETE CASCADE,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','sent','confirmed','error')),
|
||||
request_payload TEXT DEFAULT '',
|
||||
response_payload TEXT DEFAULT '',
|
||||
sent_at TIMESTAMP NULL,
|
||||
confirmed_at TIMESTAMP NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_knowledge_base (
|
||||
id SERIAL PRIMARY KEY,
|
||||
question VARCHAR(500) NOT NULL,
|
||||
answer TEXT NOT NULL,
|
||||
category VARCHAR(100) DEFAULT '',
|
||||
keywords JSONB DEFAULT '[]'::jsonb,
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
sort_order INTEGER DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_settings (
|
||||
id SERIAL PRIMARY KEY,
|
||||
key VARCHAR(100) UNIQUE NOT NULL,
|
||||
value TEXT DEFAULT '',
|
||||
description VARCHAR(500) DEFAULT ''
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_features (
|
||||
id SERIAL PRIMARY KEY,
|
||||
feature_key VARCHAR(100) UNIQUE NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
description VARCHAR(500) DEFAULT '',
|
||||
enabled BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
sort_order INTEGER DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_response_templates (
|
||||
id SERIAL PRIMARY KEY,
|
||||
template_key VARCHAR(100) UNIQUE NOT NULL,
|
||||
template_text TEXT NOT NULL,
|
||||
description VARCHAR(500) DEFAULT '',
|
||||
variables JSONB DEFAULT '[]'::jsonb
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_broadcasts (
|
||||
id SERIAL PRIMARY KEY,
|
||||
text TEXT NOT NULL,
|
||||
sent_at TIMESTAMP NULL,
|
||||
sent_by INTEGER NULL,
|
||||
recipient_count INTEGER DEFAULT 0,
|
||||
status VARCHAR(20) DEFAULT 'draft' CHECK (status IN ('draft','sent','failed'))
|
||||
);
|
||||
|
||||
-- Indexes
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_conv_user ON bot_conversations(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_conv_status ON bot_conversations(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_msg_conv ON bot_messages(conversation_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_consent_user ON bot_consent_logs(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_1c_conv ON bot_1c_sync_log(conversation_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_1c_status ON bot_1c_sync_log(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_kb_active ON bot_knowledge_base(active);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_cat_active ON bot_categories(active);
|
||||
|
||||
-- Initial settings
|
||||
INSERT INTO bot_settings (key, value, description) VALUES
|
||||
('bot_token', 'id2311381465_bot', 'Токен бота Max'),
|
||||
('assistant_name', 'София', 'Имя виртуального помощника'),
|
||||
('phone_1', '8 (861) 203-33-30', 'Телефон 1'),
|
||||
('phone_2', '8 (995) 203-33-30', 'Телефон 2'),
|
||||
('support_email', 'zakaz@aegisone.ru', 'Email поддержки'),
|
||||
('email_subject', 'Обращение через Виртуального помощника AegisOne Engineering', 'Тема письма при отказе от согласия'),
|
||||
('work_hours_start', '9', 'Начало рабочего времени (час)'),
|
||||
('work_hours_end', '18', 'Конец рабочего времени (час)'),
|
||||
('work_days', '1,2,3,4,5', 'Рабочие дни (1=Пн, 7=Вс)'),
|
||||
('timezone', 'Europe/Moscow', 'Часовой пояс'),
|
||||
('1c_webhook_url', '', 'URL webhook 1С:УНФ'),
|
||||
('consent_policy_url', 'https://aegisone.ru/politica.php', 'Ссылка на политику ПД'),
|
||||
('yandex_gpt_key', '', 'Ключ YandexGPT (пусто = ключевые слова)'),
|
||||
('yandex_folder_id', '', 'Folder ID YandexGPT')
|
||||
ON CONFLICT (key) DO NOTHING;
|
||||
|
||||
-- Initial features
|
||||
INSERT INTO bot_features (feature_key, name, description, enabled, sort_order) VALUES
|
||||
('risk_score', 'Risk Score калькулятор', 'Расчёт оценки рисков объекта', false, 1),
|
||||
('sla_status', 'Проверка SLA статуса', 'Проверка статуса SLA контракта по номеру объекта', false, 2),
|
||||
('photo_report', 'Фото-отчёт', 'Отправка фото проблемы с созданием инцидента', false, 3),
|
||||
('appointment', 'Запись на визит', 'Запись на визит инженера', false, 4),
|
||||
('quality_rate', 'Оценка качества', 'Оценка качества после закрытия обращения', false, 5),
|
||||
('commercial', 'Коммерческое предложение', 'Генерация КП по запросу', false, 6),
|
||||
('reminders', 'Напоминания о ТО', 'Автоматические напоминания о плановом ТО', false, 7),
|
||||
('my_objects', 'Мои объекты', 'Просмотр информации по объектам клиента', false, 8),
|
||||
('notifications', 'Уведомления о статусе', 'Автоматические уведомления при изменении статуса', false, 9),
|
||||
('broadcasts', 'Рассылки', 'Массовая рассылка сообщений клиентам', false, 10),
|
||||
('sla_ticket', 'SLA заявки', 'Подача заявки о неисправности клиентом с активным SLA', false, 11)
|
||||
ON CONFLICT (feature_key) DO NOTHING;
|
||||
|
||||
-- Initial categories
|
||||
INSERT INTO bot_categories (name, description, icon_emoji, keywords, sort_order) VALUES
|
||||
('Видеонаблюдение', 'Камеры, видеорегистраторы, хранилища', '📹', '["камер","видео","наблюден","регистратор","хранен","запись"]', 1),
|
||||
('СКУД', 'Контроль доступа, турникеты, замки', '🔐', '["скуд","доступ","турникет","замок","пропуск","карт"]', 2),
|
||||
('Пожарная сигнализация', 'Датчики, оповещение, пожаротушение', '🔥', '["пожар","сигнализаци","датчик","оповещен","пожаротушен","дым"]', 3),
|
||||
('IT-инфраструктура', 'Серверы, сеть, компьютеры', '💻', '["сервер","сет","компьютер","ит","инфраструктур","switch","роутер"]', 4),
|
||||
('Обслуживание', 'Плановое ТО, ремонт, замена', '🔧', '["обслуживан","ремонт","замен","то","планов","срок"]', 5),
|
||||
('Консультация', 'Вопросы, информация, расчёт стоимости', '💬', '["консультаци","вопрос","информац","стоимост","расчёт","цен"]', 6),
|
||||
('Другое', 'Прочие обращения', '📋', '[]', 7)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Initial response templates
|
||||
INSERT INTO bot_response_templates (template_key, template_text, description, variables) VALUES
|
||||
('greeting', 'Здравствуйте! Я {name}, виртуальный помощник AegisOne Engineering. Чем могу помочь?', 'Приветствие нового пользователя', '["name"]'),
|
||||
('greeting_returning', 'Здравствуйте, {first_name}! У вас открыто обращение #{conv_id}. Чем ещё могу помочь?', 'Приветствие возвращающегося пользователя', '["first_name","conv_id"]'),
|
||||
('consent_request', 'Для обработки обращения нам нужны контактные данные. Даёте согласие на обработку персональных данных?\n\n[Подробнее о политике]({policy_url})', 'Запрос согласия ФЗ-152', '["policy_url"]'),
|
||||
('consent_refused', 'Понимаем. Вы можете связаться с нами:\n\n📞 {phone_1}\n📞 {phone_2}\n✉️ {support_email}', 'Ответ при отказе от согласия', '["phone_1","phone_2","support_email"]'),
|
||||
('name_request', 'Как к вам обращаться?', 'Запрос имени', '[]'),
|
||||
('contact_request', 'Как с вами связаться?', 'Запрос контактных данных', '[]'),
|
||||
('inquiry_request', 'Опишите ваш вопрос. Вы можете прикрепить фото или документ (PDF, DOCX, XLSX, TXT).', 'Запрос обращения', '[]'),
|
||||
('out_of_hours', 'Спасибо за обращение! Сейчас мы не работаем.\n\nНаше рабочее время: Пн-Пт {work_hours_start}:00-{work_hours_end}:00 (МСК).\n\nНо вы можете оставить сообщение — мы ответим как можно быстрее.', 'Сообщение вне рабочего времени', '["work_hours_start","work_hours_end"]'),
|
||||
('confirmation', 'Спасибо, {first_name}! Обращение #{conv_id} принято.\n\nОтветим как можно быстрее.', 'Подтверждение (рабочее время)', '["first_name","conv_id"]'),
|
||||
('confirmation_ooh', 'Спасибо, {first_name}! Обращение #{conv_id} принято.\n\nСейчас нерабочее время, но мы ответим как можно быстрее.', 'Подтверждение (вне рабочего времени)', '["first_name","conv_id"]'),
|
||||
('handoff', 'Передаю обращение оператору. Ожидайте.', 'Передача оператору', '[]'),
|
||||
('quality_request', 'Обращение #{conv_id} выполнено. Оцените качество нашей работы:\n\n⭐ 1 — Плохо\n⭐⭐ 2\n⭐⭐⭐ 3\n⭐⭐⭐⭐ 4\n⭐⭐⭐⭐⭐ 5 — Отлично', 'Запрос оценки качества', '["conv_id"]'),
|
||||
('sla_found', 'Объект: {object_name}\nSLA: {service_level}\nСтатус: {status}\nЦена/мес: {price} ₽', 'Результат проверки SLA', '["object_name","service_level","status","price"]'),
|
||||
('sla_not_found', 'Объект не найден или SLA контракт не оформлен.', 'SLA не найден', '[]'),
|
||||
('risk_result', 'Risk Score вашего объекта: {score}/100\nУровень: {label}\n\nРекомендуем: {recommendation}', 'Результат Risk Score', '["score","label","recommendation"]'),
|
||||
('appointment_confirm', 'Запись на визит подтверждена.\n\nДата: {date}\nИнженер: {engineer}', 'Подтверждение записи на визит', '["date","engineer"]'),
|
||||
('commercial_offer', 'Коммерческое предложение по вашему запросу:\n\n{offer_text}', 'Коммерческое предложение', '["offer_text"]'),
|
||||
('reminder', 'Напоминаем: плановое ТО объекта {object_name} запланировано на {date}.', 'Напоминание о ТО', '["object_name","date"]'),
|
||||
('status_update', 'Ваше обращение #{conv_id} — {status}.\nИнженер: {engineer}.', 'Уведомление о статусе', '["conv_id","status","engineer"]'),
|
||||
('main_menu', 'Выберите действие:', 'Текст главного меню', '[]'),
|
||||
('emotion_escalate', 'Я понимаю ваше беспокойство. Передаю обращение старшему специалисту. Ожидайте, пожалуйста.', 'Реакция на негативные эмоции', '[]'),
|
||||
('auto_categorize_confirm', 'Я определил вашу тему как: {category}. Это верно?', 'Подтверждение авто-категоризации', '["category"]')
|
||||
ON CONFLICT (template_key) DO NOTHING;
|
||||
|
||||
-- Sample knowledge base entries
|
||||
INSERT INTO bot_knowledge_base (question, answer, category, keywords, active, sort_order) VALUES
|
||||
('Какие услуги вы оказываете?', 'Мы оказываем услуги по проектированию, монтажу и обслуживанию систем видеонаблюдения, СКУД, пожарной сигнализации и IT-инфраструктуры.', 'Общее', '["услуг","оказыва","монтаж","проект","обслуживан"]', true, 1),
|
||||
('Как оставить заявку?', 'Нажмите кнопку «Оставить заявку» в главном меню и опишите ваш вопрос.', 'Общее', '["заявк","оставит","как"]', true, 2),
|
||||
('Какие гарантии?', 'Мы предоставляем гарантию на все выполненные работы и установленное оборудование. Подробности уточняйте у менеджера.', 'Общее', '["гарант","условия"]', true, 3),
|
||||
('Режим работы?', 'Наш офис работает Пн-Пт с 9:00 до 18:00 (МСК).', 'Общее', '["режим","работ","время","график"]', true, 4),
|
||||
('Как связаться?', 'Вы можете написать нам здесь, позвонить по телефону 8 (861) 203-33-30 или написать на zakaz@aegisone.ru', 'Общее', '["связат","контакт","телефон","почт"]', true, 5)
|
||||
ON CONFLICT DO NOTHING;
|
||||
@@ -1,2 +0,0 @@
|
||||
-- Migration v1.5.1: Add recipient_ids column to bot_broadcasts
|
||||
ALTER TABLE bot_broadcasts ADD COLUMN IF NOT EXISTS recipient_ids JSONB DEFAULT '[]'::jsonb;
|
||||
@@ -1,26 +0,0 @@
|
||||
-- Migration v1.5.2: Role-based menu permissions
|
||||
CREATE TABLE IF NOT EXISTS role_menu_permissions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
role VARCHAR(20) NOT NULL,
|
||||
menu_key VARCHAR(50) NOT NULL,
|
||||
visible BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
UNIQUE(role, menu_key)
|
||||
);
|
||||
|
||||
-- Default: enable all menus for both roles
|
||||
INSERT INTO role_menu_permissions (role, menu_key, visible) VALUES
|
||||
('engineer', 'tasks', true),
|
||||
('engineer', 'reports', true),
|
||||
('engineer', 'incidents', true),
|
||||
('engineer', 'questionnaire', true),
|
||||
('engineer', 'passports', true),
|
||||
('engineer', 'customers', true),
|
||||
('engineer', 'objects', true),
|
||||
('engineer', 'sla', true),
|
||||
('engineer', 'tech_access', true),
|
||||
('engineer', 'docs', true),
|
||||
('technician', 'tasks', true),
|
||||
('technician', 'reports', true),
|
||||
('technician', 'incidents', true),
|
||||
('technician', 'checklist', true)
|
||||
ON CONFLICT (role, menu_key) DO NOTHING;
|
||||
@@ -1,121 +0,0 @@
|
||||
-- Migration v1.5.5: New tables + seed data
|
||||
-- Run: psql -U aegisone -d aegisone -f v1.5.5_tables_and_seed.sql
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- ===================== New tables =====================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_notifications (
|
||||
id SERIAL PRIMARY KEY,
|
||||
type VARCHAR(50) NOT NULL DEFAULT 'info',
|
||||
recipient_type VARCHAR(20) NOT NULL DEFAULT 'operator',
|
||||
recipient_max_user_id BIGINT NULL,
|
||||
title VARCHAR(255) DEFAULT '',
|
||||
body TEXT DEFAULT '',
|
||||
related_type VARCHAR(50) DEFAULT '',
|
||||
related_id INTEGER NULL,
|
||||
is_read BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
CHECK (recipient_type IN ('operator','level2','client'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_notification_subscriptions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES bot_users(id) ON DELETE CASCADE,
|
||||
notify_on_status_change BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
notify_on_reply BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
notify_on_broadcast BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
UNIQUE(user_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_risk_questions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
question_key VARCHAR(100) UNIQUE NOT NULL,
|
||||
question VARCHAR(500) NOT NULL,
|
||||
weight INTEGER NOT NULL DEFAULT 10,
|
||||
sort_order INTEGER DEFAULT 0,
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_tickets (
|
||||
id SERIAL PRIMARY KEY,
|
||||
ticket_number VARCHAR(20) UNIQUE NOT NULL,
|
||||
user_id INTEGER REFERENCES bot_users(id) ON DELETE SET NULL,
|
||||
object_id INTEGER NULL,
|
||||
customer_id INTEGER NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
description TEXT DEFAULT '',
|
||||
priority VARCHAR(20) DEFAULT 'normal',
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'open',
|
||||
created_by VARCHAR(20) DEFAULT 'bot',
|
||||
sla_contract_id INTEGER NULL,
|
||||
has_attachment BOOLEAN DEFAULT FALSE,
|
||||
attachment_type VARCHAR(50) DEFAULT '',
|
||||
attachment_path VARCHAR(500) DEFAULT '',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
CHECK (priority IN ('low','normal','high','urgent')),
|
||||
CHECK (status IN ('open','in_progress','resolved','closed'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_ticket_messages (
|
||||
id SERIAL PRIMARY KEY,
|
||||
ticket_id INTEGER REFERENCES bot_tickets(id) ON DELETE CASCADE NOT NULL,
|
||||
direction VARCHAR(10) NOT NULL,
|
||||
text TEXT DEFAULT '',
|
||||
sender VARCHAR(50) DEFAULT '',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_ticket_statuses (
|
||||
id SERIAL PRIMARY KEY,
|
||||
ticket_id INTEGER REFERENCES bot_tickets(id) ON DELETE CASCADE NOT NULL,
|
||||
old_status VARCHAR(20) DEFAULT '',
|
||||
new_status VARCHAR(20) NOT NULL,
|
||||
changed_by VARCHAR(50) DEFAULT '',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- ===================== Add recipient_ids to broadcasts if missing =====================
|
||||
ALTER TABLE bot_broadcasts ADD COLUMN IF NOT EXISTS recipient_ids JSONB DEFAULT '[]'::jsonb;
|
||||
|
||||
-- ===================== Ensure new columns exist on existing tables =====================
|
||||
ALTER TABLE bot_notifications ADD COLUMN IF NOT EXISTS recipient_max_user_id BIGINT NULL;
|
||||
ALTER TABLE bot_notifications ADD COLUMN IF NOT EXISTS related_type VARCHAR(50) DEFAULT '';
|
||||
ALTER TABLE bot_notifications ADD COLUMN IF NOT EXISTS related_id INTEGER NULL;
|
||||
|
||||
ALTER TABLE bot_notification_subscriptions ADD COLUMN IF NOT EXISTS notify_on_status_change BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
ALTER TABLE bot_notification_subscriptions ADD COLUMN IF NOT EXISTS notify_on_reply BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
ALTER TABLE bot_notification_subscriptions ADD COLUMN IF NOT EXISTS notify_on_broadcast BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
|
||||
-- Drop old subscribe_types column if it exists (replaced by boolean columns)
|
||||
ALTER TABLE bot_notification_subscriptions DROP COLUMN IF EXISTS subscribe_types;
|
||||
|
||||
-- ===================== Seed: enable all features =====================
|
||||
UPDATE bot_features SET enabled = true WHERE enabled = false;
|
||||
|
||||
-- ===================== Seed: risk_questions =====================
|
||||
INSERT INTO bot_risk_questions (question_key, question, weight, sort_order, is_active) VALUES
|
||||
('no_archive', '📋 Отсутствует архив документации?', 25, 1, true),
|
||||
('no_power_backup', '🔋 Нет резервного питания?', 20, 2, true),
|
||||
('no_regulations', '📜 Нет регламентов обслуживания?', 15, 3, true),
|
||||
('system_failures', '⚠️ Были отказы системы за последний год?', 20, 4, true),
|
||||
('no_documentation', '🗂️ Нет схем и паспортов объекта?', 10, 5, true),
|
||||
('outdated_equipment', '🕰️ Оборудование старше 5 лет?', 5, 6, true),
|
||||
('no_monitoring', '📡 Нет удалённого мониторинга?', 5, 7, true)
|
||||
ON CONFLICT (question_key) DO NOTHING;
|
||||
|
||||
-- ===================== Seed: default role permissions for new menu items =====================
|
||||
INSERT INTO role_menu_permissions (role, menu_key, visible)
|
||||
SELECT * FROM (VALUES
|
||||
('engineer', 'bot_tickets', true),
|
||||
('engineer', 'bot_notifications', true),
|
||||
('engineer', 'bot_risk_questions', true)
|
||||
) AS v(role, menu_key, visible)
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM role_menu_permissions r
|
||||
WHERE r.role = v.role AND r.menu_key = v.menu_key
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
@@ -1,38 +0,0 @@
|
||||
-- Migration v1.6.0: Fixes for broadcast recipient_ids + feature keys + categories + engineer permissions
|
||||
|
||||
-- 1. Add recipient_ids column to bot_broadcasts if missing
|
||||
ALTER TABLE bot_broadcasts ADD COLUMN IF NOT EXISTS recipient_ids JSONB DEFAULT '[]'::jsonb;
|
||||
|
||||
-- 2. Fix feature_key auto_notifications → notifications
|
||||
UPDATE bot_features SET feature_key = 'notifications' WHERE feature_key = 'auto_notifications';
|
||||
|
||||
-- 3. Fix feature_key commercial_offer → commercial (match code)
|
||||
UPDATE bot_features SET feature_key = 'commercial' WHERE feature_key = 'commercial_offer';
|
||||
|
||||
-- 4. Replace categories with new set
|
||||
DELETE FROM bot_categories;
|
||||
INSERT INTO bot_categories (name, description, icon_emoji, keywords, sort_order) VALUES
|
||||
('Аудит безопасности', 'Независимая оценка систем видеонаблюдения, СКУД, ОПС', '🔍', '["аудит","безопасность","оценка","видеонаблюдение","скуд","опс"]', 1),
|
||||
('Сервисное обслуживание / SLA', 'Абонентское обслуживание по SLA-контракту', '🛡️', '["сервис","sla","абонентский","обслуживание","контракт"]', 2),
|
||||
('Реагирование на инциденты', 'Срочное реагирование при нештатных ситуациях', '🚨', '["инцидент","реагирование","срочный","нештатный","авария"]', 3),
|
||||
('Технический надзор', 'Контроль качества при монтаже и пусконаладке', '📐', '["надзор","технический","контроль","качество","монтаж","пусконаладка"]', 4),
|
||||
('Проектная документация', 'Разработка и согласование проектной документации', '📄', '["проект","документация","разработка","согласование"]', 5),
|
||||
('Риск-инжиниринг', 'Оценка рисков, BCP, DRP, стратегия безопасности', '⚡', '["риск","инжиниринг","bcp","drp","стратегия","безопасность"]', 6),
|
||||
('Общий вопрос', 'Консультация или вопрос вне указанных категорий', '💬', '["консультация","вопрос","общий","другое"]', 7)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- 5. Add bot_* menu permissions for engineer role
|
||||
INSERT INTO role_menu_permissions (role, menu_key, visible) VALUES
|
||||
('engineer', 'bot_analytics', true),
|
||||
('engineer', 'bot_users', true),
|
||||
('engineer', 'bot_conversations', true),
|
||||
('engineer', 'bot_tickets', true),
|
||||
('engineer', 'bot_handoffs', true),
|
||||
('engineer', 'bot_notifications', true),
|
||||
('engineer', 'bot_risk_questions', true),
|
||||
('engineer', 'bot_broadcasts', true),
|
||||
('engineer', 'bot_storage', true)
|
||||
ON CONFLICT (role, menu_key) DO NOTHING;
|
||||
|
||||
-- 6. Drop stale view if exists
|
||||
DROP VIEW IF EXISTS bot_dashboard;
|
||||
@@ -0,0 +1,170 @@
|
||||
-- ============================================
|
||||
-- AegisOne MAX Bot — Database Schema
|
||||
-- ============================================
|
||||
-- Run this to set up all bot tables.
|
||||
-- Assumes database 'aegisone' already exists.
|
||||
|
||||
-- ============================================
|
||||
-- CLEANUP: Drop ALL old bot tables before recreating
|
||||
-- ============================================
|
||||
DROP TABLE IF EXISTS bot_1c_sync_log CASCADE;
|
||||
DROP TABLE IF EXISTS bot_ticket_messages CASCADE;
|
||||
DROP TABLE IF EXISTS bot_ticket_statuses CASCADE;
|
||||
DROP TABLE IF EXISTS bot_tickets CASCADE;
|
||||
DROP TABLE IF EXISTS bot_messages CASCADE;
|
||||
DROP TABLE IF EXISTS bot_conversations CASCADE;
|
||||
DROP TABLE IF EXISTS bot_knowledge_base CASCADE;
|
||||
DROP TABLE IF EXISTS bot_categories CASCADE;
|
||||
DROP TABLE IF EXISTS bot_users CASCADE;
|
||||
DROP TABLE IF EXISTS bot_consent_logs CASCADE;
|
||||
DROP TABLE IF EXISTS bot_features CASCADE;
|
||||
DROP TABLE IF EXISTS bot_settings CASCADE;
|
||||
DROP TABLE IF EXISTS bot_notification_subscriptions CASCADE;
|
||||
DROP TABLE IF EXISTS bot_notifications CASCADE;
|
||||
DROP TABLE IF EXISTS bot_risk_questions CASCADE;
|
||||
DROP TABLE IF EXISTS bot_broadcasts CASCADE;
|
||||
DROP TABLE IF EXISTS bot_response_templates CASCADE;
|
||||
DROP TABLE IF EXISTS file_sync_queue CASCADE;
|
||||
|
||||
-- 1. BOT SETTINGS
|
||||
CREATE TABLE IF NOT EXISTS bot_settings (
|
||||
id SERIAL PRIMARY KEY,
|
||||
key VARCHAR(255) UNIQUE NOT NULL,
|
||||
value TEXT,
|
||||
created_at TIMESTAMP DEFAULT now(),
|
||||
updated_at TIMESTAMP DEFAULT now()
|
||||
);
|
||||
|
||||
-- 2. BOT FEATURES
|
||||
CREATE TABLE IF NOT EXISTS bot_features (
|
||||
id SERIAL PRIMARY KEY,
|
||||
key VARCHAR(255) UNIQUE NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
is_enabled BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT now()
|
||||
);
|
||||
|
||||
-- 3. BOT CATEGORIES (for knowledge base)
|
||||
CREATE TABLE IF NOT EXISTS bot_categories (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
sort_order INTEGER DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT now()
|
||||
);
|
||||
|
||||
-- 4. BOT KNOWLEDGE BASE (Q&A pairs)
|
||||
CREATE TABLE IF NOT EXISTS bot_knowledge_base (
|
||||
id SERIAL PRIMARY KEY,
|
||||
category_id INTEGER REFERENCES bot_categories(id) ON DELETE SET NULL,
|
||||
question TEXT NOT NULL,
|
||||
answer TEXT,
|
||||
keywords TEXT[],
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
source_url VARCHAR(500),
|
||||
sort_order INTEGER DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT now(),
|
||||
updated_at TIMESTAMP DEFAULT now()
|
||||
);
|
||||
|
||||
-- 5. BOT USERS (MAX messenger users)
|
||||
CREATE TABLE IF NOT EXISTS bot_users (
|
||||
id BIGINT PRIMARY KEY,
|
||||
first_name VARCHAR(255),
|
||||
last_name VARCHAR(255),
|
||||
patronymic VARCHAR(255),
|
||||
username VARCHAR(255),
|
||||
phone VARCHAR(50),
|
||||
email VARCHAR(255),
|
||||
organization VARCHAR(255),
|
||||
address TEXT,
|
||||
vcf_raw TEXT,
|
||||
contact_hash VARCHAR(255),
|
||||
phone_verified BOOLEAN DEFAULT FALSE,
|
||||
email_verified BOOLEAN DEFAULT FALSE,
|
||||
consent_given BOOLEAN DEFAULT FALSE,
|
||||
consent_date TIMESTAMP,
|
||||
last_interaction TIMESTAMP,
|
||||
total_conversations INTEGER DEFAULT 0,
|
||||
total_tickets INTEGER DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT now(),
|
||||
updated_at TIMESTAMP DEFAULT now()
|
||||
);
|
||||
|
||||
-- 6. BOT CONVERSATIONS
|
||||
CREATE TABLE IF NOT EXISTS bot_conversations (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES bot_users(id) ON DELETE CASCADE,
|
||||
state VARCHAR(50) DEFAULT 'greeting',
|
||||
intent VARCHAR(50),
|
||||
inquiry_text TEXT,
|
||||
created_at TIMESTAMP DEFAULT now(),
|
||||
updated_at TIMESTAMP DEFAULT now()
|
||||
);
|
||||
|
||||
-- 7. BOT MESSAGES
|
||||
CREATE TABLE IF NOT EXISTS bot_messages (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
conversation_id BIGINT NOT NULL REFERENCES bot_conversations(id) ON DELETE CASCADE,
|
||||
direction VARCHAR(10) NOT NULL,
|
||||
text TEXT,
|
||||
attachment_json TEXT,
|
||||
created_at TIMESTAMP DEFAULT now()
|
||||
);
|
||||
|
||||
-- 8. BOT TICKETS (support tickets)
|
||||
CREATE TABLE IF NOT EXISTS bot_tickets (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES bot_users(id) ON DELETE CASCADE,
|
||||
title VARCHAR(500),
|
||||
description TEXT,
|
||||
priority VARCHAR(20) DEFAULT 'normal',
|
||||
status VARCHAR(20) DEFAULT 'Новая',
|
||||
created_by VARCHAR(100) DEFAULT 'bot',
|
||||
assigned_to BIGINT,
|
||||
created_at TIMESTAMP DEFAULT now(),
|
||||
updated_at TIMESTAMP DEFAULT now()
|
||||
);
|
||||
|
||||
-- 9. BOT TICKET MESSAGES
|
||||
CREATE TABLE IF NOT EXISTS bot_ticket_messages (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
ticket_id BIGINT NOT NULL REFERENCES bot_tickets(id) ON DELETE CASCADE,
|
||||
direction VARCHAR(10) NOT NULL,
|
||||
text TEXT,
|
||||
created_at TIMESTAMP DEFAULT now()
|
||||
);
|
||||
|
||||
-- 10. BOT TICKET STATUSES (change log)
|
||||
CREATE TABLE IF NOT EXISTS bot_ticket_statuses (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
ticket_id BIGINT NOT NULL REFERENCES bot_tickets(id) ON DELETE CASCADE,
|
||||
old_status VARCHAR(20),
|
||||
new_status VARCHAR(20) NOT NULL,
|
||||
changed_by VARCHAR(100),
|
||||
created_at TIMESTAMP DEFAULT now()
|
||||
);
|
||||
|
||||
-- ============================================
|
||||
-- CLEANUP: Drop unused tables from old bot
|
||||
-- ============================================
|
||||
DROP TABLE IF EXISTS bot_notification_subscriptions CASCADE;
|
||||
DROP TABLE IF EXISTS bot_notifications CASCADE;
|
||||
DROP TABLE IF EXISTS bot_risk_questions CASCADE;
|
||||
DROP TABLE IF EXISTS bot_broadcasts CASCADE;
|
||||
DROP TABLE IF EXISTS file_sync_queue CASCADE;
|
||||
DROP TABLE IF EXISTS bot_response_templates CASCADE;
|
||||
|
||||
-- ============================================
|
||||
-- INDEXES
|
||||
-- ============================================
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_conversations_user_id ON bot_conversations(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_conversations_state ON bot_conversations(state);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_messages_conversation_id ON bot_messages(conversation_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_tickets_user_id ON bot_tickets(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_tickets_status ON bot_tickets(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_ticket_messages_ticket_id ON bot_ticket_messages(ticket_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_knowledge_base_category ON bot_knowledge_base(category_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_knowledge_base_active ON bot_knowledge_base(is_active);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_knowledge_base_keywords ON bot_knowledge_base USING gin(keywords);
|
||||
@@ -0,0 +1,194 @@
|
||||
-- ============================================
|
||||
-- Categories
|
||||
-- ============================================
|
||||
INSERT INTO bot_categories (id, name, description, sort_order) VALUES
|
||||
(1, 'О компании', 'Общая информация о компании AegisOne Engineering', 1),
|
||||
(2, 'Аудит безопасности', 'Услуги по независимому аудиту систем безопасности', 2),
|
||||
(3, 'Сервисное обслуживание (SLA)', 'Регулярное обслуживание и SLA-контракты', 3),
|
||||
(4, 'Реагирование на инциденты', 'Мониторинг и реагирование на инциденты безопасности', 4),
|
||||
(5, 'Технический надзор', 'Контроль качества монтажа и пусконаладки', 5),
|
||||
(6, 'Документация и нормативы', 'Проектная документация и соответствие нормативам', 6),
|
||||
(7, 'Риск-инжиниринг', 'Оценка и управление рисками безопасности', 7),
|
||||
(8, 'Отрасли и кейсы', 'Отрасли, в которых работаем, и примеры из практики', 8)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- ============================================
|
||||
-- 40 Q&A pairs
|
||||
-- ============================================
|
||||
TRUNCATE bot_knowledge_base RESTART IDENTITY CASCADE;
|
||||
|
||||
-- === CATEGORY 1: О КОМПАНИИ (6 with answers) ===
|
||||
INSERT INTO bot_knowledge_base (category_id, question, answer, keywords, is_active, source_url, sort_order) VALUES
|
||||
(1, 'Чем занимается AegisOne Engineering?',
|
||||
'AegisOne Engineering предоставляет инженерные решения для безопасности бизнеса: аудит, сервисное обслуживание и технический надзор систем безопасности. Мы снижаем риски, обеспечиваем стабильность и подтверждаем соответствие нормативам.',
|
||||
ARRAY['компания', 'услуги', 'безопасность', 'инжиниринг', 'аудит', 'сервис', 'надзор'], TRUE, 'https://aegisone.ru/', 1),
|
||||
|
||||
(1, 'Где находится AegisOne Engineering?',
|
||||
'Юридический адрес: 350072, Россия, Краснодарский край, г. Краснодар, ул. Московская, д. 81/1, пом. 25. Работаем по всему Краснодарскому краю, ЮФО и всей России с выездом специалиста на объект.',
|
||||
ARRAY['адрес', 'краснодар', 'местоположение', 'офис', 'где найти'], TRUE, 'https://aegisone.ru/', 2),
|
||||
|
||||
(1, 'Сколько лет работает компания?',
|
||||
'Компания работает более 20 лет на рынке Краснодара, Краснодарского края и Южного федерального округа.',
|
||||
ARRAY['история', 'стаж', 'опыт', 'лет', 'компания'], TRUE, 'https://aegisone.ru/', 3),
|
||||
|
||||
(1, 'Какая миссия компании?',
|
||||
'Наша миссия — давать руководителям объективную картину состояния их систем безопасности, выявляя скрытые угрозы и экономя ресурсы. Мы не продаём оборудование — мы продаём спокойствие и стабильность.',
|
||||
ARRAY['миссия', 'цель', 'принципы', 'философия'], TRUE, 'https://aegisone.ru/', 4),
|
||||
|
||||
(1, 'Продаёт ли AegisOne Engineering оборудование?',
|
||||
'Нет, мы не продаём оборудование. Мы работаем с системами безопасности «под ключ», но в рамках независимых услуг выступаем исключительно как эксперты. Наша задача — объективно оценить ситуацию и обеспечить работоспособность.',
|
||||
ARRAY['оборудование', 'продажа', 'монтаж', 'не продаём'], TRUE, 'https://aegisone.ru/', 5),
|
||||
|
||||
(1, 'Какие гарантии даёт компания?',
|
||||
'Для SLA-контрактов гарантируем время реакции, периодичность ТО и KPI, прописанные в договоре. Для аудита — полноту и объективность отчёта. Все специалисты имеют профильное образование и опыт от 10 лет.',
|
||||
ARRAY['гарантии', 'качество', 'kpi', 'сроки', 'специалисты'], TRUE, 'https://aegisone.ru/', 6);
|
||||
|
||||
-- === CATEGORY 2: АУДИТ БЕЗОПАСНОСТИ (4 with answers, 2 inactive) ===
|
||||
INSERT INTO bot_knowledge_base (category_id, question, answer, keywords, is_active, source_url, sort_order) VALUES
|
||||
(2, 'Что такое независимый аудит безопасности?',
|
||||
'Это объективная оценка состояния систем видеонаблюдения, СКУД и ОПС с выявлением уязвимостей и скрытых рисков. Результат — детальный отчёт с фотографиями, рекомендациями и дорожной картой.',
|
||||
ARRAY['аудит', 'независимый', 'оценка', 'проверка', 'безопасность'], TRUE, 'https://aegisone.ru/audit/', 7),
|
||||
|
||||
(2, 'Что входит в аудит систем безопасности?',
|
||||
'Входит проверка систем видеонаблюдения (состояние камер, покрытие, качество записи), СКУД (уязвимости, корректность конфигурации) и ОПС (эффективность обнаружения угроз, своевременность реагирования).',
|
||||
ARRAY['аудит', 'видеонаблюдение', 'скуда', 'опс', 'проверка'], TRUE, 'https://aegisone.ru/audit/', 8),
|
||||
|
||||
(2, 'Сколько стоит аудит?',
|
||||
'Стоимость зависит от объекта: площади, количества систем, удалённости. Называем цену после предварительного обсуждения — это бесплатно и ни к чему не обязывает.',
|
||||
ARRAY['аудит', 'цена', 'стоимость', 'сколько стоит'], TRUE, 'https://aegisone.ru/audit/', 9),
|
||||
|
||||
(2, 'Как проходит аудит?',
|
||||
'В 5 этапов: 1) первичное обсуждение, 2) анализ документов, 3) выезд на объект (визуальный и инструментальный осмотр), 4) подготовка отчёта, 5) презентация результатов руководству.',
|
||||
ARRAY['аудит', 'процесс', 'этапы', 'как проходит'], TRUE, 'https://aegisone.ru/audit/', 10),
|
||||
|
||||
(2, 'Чем аудит отличается от внутренней проверки службы безопасности?',
|
||||
NULL,
|
||||
ARRAY['аудит', 'отличие', 'внутренняя проверка', 'собственная служба'], FALSE, 'https://aegisone.ru/audit/', 11),
|
||||
|
||||
(2, 'Что содержится в отчёте по аудиту?',
|
||||
NULL,
|
||||
ARRAY['отчёт', 'результат', 'аудит', 'содержание'], FALSE, 'https://aegisone.ru/audit/', 12);
|
||||
|
||||
-- === CATEGORY 3: SLA (3 with answers, 2 inactive) ===
|
||||
INSERT INTO bot_knowledge_base (category_id, question, answer, keywords, is_active, source_url, sort_order) VALUES
|
||||
(3, 'Что входит в сервисное обслуживание (SLA)?',
|
||||
'Регулярное техническое обслуживание, 24/7 мониторинг систем, гарантированное время реакции, отчётность по KPI. Включает профилактику отказов и плановые проверки.',
|
||||
ARRAY['sla', 'сервис', 'обслуживание', 'то', 'техническое'], TRUE, 'https://aegisone.ru/sla/', 13),
|
||||
|
||||
(3, 'Какие тарифы SLA есть?',
|
||||
'Базовый (от 15 000 ₽/мес): ежемесячное ТО, 48ч реакция, удалённый мониторинг. Оптимальный (от 30 000 ₽/мес): ТО 2 раза/мес, 24ч реакция, 24/7 мониторинг. Максимальный (от 60 000 ₽/мес): еженедельное ТО, 12ч реакция, выделенный инженер.',
|
||||
ARRAY['sla', 'тарифы', 'цены', 'стоимость', 'базовый', 'оптимальный', 'максимальный'], TRUE, 'https://aegisone.ru/sla/', 14),
|
||||
|
||||
(3, 'Нужно ли проводить аудит перед заключением SLA?',
|
||||
'Рекомендуется, чтобы определить фактическое состояние систем и оптимальный тариф. Точное предложение формируется после аудита.',
|
||||
ARRAY['аудит', 'sla', 'перед', 'нужен ли'], TRUE, 'https://aegisone.ru/sla/', 15),
|
||||
|
||||
(3, 'Как контролируется качество обслуживания по SLA?',
|
||||
NULL,
|
||||
ARRAY['контроль', 'качество', 'sla', 'отчётность', 'kpi'], FALSE, 'https://aegisone.ru/sla/', 16),
|
||||
|
||||
(3, 'Что происходит при аварийной ситуации по SLA?',
|
||||
NULL,
|
||||
ARRAY['авария', 'sla', 'экстренная', 'реагирование', 'поломка'], FALSE, 'https://aegisone.ru/sla/', 17);
|
||||
|
||||
-- === CATEGORY 4: ИНЦИДЕНТЫ (3 with answers, 1 inactive) ===
|
||||
INSERT INTO bot_knowledge_base (category_id, question, answer, keywords, is_active, source_url, sort_order) VALUES
|
||||
(4, 'Что такое реагирование на инциденты?',
|
||||
'Это 24/7 мониторинг систем безопасности и оперативное реагирование на инциденты. Включает расследование причин, устранение последствий и профилактику повторений.',
|
||||
ARRAY['инцидент', 'реагирование', 'мониторинг', '24/7'], TRUE, 'https://aegisone.ru/incident/', 18),
|
||||
|
||||
(4, 'Как быстро вы реагируете на инцидент?',
|
||||
'Время реакции — от 4 до 24 часов в зависимости от тарифа SLA. Возможен экстренный выезд.',
|
||||
ARRAY['скорость', 'время', 'реакция', 'инцидент', 'быстро'], TRUE, 'https://aegisone.ru/incident/', 19),
|
||||
|
||||
(4, 'Какие системы вы мониторите?',
|
||||
'Видеонаблюдение, СКУД, охранно-пожарную сигнализацию (ОПС) и системы периметральной защиты.',
|
||||
ARRAY['мониторинг', 'системы', 'видеонаблюдение', 'скуда', 'опс'], TRUE, 'https://aegisone.ru/incident/', 20),
|
||||
|
||||
(4, 'Нужен ли договор для разового вызова на инцидент?',
|
||||
NULL,
|
||||
ARRAY['договор', 'разовый', 'инцидент', 'без договора'], FALSE, 'https://aegisone.ru/incident/', 21);
|
||||
|
||||
-- === CATEGORY 5: ТЕХНИЧЕСКИЙ НАДЗОР (3 with answers, 1 inactive) ===
|
||||
INSERT INTO bot_knowledge_base (category_id, question, answer, keywords, is_active, source_url, sort_order) VALUES
|
||||
(5, 'Что такое технический надзор?',
|
||||
'Это независимый контроль качества монтажа и пусконаладки систем безопасности. Гарантируем соответствие проекту, нормативам и срокам.',
|
||||
ARRAY['технический надзор', 'контроль', 'монтаж', 'качество', 'пусконаладка'], TRUE, 'https://aegisone.ru/supervision/', 22),
|
||||
|
||||
(5, 'Когда нужен технический надзор?',
|
||||
'При строительстве или реконструкции объекта, при модернизации систем безопасности, а также при приёмке объекта в эксплуатацию.',
|
||||
ARRAY['надзор', 'когда нужен', 'строительство', 'модернизация', 'приёмка'], TRUE, 'https://aegisone.ru/supervision/', 23),
|
||||
|
||||
(5, 'Чем технадзор отличается от авторского надзора?',
|
||||
'Авторский надзор ведётся проектировщиком и проверяет соответствие проекту. Технический надзор — это независимый контроль качества, сроков и соблюдения норм.',
|
||||
ARRAY['отличие', 'авторский', 'технадзор', 'проектировщик'], TRUE, 'https://aegisone.ru/supervision/', 24),
|
||||
|
||||
(5, 'Как документируются результаты технадзора?',
|
||||
NULL,
|
||||
ARRAY['документы', 'акты', 'технадзор', 'результаты'], FALSE, 'https://aegisone.ru/supervision/', 25);
|
||||
|
||||
-- === CATEGORY 6: ДОКУМЕНТАЦИЯ (3 with answers, 1 inactive) ===
|
||||
INSERT INTO bot_knowledge_base (category_id, question, answer, keywords, is_active, source_url, sort_order) VALUES
|
||||
(6, 'Какие документы вы разрабатываете?',
|
||||
'Проектную документацию для видеонаблюдения, СКУД, ОПС, периметральной защиты. А также проводим экспертизу существующей документации и аудит соответствия нормативам.',
|
||||
ARRAY['документация', 'проектная', 'разработка', 'скуда', 'опс', 'пожарная'], TRUE, 'https://aegisone.ru/documentation/', 26),
|
||||
|
||||
(6, 'Какие документы нужны для сдачи объекта?',
|
||||
'Зависит от типа объекта. Обычно: проектная документация (стадии П и Р), исполнительная документация, акты скрытых работ, паспорта и сертификаты оборудования, акты приёмки.',
|
||||
ARRAY['сдача', 'документы', 'объект', 'приёмка', 'исполнительная'], TRUE, 'https://aegisone.ru/documentation/', 27),
|
||||
|
||||
(6, 'Есть ли лицензия на проектирование?',
|
||||
'Да, компания имеет все необходимые разрешения и компетенции для проектирования объектов любого класса сложности.',
|
||||
ARRAY['лицензия', 'разрешение', 'проектирование', 'сро'], TRUE, 'https://aegisone.ru/documentation/', 28),
|
||||
|
||||
(6, 'Можно ли восстановить утерянную документацию?',
|
||||
NULL,
|
||||
ARRAY['восстановление', 'документация', 'утеряна', 'исполнительная схема'], FALSE, 'https://aegisone.ru/documentation/', 29);
|
||||
|
||||
-- === CATEGORY 7: РИСК-ИНЖИНИРИНГ (3 with answers, 1 inactive) ===
|
||||
INSERT INTO bot_knowledge_base (category_id, question, answer, keywords, is_active, source_url, sort_order) VALUES
|
||||
(7, 'Что такое риск-инжиниринг?',
|
||||
'Это системный подход к управлению рисками безопасности: оценка, анализ и разработка стратегии защиты. Помогает защитить бизнес от финансовых и репутационных потерь.',
|
||||
ARRAY['риск', 'инжиниринг', 'управление', 'оценка', 'стратегия'], TRUE, 'https://aegisone.ru/risk/', 30),
|
||||
|
||||
(7, 'Чем риск-инжиниринг отличается от аудита?',
|
||||
'Аудит оценивает текущее состояние систем. Риск-инжиниринг шире: анализирует бизнес-процессы, угрозы, сценарии и предлагает стратегию с экономическим обоснованием.',
|
||||
ARRAY['отличие', 'риск-инжиниринг', 'аудит', 'разница'], TRUE, 'https://aegisone.ru/risk/', 31),
|
||||
|
||||
(7, 'Как измерить эффективность безопасности?',
|
||||
'Через ROI безопасности: сравниваем стоимость риска «до» и «после» внедрения мер. Даём конкретные цифры и сроки окупаемости.',
|
||||
ARRAY['эффективность', 'roi', 'безопасность', 'измерение', 'окупаемость'], TRUE, 'https://aegisone.ru/risk/', 32),
|
||||
|
||||
(7, 'Как часто нужно обновлять оценку рисков?',
|
||||
NULL,
|
||||
ARRAY['обновление', 'периодичность', 'риски', 'оценка', 'частота'], FALSE, 'https://aegisone.ru/risk/', 33);
|
||||
|
||||
-- === CATEGORY 8: ОТРАСЛИ И КЕЙСЫ (7 inactive — all need answers) ===
|
||||
INSERT INTO bot_knowledge_base (category_id, question, answer, keywords, is_active, source_url, sort_order) VALUES
|
||||
(8, 'В каких отраслях вы работаете?',
|
||||
NULL,
|
||||
ARRAY['отрасли', 'клиенты', 'ритейл', 'склады', 'производство', 'офисы', 'гостиницы', 'банки'], FALSE, 'https://aegisone.ru/', 34),
|
||||
|
||||
(8, 'Работаете ли вы с торговыми центрами?',
|
||||
NULL,
|
||||
ARRAY['тц', 'торговый центр', 'ритейл', 'безопасность'], FALSE, 'https://aegisone.ru/', 35),
|
||||
|
||||
(8, 'Работаете ли вы с производственными предприятиями?',
|
||||
NULL,
|
||||
ARRAY['производство', 'завод', 'промышленность', 'безопасность'], FALSE, 'https://aegisone.ru/', 36),
|
||||
|
||||
(8, 'Был ли у вас кейс со складом торговой сети?',
|
||||
NULL,
|
||||
ARRAY['кейс', 'склад', 'торговая сеть', 'пример'], FALSE, 'https://aegisone.ru/', 37),
|
||||
|
||||
(8, 'Какой результат вы получили для банковского офиса?',
|
||||
NULL,
|
||||
ARRAY['банк', 'офис', 'опс', 'пожарная', 'кейс'], FALSE, 'https://aegisone.ru/', 38),
|
||||
|
||||
(8, 'Что такое метод «Тайный покупатель»?',
|
||||
NULL,
|
||||
ARRAY['тайный покупатель', 'оценка', 'персонал', 'охрана', 'метод'], FALSE, 'https://aegisone.ru/', 39),
|
||||
|
||||
(8, 'Есть ли у вас кейсы для гостиниц?',
|
||||
NULL,
|
||||
ARRAY['гостиница', 'отель', 'кейс', 'скуда', 'бронирование'], FALSE, 'https://aegisone.ru/', 40);
|
||||
Reference in New Issue
Block a user