Initial commit

This commit is contained in:
2026-05-17 05:22:06 +03:00
commit ca4d00c895
155 changed files with 45216 additions and 0 deletions
+401
View File
@@ -0,0 +1,401 @@
-- AegisOne Engineering — PostgreSQL Schema
-- Adapted from MySQL for Python/FastAPI migration
-- Users
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
login VARCHAR(50) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
role VARCHAR(20) NOT NULL DEFAULT 'technician' CHECK (role IN ('owner','engineer','technician')),
full_name VARCHAR(255) NOT NULL,
phone VARCHAR(50) DEFAULT '',
email VARCHAR(255) DEFAULT '',
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_users_role ON users(role);
CREATE INDEX idx_users_active ON users(is_active);
-- Login attempts
CREATE TABLE IF NOT EXISTS login_attempts (
id SERIAL PRIMARY KEY,
ip_address VARCHAR(45) NOT NULL,
login VARCHAR(50) NOT NULL DEFAULT '',
attempt_time TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_login_attempts_ip ON login_attempts(ip_address);
CREATE INDEX idx_login_attempts_time ON login_attempts(attempt_time);
-- Audit log
CREATE TABLE IF NOT EXISTS audit_log (
id BIGSERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
action VARCHAR(255) NOT NULL,
details TEXT,
ip_address VARCHAR(45) DEFAULT '',
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_audit_user ON audit_log(user_id);
CREATE INDEX idx_audit_action ON audit_log(action);
CREATE INDEX idx_audit_created ON audit_log(created_at);
-- Customers
CREATE TABLE IF NOT EXISTS customers (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
inn VARCHAR(12) DEFAULT '',
kpp VARCHAR(9) DEFAULT '',
legal_address VARCHAR(500) DEFAULT '',
contact_person VARCHAR(255) DEFAULT '',
contact_phone VARCHAR(50) DEFAULT '',
contact_email VARCHAR(255) DEFAULT '',
status VARCHAR(20) NOT NULL DEFAULT 'active' CHECK (status IN ('active','inactive','prospect')),
notes TEXT,
created_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_customers_status ON customers(status);
CREATE INDEX idx_customers_inn ON customers(inn);
-- Objects
CREATE TABLE IF NOT EXISTS objects (
id SERIAL PRIMARY KEY,
customer_id INTEGER REFERENCES customers(id) ON DELETE SET NULL,
name VARCHAR(255) NOT NULL,
address VARCHAR(500) DEFAULT '',
object_type VARCHAR(100) DEFAULT '',
area_sqm NUMERIC(10,2) DEFAULT 0,
employees_count INTEGER DEFAULT 0,
contact_person VARCHAR(255) DEFAULT '',
contact_phone VARCHAR(50) DEFAULT '',
status VARCHAR(20) NOT NULL DEFAULT 'active' CHECK (status IN ('active','inactive','audit','prospective')),
notes TEXT,
risk_score NUMERIC(5,2) DEFAULT 0,
complexity_index NUMERIC(5,2) DEFAULT 0,
infrastructure_load NUMERIC(5,2) DEFAULT 0,
service_history NUMERIC(5,2) DEFAULT 0,
object_index NUMERIC(5,2) DEFAULT 0,
sla_price_monthly NUMERIC(12,2) DEFAULT 0,
region_factor NUMERIC(3,2) DEFAULT 1.00,
created_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_objects_status ON objects(status);
CREATE INDEX idx_objects_customer ON objects(customer_id);
-- Object assignments
CREATE TABLE IF NOT EXISTS object_assignments (
id SERIAL PRIMARY KEY,
object_id INTEGER NOT NULL REFERENCES objects(id) ON DELETE CASCADE,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role VARCHAR(20) NOT NULL CHECK (role IN ('engineer','technician')),
assigned_at TIMESTAMP NOT NULL DEFAULT NOW(),
unassigned_at TIMESTAMP DEFAULT NULL
);
CREATE INDEX idx_assignments_object ON object_assignments(object_id);
CREATE INDEX idx_assignments_user ON object_assignments(user_id);
-- Tasks
CREATE TABLE IF NOT EXISTS tasks (
id SERIAL PRIMARY KEY,
object_id INTEGER NOT NULL REFERENCES objects(id) ON DELETE CASCADE,
assigned_to INTEGER REFERENCES users(id) ON DELETE SET NULL,
created_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
title VARCHAR(500) NOT NULL,
description TEXT,
priority VARCHAR(4) NOT NULL DEFAULT 'P3' CHECK (priority IN ('P1','P2','P3','P4')),
status VARCHAR(20) NOT NULL DEFAULT 'open' CHECK (status IN ('open','in_progress','completed','cancelled')),
deadline TIMESTAMP,
sla_remaining_hours NUMERIC(6,1) DEFAULT 0,
response_time_minutes INTEGER DEFAULT 0,
resolution_time_minutes INTEGER DEFAULT 0,
sla_compliant BOOLEAN,
checklist_data JSONB,
result_notes TEXT,
closed_at TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_tasks_object ON tasks(object_id);
CREATE INDEX idx_tasks_assigned ON tasks(assigned_to);
CREATE INDEX idx_tasks_status ON tasks(status);
CREATE INDEX idx_tasks_priority ON tasks(priority);
-- Task comments
CREATE TABLE IF NOT EXISTS task_comments (
id SERIAL PRIMARY KEY,
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
comment TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_comments_task ON task_comments(task_id);
-- Task photos
CREATE TABLE IF NOT EXISTS task_photos (
id SERIAL PRIMARY KEY,
task_id INTEGER REFERENCES tasks(id) ON DELETE SET NULL,
report_id INTEGER REFERENCES reports(id) ON DELETE SET NULL,
file_path VARCHAR(500) NOT NULL,
description VARCHAR(500) DEFAULT '',
type VARCHAR(20) DEFAULT 'other' CHECK (type IN ('before','after','evidence','other')),
uploaded_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_photos_task ON task_photos(task_id);
-- Reports (created after task_photos for FK reference)
CREATE TABLE IF NOT EXISTS reports (
id SERIAL PRIMARY KEY,
object_id INTEGER NOT NULL REFERENCES objects(id) ON DELETE CASCADE,
created_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
report_type VARCHAR(20) NOT NULL DEFAULT 'service' CHECK (report_type IN ('inspection','service','emergency','audit','monthly')),
title VARCHAR(500) DEFAULT '',
description TEXT,
findings TEXT,
recommendations TEXT,
work_done TEXT,
cause_classification VARCHAR(100),
result_status VARCHAR(20) DEFAULT 'ok' CHECK (result_status IN ('fixed','partial','revisit','ok')),
status VARCHAR(20) NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','final','cancelled')),
client_confirmed BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_reports_object ON reports(object_id);
CREATE INDEX idx_reports_author ON reports(created_by);
-- Add FK for task_photos.report_id after reports table exists
-- (handled in application-level migration)
-- Questionnaire sessions
CREATE TABLE IF NOT EXISTS questionnaire_sessions (
id SERIAL PRIMARY KEY,
object_id INTEGER REFERENCES objects(id) ON DELETE SET NULL,
client_name VARCHAR(255) DEFAULT '',
created_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
status VARCHAR(20) NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','completed','cancelled')),
current_step SMALLINT NOT NULL DEFAULT 1,
risk_score NUMERIC(5,2) DEFAULT 0,
complexity_index NUMERIC(5,2) DEFAULT 0,
infrastructure_load NUMERIC(5,2) DEFAULT 0,
service_history NUMERIC(5,2) DEFAULT 0,
object_index NUMERIC(5,2) DEFAULT 0,
sla_price_monthly NUMERIC(12,2) DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_qsessions_object ON questionnaire_sessions(object_id);
CREATE INDEX idx_qsessions_author ON questionnaire_sessions(created_by);
-- Questionnaire answers
CREATE TABLE IF NOT EXISTS questionnaire_answers (
id SERIAL PRIMARY KEY,
session_id INTEGER NOT NULL REFERENCES questionnaire_sessions(id) ON DELETE CASCADE,
step SMALLINT NOT NULL,
question_key VARCHAR(100) NOT NULL,
answer_value TEXT,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_qanswers_session ON questionnaire_answers(session_id);
CREATE INDEX idx_qanswers_step ON questionnaire_answers(step);
-- Questionnaire items
CREATE TABLE IF NOT EXISTS questionnaire_items (
id SERIAL PRIMARY KEY,
step INTEGER NOT NULL DEFAULT 1,
section VARCHAR(100) NOT NULL DEFAULT '',
question_key VARCHAR(100) NOT NULL UNIQUE,
label VARCHAR(255) NOT NULL,
type VARCHAR(20) NOT NULL DEFAULT 'text' CHECK (type IN ('text','number','select','radio','checkbox','textarea')),
options JSONB,
required BOOLEAN NOT NULL DEFAULT FALSE,
sort_order INTEGER NOT NULL DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
help_text VARCHAR(500) DEFAULT '',
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_qitems_step ON questionnaire_items(step);
CREATE INDEX idx_qitems_key ON questionnaire_items(question_key);
CREATE INDEX idx_qitems_sort ON questionnaire_items(sort_order);
-- Object passports
CREATE TABLE IF NOT EXISTS object_passports (
id SERIAL PRIMARY KEY,
session_id INTEGER REFERENCES questionnaire_sessions(id) ON DELETE SET NULL,
object_id INTEGER REFERENCES objects(id) ON DELETE SET NULL,
object_index NUMERIC(5,2) DEFAULT 0,
risk_score NUMERIC(5,2) DEFAULT 0,
complexity_index NUMERIC(5,2) DEFAULT 0,
sla_price_monthly NUMERIC(12,2) DEFAULT 0,
passport_data JSONB,
created_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_passports_object ON object_passports(object_id);
CREATE INDEX idx_passports_session ON object_passports(session_id);
-- SLA contracts
CREATE TABLE IF NOT EXISTS sla_contracts (
id SERIAL PRIMARY KEY,
customer_id INTEGER REFERENCES customers(id) ON DELETE SET NULL,
object_id INTEGER NOT NULL REFERENCES objects(id) ON DELETE CASCADE,
contract_number VARCHAR(50) DEFAULT '',
client_name VARCHAR(255) DEFAULT '',
description TEXT,
start_date DATE NOT NULL,
end_date DATE,
sla_price_monthly NUMERIC(12,2) NOT NULL DEFAULT 0,
penalty_rate NUMERIC(5,2) DEFAULT 0,
response_time_p1 INTEGER DEFAULT 4,
response_time_p2 INTEGER DEFAULT 8,
response_time_p3 INTEGER DEFAULT 24,
response_time_hours NUMERIC(5,1),
service_level VARCHAR(20) NOT NULL DEFAULT 'business' CHECK (service_level IN ('start','business','enterprise')),
status VARCHAR(20) NOT NULL DEFAULT 'negotiation' CHECK (status IN ('active','expired','cancelled','negotiation')),
created_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_sla_object ON sla_contracts(object_id);
CREATE INDEX idx_sla_status ON sla_contracts(status);
CREATE INDEX idx_sla_customer ON sla_contracts(customer_id);
-- Incidents
CREATE TABLE IF NOT EXISTS incidents (
id SERIAL PRIMARY KEY,
object_id INTEGER NOT NULL REFERENCES objects(id) ON DELETE CASCADE,
reported_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
assigned_to INTEGER REFERENCES users(id) ON DELETE SET NULL,
title VARCHAR(500) NOT NULL,
description TEXT,
severity VARCHAR(4) NOT NULL DEFAULT 'P3' CHECK (severity IN ('P1','P2','P3')),
status VARCHAR(20) NOT NULL DEFAULT 'open' CHECK (status IN ('open','in_progress','resolved','closed')),
resolution TEXT,
cause VARCHAR(500) DEFAULT '',
resolution_time_minutes INTEGER DEFAULT 0,
sla_breached BOOLEAN DEFAULT FALSE,
resolved_at TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_incidents_object ON incidents(object_id);
CREATE INDEX idx_incidents_severity ON incidents(severity);
CREATE INDEX idx_incidents_status ON incidents(status);
CREATE INDEX idx_incidents_assigned ON incidents(assigned_to);
-- Engineer KPI
CREATE TABLE IF NOT EXISTS engineer_kpi (
id SERIAL PRIMARY KEY,
engineer_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
period_start DATE NOT NULL,
period_end DATE NOT NULL,
sla_compliance NUMERIC(5,2) DEFAULT 0,
response_time_score NUMERIC(5,2) DEFAULT 0,
resolution_time_score NUMERIC(5,2) DEFAULT 0,
diagnosis_accuracy NUMERIC(5,2) DEFAULT 0,
reopen_rate NUMERIC(5,2) DEFAULT 0,
risk_coverage_score NUMERIC(5,2) DEFAULT 0,
documentation_quality NUMERIC(5,2) DEFAULT 0,
engineer_score NUMERIC(5,2) DEFAULT 0,
grade VARCHAR(20) DEFAULT '',
components_json JSONB,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_kpi_engineer ON engineer_kpi(engineer_id);
CREATE INDEX idx_kpi_period ON engineer_kpi(period_start, period_end);
-- SHS records
CREATE TABLE IF NOT EXISTS shs_records (
id SERIAL PRIMARY KEY,
recorded_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
period_start DATE NOT NULL,
period_end DATE NOT NULL,
sla_stability NUMERIC(5,2) DEFAULT 0,
revenue_stability NUMERIC(5,2) DEFAULT 0,
retention NUMERIC(5,2) DEFAULT 0,
engineer_performance NUMERIC(5,2) DEFAULT 0,
incident_stability NUMERIC(5,2) DEFAULT 0,
sales_flow NUMERIC(5,2) DEFAULT 0,
operational_efficiency NUMERIC(5,2) DEFAULT 0,
shs_score NUMERIC(5,2) DEFAULT 0,
shs_status VARCHAR(20) DEFAULT '',
components_json JSONB,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_shs_period ON shs_records(period_start, period_end);
-- Blog posts
CREATE TABLE IF NOT EXISTS blog_posts (
id SERIAL PRIMARY KEY,
title VARCHAR(500) NOT NULL,
slug VARCHAR(500) NOT NULL UNIQUE,
content TEXT NOT NULL,
excerpt VARCHAR(1000) DEFAULT '',
category VARCHAR(50) NOT NULL DEFAULT 'audit' CHECK (category IN ('audit','sla','incident','supervision','documentation','risk','cases')),
author_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
status VARCHAR(20) NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','published','archived')),
published_at TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_blog_slug ON blog_posts(slug);
CREATE INDEX idx_blog_category ON blog_posts(category);
CREATE INDEX idx_blog_status ON blog_posts(status);
CREATE INDEX idx_blog_published ON blog_posts(published_at);
-- Formula coefficients
CREATE TABLE IF NOT EXISTS formula_coefficients (
id SERIAL PRIMARY KEY,
key VARCHAR(100) NOT NULL UNIQUE,
value NUMERIC(10,4) NOT NULL DEFAULT 0,
description VARCHAR(500) DEFAULT '',
formula_ref VARCHAR(255) DEFAULT '',
is_active BOOLEAN NOT NULL DEFAULT TRUE,
updated_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_coeff_key ON formula_coefficients(key);
CREATE INDEX idx_coeff_active ON formula_coefficients(is_active);
-- Cases
CREATE TABLE IF NOT EXISTS cases (
id SERIAL PRIMARY KEY,
title VARCHAR(500) NOT NULL,
text TEXT NOT NULL,
effect TEXT NOT NULL,
sort_order INTEGER NOT NULL DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_cases_sort ON cases(sort_order);
CREATE INDEX idx_cases_active ON cases(is_active);
-- Updated_at trigger function
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Apply triggers
CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_customers_updated_at BEFORE UPDATE ON customers FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_objects_updated_at BEFORE UPDATE ON objects FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_tasks_updated_at BEFORE UPDATE ON tasks FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_reports_updated_at BEFORE UPDATE ON reports FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_qsessions_updated_at BEFORE UPDATE ON questionnaire_sessions FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_qitems_updated_at BEFORE UPDATE ON questionnaire_items FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_sla_updated_at BEFORE UPDATE ON sla_contracts FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_incidents_updated_at BEFORE UPDATE ON incidents FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_blog_updated_at BEFORE UPDATE ON blog_posts FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_cases_updated_at BEFORE UPDATE ON cases FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_coeff_updated_at BEFORE UPDATE ON formula_coefficients FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+149
View File
@@ -0,0 +1,149 @@
-- Seed data for PostgreSQL
-- Default owner (password: AegisOne2024!)
INSERT INTO users (login, password_hash, role, full_name, is_active)
VALUES ('owner', '$2b$12$LJ3m4ys3Lk8KqJqKqKqKqO5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'owner', 'System Owner', TRUE)
ON CONFLICT (login) DO NOTHING;
-- Formula coefficients
INSERT INTO formula_coefficients (key, value, description, formula_ref) VALUES
('risk_score.no_archive', 25, 'Нет архива видеонаблюдения', 'calc_risk_score()'),
('risk_score.no_power_backup', 20, 'Нет резервного питания', 'calc_risk_score()'),
('risk_score.no_regulations', 15, 'Нет регламента обслуживания', 'calc_risk_score()'),
('risk_score.system_failures', 20, 'Частые сбои систем', 'calc_risk_score()'),
('risk_score.no_documentation', 10, 'Нет документации', 'calc_risk_score()'),
('risk_score.bound_low', 20, 'Граница низкого риска', 'risk_label()'),
('risk_score.bound_medium', 50, 'Граница среднего риска', 'risk_label()'),
('risk_score.bound_high', 75, 'Граница высокого риска', 'risk_label()'),
('risk_multiplier.low', 1.0, 'Множитель низкого риска (0-20)', 'risk_multiplier()'),
('risk_multiplier.medium', 1.3, 'Множитель среднего риска (21-50)', 'risk_multiplier()'),
('risk_multiplier.high', 1.6, 'Множитель высокого риска (51-75)', 'risk_multiplier()'),
('risk_multiplier.critical', 2.0, 'Множитель критического риска (76-100)', 'risk_multiplier()'),
('complexity.video_0_20', 10, 'Видеонаблюдение до 20 камер', 'calc_complexity()'),
('complexity.video_20_100', 20, 'Видеонаблюдение 20-100 камер', 'calc_complexity()'),
('complexity.video_100plus', 35, 'Видеонаблюдение 100+ камер', 'calc_complexity()'),
('complexity.access_0_5', 10, 'СКУД до 5 точек', 'calc_complexity()'),
('complexity.access_5_20', 20, 'СКУД 5-20 точек', 'calc_complexity()'),
('complexity.access_20plus', 30, 'СКУД 20+ точек', 'calc_complexity()'),
('complexity.fire_simple', 15, 'Пожарная сигнализация простая', 'calc_complexity()'),
('complexity.fire_medium', 25, 'Пожарная сигнализация средняя', 'calc_complexity()'),
('complexity.fire_complex', 40, 'Пожарная сигнализация сложная (>5000 м²)', 'calc_complexity()'),
('complexity.it', 10, 'IT-инфраструктура', 'calc_complexity()'),
('infra.server_none', 15, 'Нет сервера', 'calc_infrastructure_load()'),
('infra.server_weak', 10, 'Слабый сервер', 'calc_infrastructure_load()'),
('infra.network_unstable', 20, 'Нестабильная сеть', 'calc_infrastructure_load()'),
('infra.network_partial', 10, 'Частичная сеть', 'calc_infrastructure_load()'),
('infra.power_none', 20, 'Нет UPS', 'calc_infrastructure_load()'),
('infra.power_weak', 10, 'Слабый UPS', 'calc_infrastructure_load()'),
('history.none', 30, 'Нет обслуживания', 'calc_service_history()'),
('history.irregular', 20, 'Нерегулярное обслуживание', 'calc_service_history()'),
('history.formal', 10, 'Формальный подрядчик', 'calc_service_history()'),
('history.sla', 0, 'Есть SLA', 'calc_service_history()'),
('object_index.risk_weight', 0.4, 'Вес Risk Score', 'calc_object_index()'),
('object_index.complexity_weight', 0.3, 'Вес Complexity', 'calc_object_index()'),
('object_index.infra_weight', 0.2, 'Вес Infra Load', 'calc_object_index()'),
('object_index.history_weight', 0.1, 'Вес Service History', 'calc_object_index()'),
('sla_price.base_cost', 15000, 'Базовая стоимость инженера', 'calc_sla_price()'),
('sla_price.region_default', 1.0, 'Региональный коэффициент по умолчанию', 'calc_sla_price()'),
('engineer.score.sla_weight', 0.25, 'Вес SLA Compliance', 'calc_engineer_score()'),
('engineer.score.response_weight', 0.20, 'Вес Response Time', 'calc_engineer_score()'),
('engineer.score.resolution_weight', 0.20, 'Вес Resolution Time', 'calc_engineer_score()'),
('engineer.score.diagnosis_weight', 0.15, 'Вес Diagnosis Accuracy', 'calc_engineer_score()'),
('engineer.score.reopen_weight', 0.10, 'Вес Reopen Rate', 'calc_engineer_score()'),
('engineer.score.risk_coverage_weight', 0.10, 'Вес Risk Coverage', 'calc_engineer_score()'),
('engineer.grade.senior', 90, 'Граница Senior', 'engineer_grade()'),
('engineer.grade.strong', 80, 'Граница Strong', 'engineer_grade()'),
('engineer.grade.middle', 70, 'Граница Middle', 'engineer_grade()'),
('ecs.sla_weight', 0.30, 'Вес SLA Control', 'calc_engineer_control_score()'),
('ecs.task_dist_weight', 0.25, 'Вес Task Distribution', 'calc_engineer_control_score()'),
('ecs.incident_red_weight', 0.20, 'Вес Incident Reduction', 'calc_engineer_control_score()'),
('ecs.team_perf_weight', 0.15, 'Вес Team Performance', 'calc_engineer_control_score()'),
('ecs.response_coord_weight', 0.10, 'Вес Response Coordination', 'calc_engineer_control_score()'),
('shs.sla_stability_weight', 0.22, 'Вес SLA Stability', 'calc_shs()'),
('shs.revenue_stability_weight', 0.18, 'Вес Revenue Stability', 'calc_shs()'),
('shs.retention_weight', 0.18, 'Вес Retention', 'calc_shs()'),
('shs.engineer_perf_weight', 0.15, 'Вес Engineer Performance', 'calc_shs()'),
('shs.incident_stability_weight', 0.12, 'Вес Incident Stability', 'calc_shs()'),
('shs.sales_flow_weight', 0.10, 'Вес Sales Flow', 'calc_shs()'),
('shs.operational_eff_weight', 0.05, 'Вес Operational Efficiency', 'calc_shs()'),
('shs.zone_growth', 85, 'Граница зоны Growth', 'shs_status(), shs_zone()'),
('shs.zone_stable', 70, 'Граница зоны Stable', 'shs_status(), shs_zone()'),
('shs.zone_risk', 50, 'Граница зоны Risk', 'shs_status(), shs_zone()'),
('shs.delta_warning', 5, 'Порог предупреждения ΔSHS', 'shs_delta()'),
('shs.delta_critical', 10, 'Порог критического ΔSHS', 'shs_delta()'),
('ceo_shs.mrr_weight', 0.25, 'Вес MRR Growth', 'calc_ceo_shs()'),
('ceo_shs.sla_weight', 0.20, 'Вес SLA Compliance', 'calc_ceo_shs()'),
('ceo_shs.retention_weight', 0.20, 'Вес Retention', 'calc_ceo_shs()'),
('ceo_shs.productivity_weight', 0.15, 'Вес Productivity', 'calc_ceo_shs()'),
('ceo_shs.conversion_weight', 0.10, 'Вес Conversion', 'calc_ceo_shs()'),
('ceo_shs.incident_weight', 0.10, 'Вес Incident Stability', 'calc_ceo_shs()'),
('ssi.breach_severity_p1', 1.0, 'Severity P1', 'calc_breach_severity()'),
('ssi.breach_severity_p2', 0.5, 'Severity P2', 'calc_breach_severity()'),
('ssi.breach_severity_p3', 0.2, 'Severity P3', 'calc_breach_severity()'),
('isi.severity_p1', 1.0, 'Severity P1', 'calc_incident_stability_index()'),
('isi.severity_p2', 0.5, 'Severity P2', 'calc_incident_stability_index()'),
('isi.severity_p3', 0.2, 'Severity P3', 'calc_incident_stability_index()'),
('rules.shs_threshold', 70, 'Порог SHS', 'check_automation_rules()'),
('rules.sla_threshold', 90, 'Порог SLA Compliance', 'check_automation_rules()'),
('rules.retention_threshold', 90, 'Порог Retention', 'check_automation_rules()'),
('bonus.score_90plus', 90, 'Граница премии 90+', 'calc_bonus_percent()'),
('bonus.score_80_89', 80, 'Граница премии 80-89', 'calc_bonus_percent()'),
('bonus.premium_90plus', 20, 'Премия при Score >= 90 (%)', 'calc_bonus_percent()'),
('bonus.premium_80_89', 10, 'Премия при Score 80-89 (%)', 'calc_bonus_percent()'),
('bonus.premium_below_80', 0, 'Премия при Score < 80 (%)', 'calc_bonus_percent()'),
('retention.key_client_penalty', 0.1, 'Штраф за потерю ключевого клиента', 'calc_retention_key_client()')
ON CONFLICT (key) DO NOTHING;
-- Cases
INSERT INTO cases (title, text, effect, sort_order) VALUES
('Склад крупной торговой сети', 'Выявлено: слепые зоны в зонах разгрузки, недостаточное разрешение камер в зонах погрузки/разгрузки. Одна камера не исправна, у двух не настроена запись.', 'предотвращение убытков на сумму более 5 млн рублей в год.', 1),
('Производство пищевых продуктов', 'Неправильная настройка СКУД позволяла несанкционированный доступ в производственные помещения, создавая риск нарушения норм СанПин.', 'снижение риска порчи продукции и утечки конфиденциальной информации.', 2),
('Банковский офис', 'Недостатки в системе ОПС приводили к невозможности срабатывания или задержкам в реагировании при ЧП.', 'сокращение рисков утраты имущества и ценностей, повышение уровня безопасности.', 3),
('Торгово-развлекательный центр', 'Метод «Тайный покупатель» выявил: охрана не реагирует на срабатывание металлодетекторов, оставляет посты без присмотра. Камеры на парковке не охватывают часть машиномест.', 'снижение риска краж, повышение дисциплины службы охраны.', 4),
('Гостиница', 'СКУД не синхронизирована с системой бронирования — доступ в занятые номера. ОПС в части номеров отключена. Видеокамеры имеют мёртвые зоны.', 'устранение репутационных рисков и риска хищения личных вещей гостей.', 5),
('Логистический центр', 'Отсутствие видеоконтроля на участках приёмки и отгрузки приводило к систематическим хищениям. СКУД на въезде не фиксирует данные транспортных средств.', 'сокращение потерь товара на 15%.', 6)
ON CONFLICT DO NOTHING;
-- Questionnaire items
INSERT INTO questionnaire_items (step, section, question_key, label, type, options, required, sort_order, help_text) VALUES
(1, 'Коммерческий профиль объекта', 'object_name', 'Название организации / объекта', 'text', NULL, TRUE, 1, ''),
(1, 'Коммерческий профиль объекта', 'object_type', 'Тип объекта', 'select', '["","Гостиница","Склад","Производство","ТЦ","Офис","БЦ","Медицина","Образование","Другое"]', FALSE, 2, ''),
(1, 'Коммерческий профиль объекта', 'address', 'Адрес объекта', 'text', NULL, FALSE, 3, ''),
(1, 'Коммерческий профиль объекта', 'area', 'Площадь (м²)', 'number', NULL, FALSE, 4, ''),
(1, 'Коммерческий профиль объекта', 'employees', 'Количество сотрудников', 'number', NULL, FALSE, 5, ''),
(1, 'Коммерческий профиль объекта', 'contact_person', 'Контактное лицо', 'text', NULL, FALSE, 6, ''),
(1, 'Коммерческий профиль объекта', 'contact_phone', 'Телефон', 'text', NULL, FALSE, 7, ''),
(1, 'Коммерческий профиль объекта', 'region_factor', 'Региональный коэффициент', 'select', '{"1.0":"Краснодар (1.0)","1.1":"Краснодарский край (1.1)","1.3":"РФ (1.3)","1.6":"РФ удалённые (1.6)","1.8":"Москва (1.8)"}', FALSE, 8, ''),
(2, 'Видеонаблюдение', 'has_video', 'Есть система видеонаблюдения?', 'radio', '{"1":"Да","0":"Нет"}', FALSE, 1, ''),
(2, 'Видеонаблюдение', 'camera_count', 'Количество камер', 'number', NULL, FALSE, 2, ''),
(2, 'Видеонаблюдение', 'video_type', 'Тип системы', 'select', '["","ip","analog","mixed"]', FALSE, 3, ''),
(2, 'Видеонаблюдение', 'archive_depth', 'Глубина архива', 'select', '{"7":"7 дней","14":"14 дней","30":"30+ дней"}', FALSE, 4, ''),
(2, 'СКУД', 'has_acs', 'Есть система СКУД?', 'radio', '{"1":"Да","0":"Нет"}', FALSE, 5, ''),
(2, 'СКУД', 'access_points', 'Точек доступа', 'number', NULL, FALSE, 6, ''),
(2, 'СКУД', 'acs_vendor', 'Производитель СКУД', 'text', NULL, FALSE, 7, ''),
(2, 'Пожарная сигнализация', 'has_fire', 'Есть пожарная сигнализация?', 'radio', '{"1":"Да","0":"Нет"}', FALSE, 8, ''),
(2, 'Пожарная сигнализация', 'fire_type', 'Сложность системы', 'select', '{"simple":"Простая","medium":"Средняя","complex":"Сложная (>5000 м²)"}', FALSE, 9, ''),
(2, 'Инфраструктура', 'server_state', 'Состояние сервера', 'select', '{"ok":"Нормальный","weak":"Слабый","none":"Нет"}', FALSE, 10, ''),
(2, 'Инфраструктура', 'network_state', 'Состояние сети', 'select', '{"stable":"Стабильная","partial":"Частично","unstable":"Нестабильная"}', FALSE, 11, ''),
(2, 'Инфраструктура', 'power_state', 'Электропитание (UPS)', 'select', '{"ok":"Нормальное","weak":"Слабый UPS","none":"Нет UPS"}', FALSE, 12, ''),
(3, 'Эксплуатационная модель', 'service_state', 'Как обслуживается объект сейчас?', 'select', '{"none":"Не обслуживается","irregular":"Нерегулярно","formal":"Формальный подрядчик","sla":"Есть SLA"}', FALSE, 1, ''),
(3, 'Эксплуатационная модель', 'has_regulations', 'Есть ли регламент?', 'checkbox', NULL, FALSE, 2, ''),
(3, 'Эксплуатационная модель', 'problems', 'Отметьте частые проблемы', 'checkbox', '["Не работают камеры","Пропадает архив","Зависает СКУД","Ошибки пожарки","Нет реакции подрядчика","Нет понимания состояния систем"]', FALSE, 3, ''),
(3, 'Эксплуатационная модель', 'resolution_time', 'Среднее время устранения', 'select', '{"2h":"До 2 часов","24h":"До суток","days":"Несколько дней"}', FALSE, 4, ''),
(3, 'Эксплуатационная модель', 'controller', 'Кто контролирует систему?', 'select', '{"engineer":"Штатный инженер","contractor":"Подрядчик","nobody":"Никто"}', FALSE, 5, ''),
(4, 'Risk Assessment', 'no_archive', 'Нет архива видеонаблюдения (+25)', 'checkbox', NULL, FALSE, 1, ''),
(4, 'Risk Assessment', 'no_power', 'Нет резервного питания (+20)', 'checkbox', NULL, FALSE, 2, ''),
(4, 'Risk Assessment', 'no_regulations', 'Нет регламента обслуживания (+15)', 'checkbox', NULL, FALSE, 3, ''),
(4, 'Risk Assessment', 'frequent_failures', 'Частые сбои систем (+20)', 'checkbox', NULL, FALSE, 4, ''),
(4, 'Risk Assessment', 'no_documentation', 'Нет документации (+10)', 'checkbox', NULL, FALSE, 5, ''),
(4, 'Risk Assessment', 'consequences', 'Потенциальные последствия', 'checkbox', '["loss_of_evidence","shutdown","fines","no_investigation","access_fail"]', FALSE, 6, ''),
(4, 'Risk Assessment', 'confidence', 'Уровень уверенности в системе', 'select', '{"high":"Высокая","medium":"Средняя","low":"Низкая"}', FALSE, 7, ''),
(5, 'Расчёт SLA', 'sla_level', 'Предпочтительный уровень обслуживания', 'select', '{"start":"Базовый (Start SLA)","business":"Стандарт (Business SLA)","enterprise":"Расширенный (Enterprise SLA)"}', FALSE, 1, ''),
(5, 'Расчёт SLA', 'visit_frequency', 'Интенсивность обслуживания', 'select', '{"1":"1 выезд / мес","2":"2 выезда / мес","incident":"По инцидентам"}', FALSE, 2, ''),
(5, 'Расчёт SLA', 'reaction_time', 'Требуемое время реакции', 'select', '{"24":"24 часа","4":"4 часа","2":"2 часа"}', FALSE, 3, ''),
(5, 'Расчёт SLA', 'extra_247', '24/7 поддержка', 'checkbox', NULL, FALSE, 4, ''),
(5, 'Расчёт SLA', 'extra_emergency', 'Аварийные выезды', 'checkbox', NULL, FALSE, 5, ''),
(5, 'Расчёт SLA', 'extra_audit', 'Аудит', 'checkbox', NULL, FALSE, 6, ''),
(5, 'Расчёт SLA', 'extra_docs', 'Документация', 'checkbox', NULL, FALSE, 7, ''),
(5, 'Расчёт SLA', 'extra_contractors', 'Контроль подрядчиков', 'checkbox', NULL, FALSE, 8, '')
ON CONFLICT (question_key) DO NOTHING;