LearnSpace: full-stack educational whiteboard platform

Node.js/Express backend + vanilla JS frontend.
Features: real-time collaborative whiteboard (SSE), multi-page support,
LaTeX formulas, shapes/connectors, coordinate systems, number lines,
compass, zoom/pan, Catmull-Rom pencil smoothing, ruler/protractor with
rotation & resize controls, minimap navigation overlay, auto-measurements,
multi-page thumbnails sidebar, PNG export, page templates.
Student/teacher workflows: classes, assignments, library, dashboard.
Mobile responsive. SQLite (better-sqlite3).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-04-12 10:10:37 +03:00
commit be4d43105e
204 changed files with 118117 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
const { DatabaseSync } = require('node:sqlite');
const path = require('path');
const fs = require('fs');
// Resolve DB_PATH: if absolute — use as-is; if relative — resolve from backend/ dir;
// fallback to __dirname-relative default so CWD never matters.
const _rawPath = process.env.DB_PATH;
const _backendDir = path.join(__dirname, '../../');
const dbPath = _rawPath
? (path.isAbsolute(_rawPath) ? _rawPath : path.resolve(_backendDir, _rawPath))
: path.join(__dirname, '../../data/learnspace.db');
const dbDir = path.dirname(dbPath);
if (!fs.existsSync(dbDir)) fs.mkdirSync(dbDir, { recursive: true });
// Auto-migrate from old location (backend/learnspace.db → backend/data/learnspace.db)
const oldPath = path.join(__dirname, '../../learnspace.db');
if (!fs.existsSync(dbPath) && fs.existsSync(oldPath)) {
fs.copyFileSync(oldPath, dbPath);
// Also copy WAL/SHM if present
if (fs.existsSync(oldPath + '-wal')) fs.copyFileSync(oldPath + '-wal', dbPath + '-wal');
if (fs.existsSync(oldPath + '-shm')) fs.copyFileSync(oldPath + '-shm', dbPath + '-shm');
console.log('[db] Migrated database from', oldPath, '→', dbPath);
}
const db = new DatabaseSync(dbPath);
db.exec('PRAGMA journal_mode = WAL');
db.exec('PRAGMA foreign_keys = ON');
/**
* Run a synchronous function inside a BEGIN/COMMIT/ROLLBACK transaction.
* Returns the value returned by fn(), or rethrows on error.
*/
db.transaction = function transaction(fn) {
return (...args) => {
db.exec('BEGIN');
try {
const result = fn(...args);
db.exec('COMMIT');
return result;
} catch (err) {
db.exec('ROLLBACK');
throw err;
}
};
};
module.exports = db;
File diff suppressed because it is too large Load Diff
+102
View File
@@ -0,0 +1,102 @@
-- =============================================
-- LearnSpace — Initial schema
-- =============================================
-- Расширения
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- ── Пользователи ──────────────────────────────
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
role VARCHAR(20) NOT NULL DEFAULT 'student'
CHECK (role IN ('student', 'teacher', 'admin')),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_login TIMESTAMPTZ
);
-- ── Предметы ──────────────────────────────────
CREATE TABLE IF NOT EXISTS subjects (
id SERIAL PRIMARY KEY,
slug VARCHAR(50) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL,
icon VARCHAR(10)
);
INSERT INTO subjects (slug, name, icon) VALUES
('bio', 'Биология', 'dna'),
('chem', 'Химия', 'atom'),
('math', 'Математика', 'compass'),
('phys', 'Физика', 'zap')
ON CONFLICT DO NOTHING;
-- ── Темы ──────────────────────────────────────
CREATE TABLE IF NOT EXISTS topics (
id SERIAL PRIMARY KEY,
subject_id INT NOT NULL REFERENCES subjects(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
order_index INT NOT NULL DEFAULT 0
);
-- ── Банк вопросов ─────────────────────────────
CREATE TABLE IF NOT EXISTS questions (
id SERIAL PRIMARY KEY,
subject_id INT NOT NULL REFERENCES subjects(id) ON DELETE CASCADE,
topic_id INT REFERENCES topics(id) ON DELETE SET NULL,
text TEXT NOT NULL,
difficulty SMALLINT NOT NULL DEFAULT 1 CHECK (difficulty BETWEEN 1 AND 3),
year SMALLINT,
explanation TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ── Варианты ответов ──────────────────────────
CREATE TABLE IF NOT EXISTS options (
id SERIAL PRIMARY KEY,
question_id INT NOT NULL REFERENCES questions(id) ON DELETE CASCADE,
text TEXT NOT NULL,
is_correct BOOLEAN NOT NULL DEFAULT FALSE,
order_index SMALLINT NOT NULL DEFAULT 0
);
-- ── Сессии тестирования ───────────────────────
CREATE TABLE IF NOT EXISTS test_sessions (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
subject_id INT REFERENCES subjects(id) ON DELETE SET NULL,
mode VARCHAR(20) NOT NULL DEFAULT 'exam'
CHECK (mode IN ('exam', 'practice', 'topic', 'random')),
total INT NOT NULL,
score INT,
status VARCHAR(20) NOT NULL DEFAULT 'in_progress'
CHECK (status IN ('in_progress', 'completed', 'abandoned')),
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
finished_at TIMESTAMPTZ
);
-- ── Вопросы сессии ────────────────────────────
CREATE TABLE IF NOT EXISTS session_questions (
id SERIAL PRIMARY KEY,
session_id INT NOT NULL REFERENCES test_sessions(id) ON DELETE CASCADE,
question_id INT NOT NULL REFERENCES questions(id),
order_index INT NOT NULL
);
-- ── Ответы пользователя ───────────────────────
CREATE TABLE IF NOT EXISTS user_answers (
id SERIAL PRIMARY KEY,
session_id INT NOT NULL REFERENCES test_sessions(id) ON DELETE CASCADE,
question_id INT NOT NULL REFERENCES questions(id),
chosen_option_id INT REFERENCES options(id),
is_correct BOOLEAN,
time_spent_sec SMALLINT,
answered_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ── Индексы ───────────────────────────────────
CREATE INDEX IF NOT EXISTS idx_questions_subject ON questions(subject_id);
CREATE INDEX IF NOT EXISTS idx_questions_topic ON questions(topic_id);
CREATE INDEX IF NOT EXISTS idx_sessions_user ON test_sessions(user_id);
CREATE INDEX IF NOT EXISTS idx_answers_session ON user_answers(session_id);
@@ -0,0 +1,3 @@
-- Уникальный индекс для upsert ответов
CREATE UNIQUE INDEX IF NOT EXISTS idx_answers_session_question
ON user_answers (session_id, question_id);
File diff suppressed because it is too large Load Diff
+667
View File
@@ -0,0 +1,667 @@
/**
* Seed: Математика — вопросы для тестов
*
* Источники:
* — 21 вопрос ЦТ/ЦЭ (общие)
* — 32 вопроса ЦТ 2021, Вариант 1 (A1A18 + B1B14)
*
* Запуск: node src/db/seed-math.js (из папки backend/)
*/
const db = require('./db');
/* ── helpers ─────────────────────────────────────────────────────────── */
function getOrCreateTopic(subjectId, name) {
const ex = db.prepare('SELECT id FROM topics WHERE subject_id=? AND LOWER(name)=LOWER(?)').get(subjectId, name);
if (ex) return ex.id;
return db.prepare('INSERT INTO topics (subject_id, name) VALUES (?,?)').run(subjectId, name).lastInsertRowid;
}
/* ── subject ─────────────────────────────────────────────────────────── */
const math = db.prepare("SELECT id FROM subjects WHERE slug='math'").get();
if (!math) { console.error('Subject "math" not found. Run migrate first.'); process.exit(1); }
const SID = math.id;
/* ═══════════════════════════════════════════════════════════════════════
Вопросы
Поля: topic, difficulty, text, options[{text,correct}], explanation
Необязательные: year (число), type ('single'|'multiple')
═══════════════════════════════════════════════════════════════════════ */
const questions = [
/* ══════════════════════════════════════════════════════════════════════
РАЗДЕЛ 1 — Общие задания ЦТ/ЦЭ
══════════════════════════════════════════════════════════════════════ */
{
topic: 'Арифметика и степени', difficulty: 1,
text: 'Вычислите: \\((-3)^2 - 2^3 + 1\\)',
options: [
{ text: '\\(2\\)', correct: true },
{ text: '\\(4\\)', correct: false },
{ text: '\\(0\\)', correct: false },
{ text: '\\(-2\\)', correct: false },
{ text: '\\(18\\)', correct: false },
],
explanation: '\\((-3)^2=9,\\;2^3=8.\\;9-8+1=2\\)',
},
{
topic: 'Словесные задачи', difficulty: 1,
text: 'Толя купил 3 альбома и 5 карандашей. Стоимость альбома — 1 р. 20 к., карандаша — 25 к. Сколько копеек осталось у Толи после покупки, если всего у него было 6 р.?',
options: [
{ text: '115 к.', correct: true },
{ text: '145 к.', correct: false },
{ text: '110 к.', correct: false },
{ text: '125 к.', correct: false },
{ text: '275 к.', correct: false },
],
explanation: '3·120+5·25=360+125=485 к. Остаток: 600485=115 к.',
},
{
topic: 'Теория чисел', difficulty: 1,
text: 'Укажите формулу для нахождения делимого \\(n\\), если делитель равен 15, неполное частное \\(k\\), остаток 7:',
options: [
{ text: '\\(n=15k+7\\)', correct: true },
{ text: '\\(n=15(k+7)\\)', correct: false },
{ text: '\\(n=k+22\\)', correct: false },
{ text: '\\(n=7k+15\\)', correct: false },
],
explanation: 'По теореме о делении с остатком: \\(n=d\\cdot q+r=15k+7\\)',
},
{
topic: 'Тригонометрия', difficulty: 1,
text: 'Среди значений \\(-\\dfrac{\\pi}{6};\\;\\dfrac{\\pi}{4};\\;\\dfrac{\\pi}{3};\\;-\\dfrac{3\\pi}{2};\\;-6\\pi\\) укажите то, при котором \\(\\sin x=0\\):',
options: [
{ text: '\\(-6\\pi\\)', correct: true },
{ text: '\\(-\\dfrac{\\pi}{6}\\)', correct: false },
{ text: '\\(\\dfrac{\\pi}{4}\\)', correct: false },
{ text: '\\(-\\dfrac{3\\pi}{2}\\)', correct: false },
],
explanation: '\\(\\sin(-6\\pi)=0\\), так как \\(-6\\pi\\) кратно \\(\\pi\\).',
},
{
topic: 'Квадратные уравнения', difficulty: 2,
text: 'Укажите квадратное уравнение, произведение действительных корней которого равно 5:',
options: [
{ text: '\\(x^2-6x+5=0\\)', correct: true },
{ text: '\\(x^2-4x+5=0\\)', correct: false },
{ text: '\\(x^2-5x+6=0\\)', correct: false },
{ text: '\\(x^2+5x=0\\)', correct: false },
],
explanation: 'По формуле Виета произведение корней \\(=c/a\\). Для \\(x^2-6x+5=0\\): \\(5/1=5\\). Корни: \\(x=1,\\;x=5\\).',
},
{
topic: 'Тригонометрия', difficulty: 2,
text: 'Найдите значение выражения \\(\\dfrac{38}{\\pi}\\cdot\\arcsin(-1)-|-7|\\)',
options: [
{ text: '\\(-26\\)', correct: true },
{ text: '\\(-16\\)', correct: false },
{ text: '\\(-12\\)', correct: false },
{ text: '\\(26\\)', correct: false },
],
explanation: '\\(\\arcsin(-1)=-\\dfrac{\\pi}{2}\\). Тогда: \\(\\dfrac{38}{\\pi}\\cdot(-\\dfrac{\\pi}{2})-7=-19-7=-26\\).',
},
{
topic: 'Квадратные уравнения', difficulty: 1,
text: 'Найдите нули функции \\(f(x)=x^2+4x-5\\):',
options: [
{ text: '\\(x=-5\\) и \\(x=1\\)', correct: true },
{ text: '\\(x=5\\) и \\(x=-1\\)', correct: false },
{ text: '\\(x=5\\) и \\(x=1\\)', correct: false },
{ text: '\\(x=-5\\) и \\(x=-1\\)', correct: false },
],
explanation: 'Дискриминант: \\(16+20=36\\). \\(x_{1,2}=\\dfrac{-4\\pm6}{2}\\). \\(x_1=-5,\\;x_2=1\\).',
},
{
topic: 'Тригонометрия', difficulty: 2,
text: 'Найдите \\(\\operatorname{ctg}^2\\alpha\\), если \\(\\sin\\alpha=\\dfrac{1}{5}\\):',
options: [
{ text: '\\(24\\)', correct: true },
{ text: '\\(\\frac{1}{24}\\)', correct: false },
{ text: '\\(4\\)', correct: false },
{ text: '\\(25\\)', correct: false },
],
explanation: '\\(\\cos^2\\alpha=1-\\tfrac{1}{25}=\\tfrac{24}{25}\\). \\(\\operatorname{ctg}^2\\alpha=\\dfrac{24/25}{1/25}=24\\).',
},
{
topic: 'Прогрессии', difficulty: 2,
text: 'Пятый член геометрической прогрессии равен 48, шестой — 96. Найдите первый член:',
options: [
{ text: '\\(3\\)', correct: true },
{ text: '\\(1\\)', correct: false },
{ text: '\\(2\\)', correct: false },
{ text: '\\(4\\)', correct: false },
],
explanation: '\\(q=96/48=2\\). \\(b_1=48/2^4=3\\).',
},
{
topic: 'Неравенства', difficulty: 2,
text: 'Сколько целых решений имеет неравенство \\(-3\\le2-\\dfrac{3x-2}{2}<27\\,\\)?',
options: [
{ text: '\\(20\\)', correct: true },
{ text: '\\(18\\)', correct: false },
{ text: '\\(19\\)', correct: false },
{ text: '\\(21\\)', correct: false },
],
explanation: 'Решение: \\(-15\\le x\\le4\\). Целые: от 15 до 4 → \\(4-(-15)+1=20\\) чисел.',
},
{
topic: 'Квадратные уравнения', difficulty: 1,
text: 'Функция \\(f(x)=x^2+4x-5\\). Найдите сумму нулей функции.',
options: [
{ text: '\\(-4\\)', correct: true },
{ text: '\\(4\\)', correct: false },
{ text: '\\(-5\\)', correct: false },
{ text: '\\(0\\)', correct: false },
],
explanation: 'По теореме Виета: \\(-b/a=-4\\).',
},
{
topic: 'Прогрессии', difficulty: 2,
text: 'Найдите сумму всех натуральных чисел, кратных 9, которые больше 141, но меньше 170.',
options: [
{ text: '\\(459\\)', correct: true },
{ text: '\\(468\\)', correct: false },
{ text: '\\(315\\)', correct: false },
{ text: '\\(414\\)', correct: false },
],
explanation: 'Числа: 144, 153, 162. Сумма: \\(144+153+162=459\\).',
},
{
topic: 'Геометрия', difficulty: 2,
text: 'Радиус описанной окружности прямоугольного треугольника \\(ABC\\) (\\(\\angle ABC=90°\\)) равен \\(18\\sqrt{2}\\). Найдите \\(90\\cdot\\cos\\angle ACB\\), если \\(BC=6\\sqrt{2}\\).',
options: [
{ text: '\\(15\\)', correct: true },
{ text: '\\(30\\)', correct: false },
{ text: '\\(45\\)', correct: false },
{ text: '\\(60\\)', correct: false },
],
explanation: '\\(AC=2R=36\\sqrt{2}\\). \\(\\cos\\angle ACB=\\dfrac{6\\sqrt{2}}{36\\sqrt{2}}=\\dfrac{1}{6}\\). Ответ: \\(15\\).',
},
{
topic: 'Словесные задачи', difficulty: 2,
text: 'Проездной на месяц стоит 39 р., разовый — 80 к. 75% потраченной Машей суммы равны стоимости проездного. Сколько поездок она совершила?',
options: [
{ text: '65', correct: true },
{ text: '52', correct: false },
{ text: '75', correct: false },
{ text: '60', correct: false },
],
explanation: '75% суммы=39 р. → сумма=52 р.=5200 к. Поездок: \\(5200/80=65\\).',
},
{
topic: 'Неравенства', difficulty: 2,
text: 'Найдите сумму наименьшего и наибольшего целых решений неравенства \\(-3\\le2-\\dfrac{3x-2}{2}<27\\).',
options: [
{ text: '\\(-11\\)', correct: true },
{ text: '\\(-10\\)', correct: false },
{ text: '\\(-12\\)', correct: false },
{ text: '\\(-14\\)', correct: false },
],
explanation: 'Целые решения: от −15 до 4. Сумма крайних: \\(-15+4=-11\\).',
},
{
topic: 'Функции', difficulty: 2,
text: 'Функция \\(y=f(x)\\) чётная. Точки \\(A(3;-\\tfrac{2}{3})\\) и \\(B(6;-\\tfrac{3}{4})\\) на графике. Найдите \\(6f(-3)+8f(-6)\\).',
options: [
{ text: '\\(-10\\)', correct: true },
{ text: '\\(10\\)', correct: false },
{ text: '\\(-6\\)', correct: false },
{ text: '\\(-4\\)', correct: false },
],
explanation: '\\(f(-3)=f(3)=-\\tfrac{2}{3}\\), \\(f(-6)=-\\tfrac{3}{4}\\). \\(6\\cdot(-\\tfrac{2}{3})+8\\cdot(-\\tfrac{3}{4})=-4-6=-10\\).',
},
{
topic: 'Логарифмы', difficulty: 3,
text: 'Найдите произведение корней уравнения \\(\\log_2^2x-2\\log_2x=\\log_224-\\log_23\\), умноженное на 11.',
options: [
{ text: '\\(44\\)', correct: true },
{ text: '\\(22\\)', correct: false },
{ text: '\\(88\\)', correct: false },
{ text: '\\(48\\)', correct: false },
],
explanation: 'Пусть \\(t=\\log_2x\\): \\(t^2-2t=3\\Rightarrow t=3\\) или \\(t=-1\\). Корни: \\(x=8,\\;x=\\tfrac{1}{2}\\). Произведение \\(=4\\). \\(4\\times11=44\\).',
},
{
topic: 'Геометрия', difficulty: 3,
text: 'Плоскость, параллельная основанию треугольной пирамиды, делит высоту в отношении \\(5:3\\) от вершины. Найдите площадь сечения, если она меньше площади основания на 39.',
options: [
{ text: '\\(25\\)', correct: true },
{ text: '\\(36\\)', correct: false },
{ text: '\\(16\\)', correct: false },
{ text: '\\(49\\)', correct: false },
],
explanation: '\\(k=\\tfrac{5}{8}\\). \\(S_{\\text{осн}}-\\tfrac{25}{64}S_{\\text{осн}}=39\\Rightarrow S_{\\text{осн}}=64\\). \\(S_{\\text{сеч}}=25\\).',
},
{
topic: 'Показательные неравенства', difficulty: 3,
text: 'Найдите наименьшее целое решение неравенства \\(8^{2x-32}+10\\cdot4^{3x-49}>56\\).',
options: [
{ text: '\\(17\\)', correct: true },
{ text: '\\(16\\)', correct: false },
{ text: '\\(18\\)', correct: false },
{ text: '\\(15\\)', correct: false },
],
explanation: 'Пусть \\(t=2^{6x-98}\\): \\(4t+10t>56\\Rightarrow t>4\\Rightarrow x>16{,}\\overline{6}\\). Мин. целое: 17.',
},
{
topic: 'Логарифмы', difficulty: 3,
text: 'Найдите произведение наименьшего и наибольшего целых решений неравенства \\(\\log_3^2(x+12)-\\log_3(x+12)-6<0\\).',
options: [
{ text: '\\(-154\\)', correct: true },
{ text: '\\(154\\)', correct: false },
{ text: '\\(-110\\)', correct: false },
{ text: '\\(-180\\)', correct: false },
],
explanation: 'Пусть \\(t=\\log_3(x+12)\\): \\(-2<t<3\\Rightarrow -11{,}88<x<15\\). Целые: от 11 до 14. \\(-11\\times14=-154\\).',
},
{
topic: 'Неравенства', difficulty: 2,
text: 'Решите неравенство \\(|2x-3|\\le7\\) и укажите число целых решений.',
options: [
{ text: '\\(8\\)', correct: true },
{ text: '\\(7\\)', correct: false },
{ text: '\\(9\\)', correct: false },
{ text: '\\(6\\)', correct: false },
],
explanation: '\\(-7\\le2x-3\\le7\\Rightarrow-2\\le x\\le5\\). Целые: \\(-2,-1,0,1,2,3,4,5\\) — 8 чисел.',
},
/* ══════════════════════════════════════════════════════════════════════
РАЗДЕЛ 2 — ЦТ 2021, Вариант 1
══════════════════════════════════════════════════════════════════════ */
{
topic: 'Геометрия', difficulty: 1, year: 2021,
text: '[ЦТ 2021 · A1]\n\nТреугольник \\(ABC\\) — равнобедренный с основанием \\(AB\\). Угол при вершине \\(C = 56°\\). Найдите угол \\(BAC\\).',
options: [
{ text: '62°', correct: true },
{ text: '68°', correct: false },
{ text: '34°', correct: false },
{ text: '64°', correct: false },
{ text: '28°', correct: false },
],
explanation: '\\(2\\cdot\\angle BAC=180°-56°=124°\\Rightarrow\\angle BAC=62°\\).',
},
{
topic: 'Арифметика и степени', difficulty: 1, year: 2021,
text: '[ЦТ 2021 · A2]\n\nСреди дробей \\(\\dfrac{13}{7};\\;\\dfrac{15}{7};\\;\\dfrac{30}{7};\\;\\dfrac{27}{7};\\;\\dfrac{18}{7}\\) укажите ту, которая равна \\(4\\dfrac{2}{7}\\).',
options: [
{ text: '\\(\\dfrac{13}{7}\\)', correct: false },
{ text: '\\(\\dfrac{15}{7}\\)', correct: false },
{ text: '\\(\\dfrac{30}{7}\\)', correct: true },
{ text: '\\(\\dfrac{27}{7}\\)', correct: false },
{ text: '\\(\\dfrac{18}{7}\\)', correct: false },
],
explanation: '\\(4\\dfrac{2}{7}=\\dfrac{30}{7}\\).',
},
{
topic: 'Уравнения', difficulty: 1, year: 2021,
text: '[ЦТ 2021 · A3]\n\nУкажите пару, которая НЕ является решением \\(x+y=12\\): \\((3;9)\\), \\((-15;3)\\), \\((0;12)\\), \\((14;-2)\\), \\((6;6)\\).',
options: [
{ text: '\\((3;9)\\)', correct: false },
{ text: '\\((-15;3)\\)', correct: true },
{ text: '\\((0;12)\\)', correct: false },
{ text: '\\((14;-2)\\)', correct: false },
{ text: '\\((6;6)\\)', correct: false },
],
explanation: '\\(-15+3=-12\\neq12\\).',
},
{
topic: 'Неравенства', difficulty: 1, year: 2021,
text: '[ЦТ 2021 · A4]\n\nСреди чисел \\(-7;-11;11;-1;0\\) укажите то, которое не меньше \\(-9\\) и не больше \\(-2\\).',
options: [
{ text: '\\(-7\\)', correct: true },
{ text: '\\(-11\\)', correct: false },
{ text: '\\(11\\)', correct: false },
{ text: '\\(-1\\)', correct: false },
{ text: '\\(0\\)', correct: false },
],
explanation: '\\(-9\\le-7\\le-2\\) — только \\(-7\\) подходит.',
},
{
topic: 'Словесные задачи', difficulty: 1, year: 2021,
text: '[ЦТ 2021 · A5]\n\nТочка \\(C\\) делит отрезок \\(AB\\) в отношении \\(5:3\\) от \\(A\\). Длина \\(AB=24\\). Найдите \\(CB\\).',
options: [
{ text: '\\(14{,}4\\)', correct: false },
{ text: '\\(9{,}6\\)', correct: false },
{ text: '\\(6\\)', correct: false },
{ text: '\\(9\\)', correct: true },
{ text: '\\(15\\)', correct: false },
],
explanation: '\\(CB=\\dfrac{3}{8}\\cdot24=9\\).',
},
{
topic: 'Словесные задачи', difficulty: 2, year: 2021,
text: '[ЦТ 2021 · A6]\n\nВ магазин поступило 43 коробки по 110 пачек масла. Какое наименьшее количество пачек нужно продавать ежедневно, чтобы распродать за не более чем 60 дней?',
options: [
{ text: '78', correct: false },
{ text: '81', correct: false },
{ text: '79', correct: true },
{ text: '83', correct: false },
],
explanation: '\\(43\\times110=4730\\). \\(\\lceil4730/60\\rceil=79\\).',
},
{
topic: 'Функции', difficulty: 2, year: 2021,
text: '[ЦТ 2021 · A7]\n\nНайдите количество целых \\(x\\in[-6;6]\\), при которых \\(f(x)\\le-3\\) (по графику функции).',
options: [
{ text: '7', correct: true },
{ text: '6', correct: false },
{ text: '5', correct: false },
{ text: '9', correct: false },
],
explanation: 'По графику: 7 целых значений.',
},
{
topic: 'Функции', difficulty: 2, year: 2021,
text: '[ЦТ 2021 · A8]\n\nУпростите \\(|a-6|-|a|\\) при \\(\\dfrac{1}{6}<a<\\dfrac{3}{8}\\).',
options: [
{ text: '\\(-6\\)', correct: false },
{ text: '\\(2a+6\\)', correct: false },
{ text: '\\(-2a-6\\)', correct: false },
{ text: '\\(6-2a\\)', correct: true },
],
explanation: '\\(|a-6|=6-a\\) и \\(|a|=a\\). Результат: \\(6-a-a=6-2a\\).',
},
{
topic: 'Логарифмы', difficulty: 2, year: 2021,
text: '[ЦТ 2021 · A9]\n\nЧему равно \\(\\log_798-\\log_78+\\log_7\\dfrac{4}{7}\\)?',
options: [
{ text: '\\(1\\)', correct: true },
{ text: '\\(2\\)', correct: false },
{ text: '\\(\\log_72\\)', correct: false },
{ text: '\\(0\\)', correct: false },
],
explanation: '\\(\\log_7\\dfrac{98\\cdot4}{8\\cdot7}=\\log_77=1\\).',
},
{
topic: 'Словесные задачи', difficulty: 1, year: 2021,
text: '[ЦТ 2021 · A10]\n\nВелосипедист в первый день проехал 52 км, во второй — на 15% меньше. Сколько км за два дня?',
options: [
{ text: '102,4', correct: false },
{ text: '96,2', correct: true },
{ text: '89', correct: false },
{ text: '88,4', correct: false },
],
explanation: '\\(52\\times0{,}85=44{,}2\\). Итого: \\(52+44{,}2=96{,}2\\).',
},
{
topic: 'Уравнения', difficulty: 1, year: 2021,
text: '[ЦТ 2021 · A11]\n\nНайдите произведение координат точки пересечения прямых \\(6x-y=4\\) и \\(y-18=0\\).',
options: [
{ text: '4', correct: false },
{ text: '18', correct: false },
{ text: '72', correct: false },
{ text: '66', correct: true },
],
explanation: '\\(y=18\\Rightarrow6x=22\\Rightarrow x=\\tfrac{11}{3}\\). Произведение: \\(\\tfrac{11}{3}\\cdot18=66\\).',
},
{
topic: 'Функции', difficulty: 2, year: 2021, type: 'multiple',
text: '[ЦТ 2021 · A12]\n\nУкажите номера чётных функций:\n1) \\(y=0{,}2x^2\\)\n2) \\(y=8^{\\tfrac{x^4-16}{2|x|}}\\)\n3) \\(y=-\\dfrac{3}{x}\\)\n4) \\(y=x^2-x+2\\)\n5) \\(y=\\sin2x\\)',
options: [
{ text: '1', correct: true },
{ text: '2', correct: true },
{ text: '3', correct: false },
{ text: '4', correct: false },
{ text: '5', correct: false },
],
explanation: '1) ✓ чётная. 2) ✓ показатель не меняется при \\(x\\to-x\\). 3) нечётная. 4) ни чётная ни нечётная. 5) нечётная.',
},
{
topic: 'Геометрия', difficulty: 2, year: 2021,
text: '[ЦТ 2021 · A13]\n\nПлощадь прямоугольного треугольника равна 2, радиус описанной окружности \\(R\\). Укажите верную формулу суммы катетов \\(a+b\\).\n1) \\(\\dfrac{R^2+4}{R}\\)\n2) \\(\\sqrt{R^2+2}\\)\n3) \\(2\\sqrt{R^2+4}\\)\n4) \\(\\dfrac{R^2+2}{R}\\)\n5) \\(2\\sqrt{R^2+2}\\)',
options: [
{ text: '1', correct: false },
{ text: '2', correct: false },
{ text: '3', correct: false },
{ text: '4', correct: false },
{ text: '5', correct: true },
],
explanation: '\\(c=2R,\\;ab=4\\). \\((a+b)^2=4R^2+8\\Rightarrow a+b=2\\sqrt{R^2+2}\\).',
},
{
topic: 'Геометрия', difficulty: 3, year: 2021,
text: '[ЦТ 2021 · A14]\n\nПрямая треугольная призма \\(ABCA_1B_1C_1\\): \\(\\angle A=20°\\), \\(\\angle C=25°\\), радиус описанной окружности \\(\\sqrt{7}\\). Площадь грани \\(AA_1C_1C=2\\sqrt{35}\\). Найдите длину диагонали этой грани.',
options: [
{ text: '\\(3\\sqrt{3}\\)', correct: false },
{ text: '\\(2\\sqrt{5}\\)', correct: false },
{ text: '\\(2\\sqrt{6}\\)', correct: true },
{ text: '\\(4\\sqrt{6}\\)', correct: false },
],
explanation: '\\(\\angle B=135°\\). \\(AC=\\sqrt{14}\\). \\(AA_1=\\sqrt{10}\\). Диагональ: \\(\\sqrt{14+10}=2\\sqrt{6}\\).',
},
{
topic: 'Квадратные уравнения', difficulty: 2, year: 2021,
text: '[ЦТ 2021 · A15]\n\nПараболы \\(y=2x^2+bx+c\\) с нулями \\(x=3\\) и \\(x=4\\). Найдите \\(b+c\\).',
options: [
{ text: '12', correct: false },
{ text: '5', correct: false },
{ text: '10', correct: true },
{ text: '14', correct: false },
],
explanation: '\\(b=-14,\\;c=24\\). \\(b+c=10\\).',
},
{
topic: 'Уравнения', difficulty: 2, year: 2021, type: 'multiple',
text: '[ЦТ 2021 · A16]\n\nУкажите номера равносильных уравнений:\n1) \\((x-6)(x+6)=0\\)\n2) \\(\\sqrt{x+10}=2\\)\n3) \\(x^2+36=0\\)\n4) \\(\\dfrac{x-x^2-5}{4}+\\dfrac{x^2-x-3}{3}=\\dfrac{1}{4}\\)\n5) \\(|x|-6=0\\)',
options: [
{ text: '1', correct: true },
{ text: '2', correct: false },
{ text: '3', correct: false },
{ text: '4', correct: false },
{ text: '5', correct: true },
],
explanation: 'Ур. 1 и 5 имеют одинаковые решения \\(x=\\pm6\\).',
},
{
topic: 'Геометрия', difficulty: 2, year: 2021,
text: '[ЦТ 2021 · A17]\n\nТочки \\(A\\) и \\(B\\) — соседние вершины квадрата; \\(\\Delta x=7\\), \\(\\Delta y=2\\). Найдите площадь квадрата \\(ABCD\\).',
options: [
{ text: '37', correct: false },
{ text: '14', correct: false },
{ text: '50', correct: false },
{ text: '53', correct: true },
],
explanation: '\\(AB^2=7^2+2^2=53\\). Площадь \\(=AB^2=53\\).',
},
{
topic: 'Геометрия', difficulty: 3, year: 2021,
text: '[ЦТ 2021 · A18]\n\nПравильная четырёхугольная пирамида \\(SABCD\\), все рёбра 48. \\(M\\) — середина \\(SD\\), \\(N\\in SC\\), \\(CN:NS=1:3\\). Плоскость через \\(M,N\\), параллельная \\(SA\\), пересекает основание по отрезку длиной…',
options: [
{ text: '\\(16\\sqrt{13}\\)', correct: false },
{ text: '\\(16\\sqrt{10}\\)', correct: true },
{ text: '\\(8\\sqrt{37}\\)', correct: false },
{ text: '\\(12\\sqrt{17}\\)', correct: false },
],
explanation: 'Ответ: \\(16\\sqrt{10}\\).',
},
{
topic: 'Статистика и диаграммы', difficulty: 1, year: 2021,
text: '[ЦТ 2021 · B1]\n\nПо диаграмме посещений сайта (вт≈400, ср≈440, чт≈260, пт≈280, сб≈640, вс≈650) установите соответствие:\n\nА) В какой день было на 20 посещений больше предыдущего?\nБ) В какой день — на 35% меньше вторника?\nВ) В какой день — на 10% больше предыдущего?',
options: [
{ text: 'А4Б3В2', correct: true },
{ text: 'А2Б3В4', correct: false },
{ text: 'А4Б2В3', correct: false },
{ text: 'А3Б4В2', correct: false },
],
explanation: 'А) пятница(+20 от чт)→4; Б) 400×0,65=260=чт→3; В) 400×1,1=440=ср→2. Ответ: А4Б3В2.',
},
{
topic: 'Тригонометрия', difficulty: 3, year: 2021, type: 'multiple',
text: '[ЦТ 2021 · B2]\n\nВыберите три верных утверждения:\n1) если \\(\\cos(\\arccos a)=\\cos(\\arccos\\tfrac{1}{18})\\), то \\(a=\\tfrac{1}{18}\\)\n2) если \\(\\cos a=-\\cos\\tfrac{\\pi}{18}\\), то \\(\\arccos(\\cos a)=-\\tfrac{\\pi}{18}\\)\n3) если \\(\\sin a=\\sin\\tfrac{17\\pi}{18}\\), то \\(\\arcsin(\\sin a)=\\tfrac{17\\pi}{18}\\)\n4) если \\(\\arccos a=\\tfrac{\\pi}{18}\\), то \\(a=\\cos\\tfrac{\\pi}{18}\\)\n5) если \\(\\sin a=\\sin\\tfrac{\\pi}{18}\\), то \\(a=-\\tfrac{\\pi}{18}\\)\n6) если \\(a=\\tfrac{\\pi}{18}\\) и \\(\\sin a=\\sin\\tfrac{\\pi}{18}\\), то \\(\\arcsin(\\sin a)=\\tfrac{\\pi}{18}\\)',
options: [
{ text: '1', correct: true },
{ text: '2', correct: false },
{ text: '3', correct: false },
{ text: '4', correct: true },
{ text: '5', correct: false },
{ text: '6', correct: true },
],
explanation: '1) ✓, 4) ✓ по определению arccos, 6) ✓ т.к. \\(\\tfrac{\\pi}{18}\\in[-\\tfrac{\\pi}{2};\\tfrac{\\pi}{2}]\\).',
},
{
topic: 'Геометрия', difficulty: 3, year: 2021, type: 'multiple',
text: '[ЦТ 2021 · B3]\n\nПлоскости \\(\\alpha\\perp\\beta\\), пересекаются по \\(a\\), точка \\(A\\in\\beta\\). Выберите три верных утверждения:\n1) любая прямая через A, пересекающая α, пересекает a\n2) единственная прямая через A ⊥ α\n3) прямая через A ⊥ β тоже ⊥ α\n4) любая точка a ∈ α и β\n5) любая прямая в α ⊥ a тоже ⊥ β\n6) любая прямая ⊥ a принадлежит β',
options: [
{ text: '1', correct: true },
{ text: '2', correct: false },
{ text: '3', correct: false },
{ text: '4', correct: true },
{ text: '5', correct: true },
{ text: '6', correct: false },
],
explanation: '1) ✓, 4) ✓ — по определению линии пересечения, 5) ✓ — признак перпендикулярности плоскостей.',
},
{
topic: 'Словесные задачи', difficulty: 2, year: 2021,
text: '[ЦТ 2021 · B4]\n\nНа пастбище (квадрат) загон для скота занимает \\(\\tfrac{1}{32}\\) площади пастбища. Найдите площадь загона (в м²), если по рисунку ширина пастбища связана с размером загона.',
options: [
{ text: '800', correct: true },
{ text: '640', correct: false },
{ text: '900', correct: false },
{ text: '1000', correct: false },
],
explanation: '\\(S_{\\text{паст}}=32\\cdot S_{\\text{загон}}\\). При \\(a^2=800\\): площадь загона = 800 м².',
},
{
topic: 'Арифметика и степени', difficulty: 2, year: 2021,
text: '[ЦТ 2021 · B5]\n\nНайдите \\(\\sqrt{8}\\cdot\\sqrt[3]{-7}\\cdot\\sqrt{32}\\cdot\\sqrt[3]{49}-7\\cdot\\dfrac{\\sqrt[3]{64}}{-2}\\).',
options: [
{ text: '\\(-98\\)', correct: true },
{ text: '\\(-126\\)', correct: false },
{ text: '\\(-84\\)', correct: false },
{ text: '\\(-70\\)', correct: false },
],
explanation: '\\(\\sqrt{8}\\cdot\\sqrt{32}=16\\). \\(\\sqrt[3]{-7\\cdot49}=-7\\). Первое: \\(-112\\). Второе: \\(7\\cdot4/(-2)=-14\\). \\(-112-(-14)=-98\\).',
},
{
topic: 'Геометрия', difficulty: 2, year: 2021,
text: '[ЦТ 2021 · B6]\n\nПлощадь боковой поверхности цилиндра \\(=15\\pi\\), радиус на 3,5 больше высоты. Найдите \\(\\dfrac{6V}{\\pi}\\).',
options: [
{ text: '225', correct: true },
{ text: '150', correct: false },
{ text: '300', correct: false },
{ text: '180', correct: false },
],
explanation: '\\(rh=7{,}5,\\;r=h+3{,}5\\Rightarrow h=1{,}5,\\;r=5\\). \\(V=37{,}5\\pi\\). \\(6V/\\pi=225\\).',
},
{
topic: 'Тригонометрия', difficulty: 3, year: 2021,
text: '[ЦТ 2021 · B7]\n\nРешите \\(\\sqrt{3}\\cos\\!\\left(\\dfrac{5\\pi}{18}+\\pi x\\right)=-1{,}5\\). Найдите: 3 × наибольший корень на [3;9] × количество корней на [3;9].',
options: [
{ text: '160', correct: true },
{ text: '120', correct: false },
{ text: '200', correct: false },
{ text: '90', correct: false },
],
explanation: 'На [3;9]: 6 корней, \\(x_{\\max}=\\tfrac{80}{9}\\). Ответ: \\(3\\cdot\\tfrac{80}{9}\\cdot6=160\\).',
},
{
topic: 'Логарифмы', difficulty: 3, year: 2021,
text: '[ЦТ 2021 · B8]\n\nНайдите сумму всех целых решений \\(\\log_{0{,}3}\\log_{4{,}7}(2^{x+9{,}1}-1)\\ge0\\).',
options: [
{ text: '\\(-15\\)', correct: true },
{ text: '\\(-10\\)', correct: false },
{ text: '\\(-21\\)', correct: false },
{ text: '\\(-6\\)', correct: false },
],
explanation: 'Целые решения: \\(x=-8\\) и \\(x=-7\\). Сумма: \\(-15\\).',
},
{
topic: 'Геометрия', difficulty: 3, year: 2021,
text: '[ЦТ 2021 · B9]\n\n\\(AC\\) — общая гипотенуза прямоугольных треугольников \\(ABC\\) и \\(ADC\\), плоскости ⊥. \\(AB=9\\sqrt{3}\\), \\(BC=9\\sqrt{5}\\), \\(AD=DC\\). Найдите \\(BD^2\\).',
options: [
{ text: '324', correct: true },
{ text: '256', correct: false },
{ text: '400', correct: false },
{ text: '289', correct: false },
],
explanation: '\\(AC=18\\sqrt{2}\\), \\(AD=18\\). \\(BD^2=324\\).',
},
{
topic: 'Прогрессии', difficulty: 2, year: 2021,
text: '[ЦТ 2021 · B10]\n\n\\(a_n=2n^2-15n\\). Найдите наименьший член \\(a_m\\) и его номер \\(m\\). Запишите \\(m\\cdot a_m\\).',
options: [
{ text: '\\(-112\\)', correct: true },
{ text: '\\(-98\\)', correct: false },
{ text: '\\(-128\\)', correct: false },
{ text: '\\(-80\\)', correct: false },
],
explanation: '\\(a_4=-28\\) — минимум. \\(4\\times(-28)=-112\\).',
},
{
topic: 'Уравнения', difficulty: 3, year: 2021,
text: '[ЦТ 2021 · B11]\n\nНайдите \\(25\\cdot(x_1^2+x_2^2)\\), где \\(x_1,x_2\\) — корни уравнения \\(10\\sqrt{\\dfrac{x^2}{14+5x-x^2}}-2\\sqrt{\\dfrac{14+5x-x^2}{x^2}}=19\\).',
options: [
{ text: '960', correct: true },
{ text: '800', correct: false },
{ text: '1200', correct: false },
{ text: '750', correct: false },
],
explanation: '\\(t=2\\Rightarrow5x^2-20x-56=0\\). \\(x_1^2+x_2^2=\\tfrac{192}{5}\\). \\(25\\cdot\\tfrac{192}{5}=960\\).',
},
{
topic: 'Геометрия', difficulty: 3, year: 2021,
text: '[ЦТ 2021 · B12]\n\nПрямая через вершину \\(K\\) треугольника \\(KMN\\) делит медиану \\(MA\\) в отношении \\(8:3\\) от \\(M\\) и пересекает \\(MN\\) в точке \\(B\\). \\(S_{KMB}=16\\). Найдите \\(S_{KMN}\\).',
options: [
{ text: '28', correct: true },
{ text: '24', correct: false },
{ text: '32', correct: false },
{ text: '36', correct: false },
],
explanation: '\\(MB:BN=8:3\\Rightarrow MN/MB=11/8\\). \\(S_{KMN}=28\\).',
},
{
topic: 'Теория чисел', difficulty: 3, year: 2021,
text: '[ЦТ 2021 · B13]\n\nПетя записал два различных натуральных числа, сложил, перемножил, вычел меньшее из большего, разделил большее на меньшее. Сумма четырёх результатов = 1521. Найдите сумму всех таких пар.',
options: [
{ text: '460', correct: true },
{ text: '390', correct: false },
{ text: '520', correct: false },
{ text: '410', correct: false },
],
explanation: 'Пары: (108,12) и (338,2). Суммы: 120+340=460.',
},
{
topic: 'Геометрия', difficulty: 3, year: 2021,
text: '[ЦТ 2021 · B14]\n\nПирамида \\(SABCD\\): \\(AO=9\\), \\(OC=16\\), \\(BO=OD=12\\), диагонали ⊥. Вершина \\(S\\) удалена на \\(\\tfrac{61}{7}\\) от каждой стороны основания. Через середину высоты параллельно основанию проведена плоскость. Найдите \\(10\\cdot V_{\\text{большей части}}\\).',
options: [
{ text: '1375', correct: true },
{ text: '1250', correct: false },
{ text: '1500', correct: false },
{ text: '1100', correct: false },
],
explanation: '\\(S_{ABCD}=300\\), \\(h=\\tfrac{11}{7}\\), \\(V=\\tfrac{1100}{7}\\). Большая часть: \\(\\tfrac{1925}{14}\\). \\(10V=1375\\).',
},
];
/* ── Вставка (идемпотентно — пропускать уже существующие) ─────────────── */
let inserted = 0, skipped = 0;
const checkQ = db.prepare('SELECT id FROM questions WHERE subject_id=? AND text=?');
const insertQ = db.prepare(
'INSERT INTO questions (subject_id, topic_id, text, difficulty, year, explanation, type) VALUES (?,?,?,?,?,?,?)'
);
const insertO = db.prepare(
'INSERT INTO options (question_id, text, is_correct, order_index) VALUES (?,?,?,?)'
);
db.exec('BEGIN');
try {
for (const q of questions) {
if (checkQ.get(SID, q.text)) { skipped++; continue; }
const topicId = getOrCreateTopic(SID, q.topic);
const { lastInsertRowid: qid } = insertQ.run(
SID, topicId, q.text, q.difficulty,
q.year || null, q.explanation || null, q.type || 'single'
);
q.options.forEach((o, i) => insertO.run(qid, o.text, o.correct ? 1 : 0, i));
inserted++;
}
db.exec('COMMIT');
console.log(`✓ math: вставлено ${inserted}, пропущено ${skipped} (уже было).`);
} catch (err) {
db.exec('ROLLBACK');
console.error('Ошибка:', err.message);
process.exit(1);
}
File diff suppressed because it is too large Load Diff
+405
View File
@@ -0,0 +1,405 @@
/**
* seed-red-book-extra.js
* Добавляет дополнительные виды, сезонность и пищевые связи.
* Запуск: node src/db/seed-red-book-extra.js (из папки backend/)
* Идемпотентен — не дублирует существующие виды.
*/
require('./migrate');
const db = require('./db');
/* ── helpers ── */
const groupRow = (name) => db.prepare("SELECT id FROM rb_groups WHERE name_ru = ?").get(name);
const habitatRow = (type) => db.prepare("SELECT id FROM rb_habitats WHERE type = ?").get(type);
const insSp = db.prepare(`
INSERT INTO rb_species
(group_id, habitat_id, name_ru, name_be, name_lat, category, by_category,
description, interesting_fact, threats, conservation, where_to_see,
photo_url, model_type, population_trend, biomass_kg, season_active)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
`);
const insReg = db.prepare('INSERT OR IGNORE INTO rb_species_regions (species_id, region_code) VALUES (?,?)');
const insWeb = db.prepare('INSERT OR IGNORE INTO rb_food_web (predator_id, prey_id, strength) VALUES (?,?,?)');
function addSp(data) {
// Guard: skip if already exists by name_ru
const existing = db.prepare("SELECT id FROM rb_species WHERE name_ru = ?").get(data.name_ru);
if (existing) return existing.id;
const gid = groupRow(data.group)?.id;
const hid = habitatRow(data.habitat)?.id || null;
if (!gid) { console.warn(`Group not found: ${data.group}`); return null; }
const id = insSp.run(
gid, hid,
data.name_ru, data.name_be || '', data.name_lat || '',
data.category, data.by_category || 'III',
data.description || '', data.fact || '',
JSON.stringify(data.threats || []),
data.conservation || '', data.where_to_see || '',
data.photo || '', data.model || 'silhouette',
JSON.stringify(data.trend || []),
data.biomass || 0,
JSON.stringify(data.seasons || []),
).lastInsertRowid;
(data.regions || []).forEach(r => insReg.run(id, r));
return id;
}
/* ── existing ids by name ── */
function getId(name) {
const r = db.prepare("SELECT id FROM rb_species WHERE name_ru = ?").get(name);
return r?.id || null;
}
db.exec('BEGIN');
/* ══════════════════════════════════════════════════════════════════════════
НОВЫЕ ВИДЫ — ПТИЦЫ
══════════════════════════════════════════════════════════════════════════ */
const pukhlyak = addSp({
group: 'Птицы', habitat: 'conifer',
name_ru: 'Пухляк (гаичка буроголовая)', name_be: 'Мухаловка шэрая', name_lat: 'Poecile montanus',
category: 'NT', by_category: 'IV',
description: 'Небольшая птица хвойных лесов, запасающая корм в тайниках на зиму. Способна запомнить сотни тайников одновременно. Активна круглый год, не покидает территорию даже в сильные морозы.',
fact: 'Пухляк запасает до 2000 семян за осень, запоминая каждое место с точностью до нескольких сантиметров.',
threats: ['Вырубка ельников', 'Конкуренция с синицами'],
conservation: 'Сохранение разновозрастных хвойных лесов.',
where_to_see: 'Беловежская пуща, Налибокская пуща, Нарочанский НП',
biomass: 0.012, seasons: ['10','11','12','1','2','3'],
regions: ['vitebsk','minsk','grodno'],
});
const lebed = addSp({
group: 'Птицы', habitat: 'river',
name_ru: 'Лебедь-шипун', name_be: 'Лебедзь-шыпун', name_lat: 'Cygnus olor',
category: 'NT', by_category: 'IV',
description: 'Один из крупнейших летающих птиц мира — масса до 14 кг. Агрессивно защищает гнездо от любых нарушителей. Пары создаются на много лет. В Беларуси гнездится на озёрах, прудах и медленных реках.',
fact: 'Лебедь-шипун — самая тяжёлая из всех летающих птиц. Чтобы взлететь, ему нужно пробежать по воде 40–50 м.',
threats: ['Рекреационная нагрузка', 'Свинцовое отравление', 'Беспокойство у гнёзд'],
conservation: 'Охранные зоны у гнёзд. Ограничение водно-моторного спорта.',
where_to_see: 'Браславские озёра, Нарочанский НП, Минское море',
biomass: 10.0, seasons: ['4','5','6','7','8'],
regions: ['vitebsk','minsk','brest','grodno'],
});
const klikun = addSp({
group: 'Птицы', habitat: 'river',
name_ru: 'Лебедь-кликун', name_be: 'Лебедзь-крыкун', name_lat: 'Cygnus cygnus',
category: 'VU', by_category: 'II',
description: 'Перелётный лебедь, зимующий в Беларуси. В отличие от шипуна держит шею прямо. Мощный трубный крик слышен далеко. Небольшая гнездовая популяция в Витебской области значительно выросла за последние 30 лет.',
fact: 'Лебедь-кликун мигрирует на высоте до 8000 м. Зафиксированы особи, долетавшие из Западной Сибири.',
threats: ['Беспокойство', 'Отравление свинцом', 'Недостаток спокойных зимовальных мест'],
conservation: 'Охранные зоны. Запрет охоты.',
where_to_see: 'Браславские озёра (зимовка), Витебская область (гнездование)',
biomass: 9.0, seasons: ['10','11','12','1','2','3'],
regions: ['vitebsk','minsk'],
});
const domovoy_sych = addSp({
group: 'Птицы', habitat: 'meadow',
name_ru: 'Сыч домовый', name_be: 'Хатні сычык', name_lat: 'Athene noctua',
category: 'EN', by_category: 'II',
description: 'Маленькая сова агроландшафтов, живущая в старых постройках, скалах и пнях. Активна в сумерках. В Беларуси редкость — известны единицы гнездовых пар. Численность катастрофически снизилась из-за химизации сельского хозяйства.',
fact: 'Домовый сыч ловит до 600 мышей и 2000 насекомых в год, являясь природным регулятором грызунов.',
threats: ['Применение пестицидов', 'Снижение числа старых построек', 'Кошки'],
conservation: 'Установка гнездовых ящиков. Ограничение пестицидов.',
where_to_see: 'Брестская область (единичные пары)',
biomass: 0.18, seasons: ['3','4','5','6','7','8','9'],
regions: ['brest','gomel'],
});
const velik_kulak = addSp({
group: 'Птицы', habitat: 'wetland',
name_ru: 'Большой кроншнеп', name_be: 'Вялікі кроншнеп', name_lat: 'Numenius arquata',
category: 'EN', by_category: 'II',
description: 'Крупнейший кулик Беларуси с изогнутым книзу клювом. Гнездится на влажных лугах и болотах. Очень пуглив — уже с 500 метров покидает гнездо при появлении людей. Численность неуклонно сокращается.',
fact: 'Большой кроншнеп может зондировать почву на глубину 15 см своим клювом, находя дождевых червей по осязанию.',
threats: ['Ранний сенокос', 'Осушение лугов', 'Хищничество лисиц'],
conservation: 'Отсрочка сенокоса. Сохранение пойменных лугов.',
where_to_see: 'Пойма Припяти, Нарочанский НП',
biomass: 0.7, seasons: ['4','5','6','7'],
regions: ['brest','gomel','minsk','vitebsk'],
});
/* ══════════════════════════════════════════════════════════════════════════
НОВЫЕ ВИДЫ — МЛЕКОПИТАЮЩИЕ
══════════════════════════════════════════════════════════════════════════ */
const zaets = addSp({
group: 'Млекопитающие', habitat: 'forest',
name_ru: 'Заяц-беляк', name_be: 'Заяц-белы', name_lat: 'Lepus timidus',
category: 'NT', by_category: 'IV',
description: 'Самый «белорусский» заяц — зимой его мех белеет, что обеспечивает маскировку на снегу. Предпочитает смешанные леса и опушки. Численность регулярно колеблется с периодом 10–11 лет в зависимости от популяции хищников.',
fact: 'Заяц-беляк может развивать скорость до 70 км/ч и прыгать на 4 м в длину, уходя от преследователей.',
threats: ['Волки', 'Лисы', 'Охота', 'Уменьшение подлеска'],
conservation: 'Регулирование охотничьей нагрузки.',
where_to_see: 'Все крупные леса Беларуси',
biomass: 3.5, seasons: ['11','12','1','2','3'],
regions: ['vitebsk','minsk','grodno','brest','gomel','mogilev'],
});
const lasitsa = addSp({
group: 'Млекопитающие', habitat: 'meadow',
name_ru: 'Ласка обыкновенная', name_be: 'Ласіца звычайная', name_lat: 'Mustela nivalis',
category: 'NT', by_category: 'IV',
description: 'Самый маленький хищник Беларуси весом 70–130 г. Способна следовать за мышами в их норы. Зимой меняет мех на белый. Настоящий «регулятор» популяций полёвок — за год уничтожает до 2000 грызунов.',
fact: 'Ласка может убивать добычу, в 5 раз тяжелее себя — кроликов и крыс.',
threats: ['Яды для грызунов', 'Уничтожение живых изгородей'],
conservation: 'Ограничение использования родентицидов.',
where_to_see: 'Луга и поля по всей Беларуси',
biomass: 0.1, seasons: ['1','2','3','4','5','6','7','8','9','10','11','12'],
regions: ['vitebsk','minsk','grodno','brest','gomel','mogilev'],
});
const night_bat = addSp({
group: 'Млекопитающие', habitat: 'forest',
name_ru: 'Широкоушка европейская', name_be: 'Шырокавухая кажан', name_lat: 'Barbastella barbastellus',
category: 'EN', by_category: 'II',
description: 'Редкая летучая мышь с характерными широкими ушами. Зимует в пещерах и подвалах. Охотится на ночных бабочек, улавливая их вибрации. Одна из наиболее уязвимых рукокрылых Европы.',
fact: 'Широкоушка способна поймать до 2000 насекомых за ночь. Её эхолокация настолько совершенна, что она не задевает паутину.',
threats: ['Утепление зданий', 'Химические обработки леса', 'Беспокойство в зимовочных убежищах'],
conservation: 'Охрана зимовочных убежищ. Вывешивание летних домиков.',
where_to_see: 'Беловежская пуща, старые здания Гродненской области',
biomass: 0.01, seasons: ['4','5','6','7','8','9'],
regions: ['grodno','brest','minsk'],
});
/* ══════════════════════════════════════════════════════════════════════════
НОВЫЕ ВИДЫ — РАСТЕНИЯ
══════════════════════════════════════════════════════════════════════════ */
const kupena = addSp({
group: 'Растения', habitat: 'forest',
name_ru: 'Купена многоцветковая', name_be: 'Купена шматкветкавая', name_lat: 'Polygonatum multiflorum',
category: 'NT', by_category: 'IV',
description: 'Изящное тенелюбивое растение тёмных лесов с белыми колокольчатыми цветами. Плоды — тёмно-синие ягоды — ядовиты для человека, но поедаются птицами. Медленно растёт: возраст крупных особей может достигать 50 лет.',
fact: 'Купена цветёт только один раз в жизни. Название происходит от «купель» — ранее растение применялось в народной медицине.',
threats: ['Сбор на букеты', 'Рекреационная нагрузка', 'Вырубка лесов'],
conservation: 'Запрет сбора. Включение в охраняемые участки.',
where_to_see: 'Беловежская пуща, дубравы Полесья',
biomass: 0.05, seasons: ['4','5','6'],
regions: ['grodno','brest','gomel','minsk'],
});
const kruzhevnitsa = addSp({
group: 'Растения', habitat: 'wetland',
name_ru: 'Альдрованда пузырчатая', name_be: 'Альдрованда бурбалкавая', name_lat: 'Aldrovanda vesiculosa',
category: 'CR', by_category: 'I',
description: 'Единственное в мире водное хищное растение, не имеющее корней. Ловит водных беспозвоночных ловушками, напоминающими венерину мухоловку. Популяции в Беларуси — единственные в центральной Европе. Крайне чувствительна к загрязнению воды.',
fact: 'Альдрованда — самое быстрое хищное растение в воде: захлопывает ловушку за 0.01 секунды.',
threats: ['Загрязнение воды', 'Эвтрофикация', 'Зарастание водоёмов'],
conservation: 'Охрана болотных водоёмов. Восстановление популяций.',
where_to_see: 'Ольманские болота, Брестская область',
biomass: 0.001, seasons: ['6','7','8'],
regions: ['brest'],
});
const korolev_papor = addSp({
group: 'Растения', habitat: 'forest',
name_ru: 'Папоротник-орляк', name_be: 'Арляк звычайны', name_lat: 'Pteridium aquilinum',
category: 'NT', by_category: 'IV',
description: 'Один из древнейших видов флоры Беларуси — существует без изменений 55 миллионов лет. Образует обширные куртины в сосновых лесах. Молодые побеги («скрипухи») ядовиты для скота, но съедобны для людей после варки.',
fact: 'Орляк — один из немногих папоротников, покрывающих более 1% суши Земли. Его корневища уходят на глубину 4 м.',
threats: ['Интенсивные рубки', 'Пожары', 'Иссушение лесов'],
conservation: 'Охрана старых лесов.',
where_to_see: 'Хвойные леса по всей Беларуси',
biomass: 0.3, seasons: ['5','6','7','8','9'],
regions: ['vitebsk','minsk','grodno','brest','gomel','mogilev'],
});
/* ══════════════════════════════════════════════════════════════════════════
НОВЫЕ ВИДЫ — НАСЕКОМЫЕ
══════════════════════════════════════════════════════════════════════════ */
const rosalia = addSp({
group: 'Насекомые', habitat: 'forest',
name_ru: 'Усач альпийский', name_be: 'Вусач альпійскі', name_lat: 'Rosalia alpina',
category: 'EN', by_category: 'II',
description: 'Один из красивейших жуков Европы — голубовато-серый с чёрными пятнами, с усами длиннее тела. Личинка живёт в мёртвой буковой древесине. В Беларуси встречается на границе ареала, в Беловежской пуще.',
fact: 'Усач альпийский — один из символов охраны природы Европы. Его изображение есть на почтовых марках многих стран.',
threats: ['Вырубка старых буков', 'Удаление мёртвой древесины'],
conservation: 'Сохранение старых буковых деревьев и валежника.',
where_to_see: 'Беловежская пуща',
biomass: 0.003, seasons: ['6','7','8'],
regions: ['brest'],
});
const pchela_plothnik = addSp({
group: 'Насекомые', habitat: 'forest',
name_ru: 'Пчела-плотник', name_be: 'Пчала-сталяр', name_lat: 'Xylocopa violacea',
category: 'VU', by_category: 'III',
description: 'Крупнейшая дикая пчела Беларуси — тело до 28 мм с фиолетово-синим блеском крыльев. Не живёт в ульях — самостоятельно прогрызает норы в сухой древесине. Важный опылитель с длинным хоботком для глубоких цветков.',
fact: 'Пчела-плотник способна прогрызть тоннель длиной 30 см в дубовом бревне.',
threats: ['Уборка старой и мёртвой древесины', 'Применение инсектицидов'],
conservation: 'Сохранение старых деревьев. Установка гнездовых брёвен.',
where_to_see: 'Беловежская пуща, старые сады Брестской области',
biomass: 0.001, seasons: ['4','5','6','7','8'],
regions: ['brest','grodno'],
});
const zelenyi_metallik = addSp({
group: 'Насекомые', habitat: 'meadow',
name_ru: 'Бронзовка гладкая', name_be: 'Бронзаўка гладкая', name_lat: 'Protaetia aeruginosa',
category: 'EN', by_category: 'II',
description: 'Жук с изумрудным металлическим блеском. Личинка развивается в дуплах старых дубов, питаясь перегноем. Взрослые особи питаются цветочным нектаром. Исчезает вместе с вековыми дубами.',
fact: 'Бронзовка — «инженер экосистем»: личинки рыхлят перегной в дуплах, создавая субстрат для редких растений и других насекомых.',
threats: ['Вырубка старых дубов', 'Исчезновение дупел', 'Применение инсектицидов'],
conservation: 'Сохранение старовозрастных дубрав.',
where_to_see: 'Беловежская пуща, дубравы Налибокской пущи',
biomass: 0.002, seasons: ['5','6','7'],
regions: ['brest','grodno','minsk'],
});
/* ══════════════════════════════════════════════════════════════════════════
НОВЫЕ ВИДЫ — РЫБЫ
══════════════════════════════════════════════════════════════════════════ */
const som = addSp({
group: 'Рыбы', habitat: 'river',
name_ru: 'Сом обыкновенный', name_be: 'Сом звычайны', name_lat: 'Silurus glanis',
category: 'NT', by_category: 'IV',
description: 'Крупнейшая пресноводная рыба Беларуси — достигает 3 м длины и 200 кг. Ведёт ночной образ жизни. Охотится в основном на рыбу, но не брезгует птицами и млекопитающими. В старых реках встречались особи возрастом 60–80 лет.',
fact: 'Сом может выползать на берег, чтобы охотиться на купающихся уток и голубей — этот феномен наблюдали на р. Биже в Испании.',
threats: ['Браконьерство', 'Ухудшение качества воды', 'Перекрытие миграционных путей'],
conservation: 'Ограничение промысла. Охрана нерестилищ.',
where_to_see: 'Припять, Нёман, Днепр, Западная Двина',
biomass: 30.0, seasons: ['5','6','7','8','9'],
regions: ['brest','gomel','grodno','minsk','vitebsk','mogilev'],
});
const taimen = addSp({
group: 'Рыбы', habitat: 'river',
name_ru: 'Хариус европейский', name_be: 'Харыус еўрапейскі', name_lat: 'Thymallus thymallus',
category: 'VU', by_category: 'III',
description: 'Стремительная рыба холодных чистых рек с высоким, похожим на парус, спинным плавником. Индикатор качества воды — исчезает при малейшем загрязнении. Питается насекомыми, упавшими на поверхность воды.',
fact: 'Хариус умеет «читать» поверхность воды: он точно определяет, куда упадёт насекомое, и занимает позицию заблаговременно.',
threats: ['Загрязнение рек', 'Чрезмерный вылов', 'Мелиорация'],
conservation: 'Охрана чистых рек. Запрет химических обработок водосборов.',
where_to_see: 'Западная Двина (верховья), Вилия, Нёман (верховья)',
biomass: 0.3, seasons: ['4','5','6'],
regions: ['vitebsk','grodno'],
});
/* ══════════════════════════════════════════════════════════════════════════
НОВЫЕ ВИДЫ — РЕПТИЛИИ И АМФИБИИ
══════════════════════════════════════════════════════════════════════════ */
const gadyuka = addSp({
group: 'Рептилии и амфибии', habitat: 'wetland',
name_ru: 'Гадюка обыкновенная', name_be: 'Гадзюка звычайная', name_lat: 'Vipera berus',
category: 'NT', by_category: 'IV',
description: 'Единственная ядовитая змея Беларуси. Укус редко опасен для взрослого здорового человека, но вызывает болезненный отёк. Питается грызунами, лягушками и ящерицами. Незаменима в экосистеме как регулятор численности грызунов.',
fact: 'Гадюка рожает живых детёнышей (до 12 штук), а не откладывает яйца. Молодые змеи с первого дня ядовиты.',
threats: ['Уничтожение людьми', 'Осушение болот', 'Автодороги'],
conservation: 'Просветительская работа. Охрана болотных участков.',
where_to_see: 'Болота и леса по всей Беларуси',
biomass: 0.15, seasons: ['4','5','6','7','8','9'],
regions: ['vitebsk','minsk','grodno','brest','gomel','mogilev'],
});
const listvy_trit = addSp({
group: 'Рептилии и амфибии', habitat: 'wetland',
name_ru: 'Гребенчатый тритон', name_be: 'Грабенчасты трытон', name_lat: 'Triturus cristatus',
category: 'NT', by_category: 'IV',
description: 'Крупнейший тритон Беларуси. В брачный период самец развивает зубчатый гребень вдоль всего тела. Обитает во влажных лесах и болотах. Выделяет ядовитые вещества через кожу для защиты от хищников.',
fact: 'Гребенчатый тритон может прожить 20–25 лет. В воде он дышит через кожу, не нуждаясь в воздухе часами.',
threats: ['Осушение водоёмов', 'Хищники', 'Загрязнение'],
conservation: 'Охрана болотных водоёмов. Создание пруд-убежищ.',
where_to_see: 'Пуща Налибокская, лесные водоёмы Витебской области',
biomass: 0.02, seasons: ['3','4','5','6'],
regions: ['vitebsk','minsk','grodno'],
});
/* ══════════════════════════════════════════════════════════════════════════
НОВЫЕ ВИДЫ — ГРИБЫ
══════════════════════════════════════════════════════════════════════════ */
const hleb_grib = addSp({
group: 'Грибы', habitat: 'forest',
name_ru: 'Ёж гриб (герций)', name_be: 'Гербій жаўтлявы', name_lat: 'Hericium erinaceus',
category: 'EN', by_category: 'II',
description: 'Необычный гриб с длинными белыми иглами, свисающими каскадом с дерева. Живёт на старых дубах и буках. Съедобен и ценится как деликатес. Исчезает вместе со старовозрастными широколиственными лесами.',
fact: 'Гриб-ёж содержит вещества, стимулирующие рост нейронов. Изучается как потенциальное средство при болезни Альцгеймера.',
threats: ['Вырубка старых деревьев', 'Сбор грибниками'],
conservation: 'Сохранение старых дубрав. Запрет сбора.',
where_to_see: 'Беловежская пуща',
biomass: 0.5, seasons: ['8','9','10'],
regions: ['brest'],
});
/* ══════════════════════════════════════════════════════════════════════════
ОБНОВИТЬ СЕЗОННОСТЬ СУЩЕСТВУЮЩИХ ВИДОВ
══════════════════════════════════════════════════════════════════════════ */
const seasonUpdate = db.prepare("UPDATE rb_species SET season_active = ? WHERE name_ru = ?");
const seasonMap = [
['Орлан-белохвост', JSON.stringify(['1','2','3','4','5','6','7','8','9','10','11','12'])],
['Чёрный аист', JSON.stringify(['4','5','6','7','8','9'])],
['Скопа', JSON.stringify(['4','5','6','7','8','9'])],
['Коростель', JSON.stringify(['5','6','7','8'])],
['Серый журавль', JSON.stringify(['3','4','5','6','7','8','9','10'])],
['Филин', JSON.stringify(['1','2','3','4','5','6','7','8','9','10','11','12'])],
['Зубр', JSON.stringify(['1','2','3','4','5','6','7','8','9','10','11','12'])],
['Рысь', JSON.stringify(['1','2','3','4','5','6','7','8','9','10','11','12'])],
['Бурый медведь', JSON.stringify(['4','5','6','7','8','9','10'])],
['Бобёр европейский', JSON.stringify(['1','2','3','4','5','6','7','8','9','10','11','12'])],
['Выдра речная', JSON.stringify(['1','2','3','4','5','6','7','8','9','10','11','12'])],
['Стерлядь', JSON.stringify(['4','5','6','7','8','9'])],
['Болотная черепаха', JSON.stringify(['4','5','6','7','8','9'])],
['Венерин башмачок', JSON.stringify(['5','6'])],
['Водяной орех', JSON.stringify(['6','7','8','9'])],
['Жук-олень', JSON.stringify(['6','7','8'])],
['Махаон', JSON.stringify(['5','6','7','8','9'])],
['Богомол обыкновенный', JSON.stringify(['7','8','9'])],
['Трюфель белый', JSON.stringify(['8','9','10','11'])],
];
seasonMap.forEach(([name, json]) => seasonUpdate.run(json, name));
db.exec('COMMIT');
console.log('✓ Дополнительные виды добавлены');
console.log(` Всего видов: ${db.prepare('SELECT COUNT(*) as n FROM rb_species').get().n}`);
console.log(` Пищевых связей: ${db.prepare('SELECT COUNT(*) as n FROM rb_food_web').get().n}`);
/* ══════════════════════════════════════════════════════════════════════════
ПИЩЕВЫЕ СВЯЗИ ДЛЯ НОВЫХ ВИДОВ
══════════════════════════════════════════════════════════════════════════ */
db.exec('BEGIN');
const newLinks = [
// Лебедь-шипун — водные растения
[lebed, getId('Водяной орех'), 0.4],
// Лебедь-кликун — водные растения
[klikun, getId('Водяной орех'), 0.4],
// Сом — рыба, лягушки
[som, getId('Стерлядь'), 0.3],
[som, getId('Хариус европейский'), 0.3],
// Гадюка — грызуны (опосредованно через тритона)
[gadyuka, getId('Тритон обыкновенный') || listvy_trit, 0.4],
// Гребенчатый тритон — насекомые
[listvy_trit, getId('Богомол обыкновенный'), 0.1],
// Выдра охотится на хариуса и сома
[getId('Выдра речная'), som, 0.2],
[getId('Выдра речная'), taimen, 0.3],
// Орлан на лебедей (редко)
[getId('Орлан-белохвост'), lebed, 0.1],
// Рысь — зайца
[getId('Рысь'), zaets, 0.4],
// Волк — зайца
[getId('Волк обыкновенный'), zaets, 0.3],
// Ласка — полёвки (через вид)
[lasitsa, getId('Коростель'), 0.1],
// Пухляк и семена
[getId('Воробьиный сыч'), pukhlyak, 0.2],
// Широкоушка — насекомые
[night_bat, getId('Махаон'), 0.2],
[night_bat, getId('Богомол обыкновенный'), 0.2],
].filter(([p, q]) => p && q);
newLinks.forEach(([p, q, s]) => {
try { insWeb.run(p, q, s); } catch {}
});
db.exec('COMMIT');
console.log(` Новых пищевых связей: ${newLinks.length}`);
console.log('\n✅ seed-red-book-extra.js завершён!');
+493
View File
@@ -0,0 +1,493 @@
/**
* seed-red-book-phase2.js
* +20 видов: мхи/лишайники, пресноводные, редкие насекомые, птицы болот, ночные хищники
* +10 квестов
* Запуск: node src/db/seed-red-book-phase2.js (из папки backend/)
* Идемпотентен — не дублирует существующее.
*/
require('./migrate');
const db = require('./db');
/* ── helpers ── */
const groupRow = n => db.prepare('SELECT id FROM rb_groups WHERE name_ru = ?').get(n);
const habitatRow = t => db.prepare('SELECT id FROM rb_habitats WHERE type = ?').get(t);
const getId = n => db.prepare('SELECT id FROM rb_species WHERE name_ru = ?').get(n)?.id || null;
const insSp = db.prepare(`
INSERT INTO rb_species
(group_id, habitat_id, name_ru, name_be, name_lat, category, by_category,
description, interesting_fact, threats, conservation, where_to_see,
photo_url, model_type, population_trend, biomass_kg, season_active)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
`);
const insReg = db.prepare('INSERT OR IGNORE INTO rb_species_regions (species_id, region_code) VALUES (?,?)');
const insWeb = db.prepare('INSERT OR IGNORE INTO rb_food_web (predator_id, prey_id, strength) VALUES (?,?,?)');
function addSp(d) {
const existing = db.prepare('SELECT id FROM rb_species WHERE name_ru = ?').get(d.name_ru);
if (existing) return existing.id;
const gid = groupRow(d.group)?.id;
const hid = habitatRow(d.habitat)?.id || null;
if (!gid) { console.warn('Group not found:', d.group); return null; }
const id = insSp.run(
gid, hid,
d.name_ru, d.name_be || '', d.name_lat || '',
d.category, d.by_category || 'III',
d.description || '', d.fact || '',
JSON.stringify(d.threats || []),
d.conservation || '', d.where_to_see || '',
d.photo || '', d.model || 'silhouette',
JSON.stringify(d.trend || []),
d.biomass || 0,
JSON.stringify(d.seasons || []),
).lastInsertRowid;
(d.regions || []).forEach(r => insReg.run(id, r));
return id;
}
/* ════════════════════════════════════════════════════════════════════════
БЛОК 1 — ВИДЫ
════════════════════════════════════════════════════════════════════════ */
db.exec('BEGIN');
/* ── ПТИЦЫ (3) ─────────────────────────────────────────────────────── */
const kamyshevka = addSp({
group: 'Птицы', habitat: 'wetland',
name_ru: 'Вертлявая камышевка', name_be: 'Вярцёжная чаротнiца', name_lat: 'Acrocephalus paludicola',
category: 'CR', by_category: 'I',
description: 'Одна из наиболее угрожаемых перелётных птиц Европы. Гнездится исключительно в низинных болотах с осоково-злаковой растительностью. Беларусь — мировой центр гнездования, здесь обитает до 80 % мировой популяции. Зимует в западной Африке.',
fact: 'Беларусь является главным «домом» вертлявой камышевки — здесь гнездится большинство всех особей планеты.',
threats: ['Осушение болот', 'Зарастание гнездовых угодий', 'Пожары в поймах', 'Потеря зимовок в Африке'],
conservation: 'Охрана низинных болот. Восстановление обводнённых пойм. Мониторинг популяции.',
where_to_see: 'Споровское болото (Брестская обл.), Ельня (Витебская обл.), Дикое болото',
biomass: 0.013, seasons: ['5','6','7','8'],
trend: [{year:1990,count_estimate:8000,source:'IUCN'},{year:2000,count_estimate:4500,source:'BirdLife'},{year:2010,count_estimate:2800,source:'BirdLife'},{year:2024,count_estimate:2100,source:'АПБ'}],
regions: ['vitebsk','grodno','brest','minsk'],
});
const dupen = addSp({
group: 'Птицы', habitat: 'wetland',
name_ru: 'Дупель', name_be: 'Дупель', name_lat: 'Gallinago media',
category: 'EN', by_category: 'II',
description: 'Скрытный кулик заливных лугов и болот. Самцы собираются на токах, где демонстрируют оперение и трясутся всем телом. После спаривания самка одна воспитывает птенцов. Ночной образ жизни затрудняет учёт.',
fact: 'Самцы дупеля токуют по ночам на «арене» — постоянном месте сбора, которое используется десятилетиями.',
threats: ['Осушение пойменных лугов', 'Раннее сенокошение', 'Браконьерство на пролёте'],
conservation: 'Запрет сенокошения до августа на ключевых участках. Сохранение влажных лугов.',
where_to_see: 'Ельня, Освейское озеро, пойма р. Припять',
biomass: 0.18, seasons: ['4','5','6','7','8','9'],
trend: [{year:1995,count_estimate:1200,source:'НООО БПО'},{year:2010,count_estimate:650,source:'НООО БПО'},{year:2024,count_estimate:400,source:'НООО БПО'}],
regions: ['vitebsk','minsk','grodno','brest'],
});
const kobchik = addSp({
group: 'Птицы', habitat: 'meadow',
name_ru: 'Кобчик', name_be: 'Кабчык', name_lat: 'Falco vespertinus',
category: 'VU', by_category: 'III',
description: 'Изящный сокол размером с галку. Самцы — шиферно-серые с рыжей «штанишками», самки рябые. Охотится на крупных насекомых — прямокрылых, стрекоз, жуков. Гнездится колониями в чужих гнёздах — грачей и сорок. Перелётный, зимует в Африке.',
fact: 'Кобчик охотится как ласточка — хватает насекомых прямо в воздухе на бреющем полёте над лугами.',
threats: ['Деградация пойменных лугов', 'Применение пестицидов (уменьшение насекомых)', 'Уничтожение колоний грачей'],
conservation: 'Сохранение лугов. Запрет уничтожения грачиных колоний.',
where_to_see: 'Припятский НП, луга Брестской и Гомельской областей',
biomass: 0.16, seasons: ['5','6','7','8','9'],
trend: [{year:1990,count_estimate:900,source:'БО'},{year:2005,count_estimate:550,source:'БО'},{year:2024,count_estimate:320,source:'АПБ'}],
regions: ['brest','gomel','minsk','vitebsk'],
});
/* ── МЛЕКОПИТАЮЩИЕ (2) ──────────────────────────────────────────────── */
const vyhuhol = addSp({
group: 'Млекопитающие', habitat: 'river',
name_ru: 'Выхухоль русская', name_be: 'Расамаха расійская', name_lat: 'Desmana moschata',
category: 'CR', by_category: 'I',
description: 'Один из древнейших зверьков Земли — живой ископаемый. Обитает в старицах, заводях и медленных протоках рек Припятского бассейна. Почти слепа, ориентируется по обонянию и осязанию через вибриссы на хоботке. Выделяет мускусный секрет. Под угрозой исчезновения в Беларуси.',
fact: 'Выхухоль существует без изменений более 30 миллионов лет — её предки жили рядом с носорогами и мамонтами.',
threats: ['Загрязнение рек', 'Осушение пойм', 'Рыболовные сети', 'Хищники (норка американская)'],
conservation: 'Охрана пойменных угодий р. Припять. Борьба с инвазивной норкой американской. Заповедники.',
where_to_see: 'Пойма р. Припять (Гомельская и Брестская обл.), Припятский НП',
biomass: 0.42, seasons: ['1','2','3','4','5','6','7','8','9','10','11','12'],
trend: [{year:1990,count_estimate:1500,source:'НАН'},{year:2000,count_estimate:700,source:'НАН'},{year:2010,count_estimate:280,source:'НАН'},{year:2024,count_estimate:80,source:'IUCN'}],
regions: ['gomel','brest'],
});
const nochnitza = addSp({
group: 'Млекопитающие', habitat: 'river',
name_ru: 'Ночница прудовая', name_be: 'Начніца сажалкавая', name_lat: 'Myotis dasycneme',
category: 'EN', by_category: 'II',
description: 'Крупная летучая мышь, охотящаяся над открытой водой. Подхватывает насекомых с поверхности прудов и рек с помощью крыльев или хвостовой перепонки. Зимует в подземельях. Образует небольшие колонии в постройках. Чувствительна к беспокойству зимовок.',
fact: 'Ночница прудовая — настоящий рыбак: она «читает» рябь воды ушами, определяя местонахождение добычи под поверхностью.',
threats: ['Уничтожение зимовок', 'Загрязнение водоёмов', 'Ветроэнергетика', 'Применение пестицидов'],
conservation: 'Охрана зимовок (подземелья, форты). Мониторинг колоний. Сохранение прибрежных водоёмов.',
where_to_see: 'Гродненские подземелья, водохранилища Минской обл., Беловежская пуща',
biomass: 0.016, seasons: ['5','6','7','8','9'],
trend: [{year:2000,count_estimate:600,source:'Bat Conservation'},{year:2015,count_estimate:350,source:'БО'},{year:2024,count_estimate:220,source:'БО'}],
regions: ['minsk','grodno','brest','vitebsk'],
});
/* ── РАСТЕНИЯ (2) ───────────────────────────────────────────────────── */
const yatrish = addSp({
group: 'Растения', habitat: 'meadow',
name_ru: 'Ятрышник шлемоносный', name_be: 'Ятрышнік шлемавідны', name_lat: 'Orchis militaris',
category: 'VU', by_category: 'III',
description: 'Редкая наземная орхидея с необычными лилово-розовыми соцветиями, лепестки которых складываются в форму человечка с «руками» и «ногами». Растёт на известняковых лугах и в светлых лесах. Требует симбиоза с почвенными грибами для прорастания семян.',
fact: 'Семена ятрышника невесомы — в 1 грамме до 50 000 штук. Но без почвенного гриба-симбионта ни одно не прорастёт.',
threats: ['Распашка и застройка лугов', 'Выпас скота', 'Сбор растений'],
conservation: 'Охрана мест обитания. Запрет сбора. Создание питомников.',
where_to_see: 'Мозырские гряды, Новогрудская возвышенность, меловые склоны р. Сож',
biomass: 0.04, seasons: ['5','6'],
trend: [{year:1990,count_estimate:3200,source:'НАН'},{year:2010,count_estimate:1800,source:'НАН'},{year:2024,count_estimate:950,source:'НАН'}],
regions: ['brest','grodno','minsk','gomel'],
});
const rosynka = addSp({
group: 'Растения', habitat: 'wetland',
name_ru: 'Росянка английская', name_be: 'Расіца англійская', name_lat: 'Drosera anglica',
category: 'EN', by_category: 'II',
description: 'Насекомоядное растение сфагновых болот. Длинные листья-ловушки с липкими железистыми ворсинками ловят и переваривают мух и комаров. Получает азот и фосфор из добычи, компенсируя бедность болотной почвы. Крупнейшая из белорусских росянок.',
fact: 'Росянка английская переваривает насекомых за 24–48 часов, выделяя ферменты, аналогичные желудочному соку животных.',
threats: ['Осушение верховых болот', 'Торфоразработка', 'Изменение гидрологии'],
conservation: 'Сохранение верховых сфагновых болот. Контроль торфоразработок.',
where_to_see: 'Ельня, Обстерно, Мох Великий (Витебская обл.)',
biomass: 0.008, seasons: ['6','7','8'],
trend: [{year:1990,count_estimate:18000,source:'НАН'},{year:2010,count_estimate:9000,source:'НАН'},{year:2024,count_estimate:5200,source:'НАН'}],
regions: ['vitebsk','grodno','minsk'],
});
/* ── НАСЕКОМЫЕ (4) ──────────────────────────────────────────────────── */
const zhuk_olen = addSp({
group: 'Насекомые', habitat: 'forest',
name_ru: 'Жук-олень', name_be: 'Жук-алень', name_lat: 'Lucanus cervus',
category: 'EN', by_category: 'II',
description: 'Крупнейший жук Европы: самцы достигают 8 см. «Рога» — гипертрофированные верхние челюсти-мандибулы. Используются в турнирах за самку. Личинка живёт 5–8 лет в гнилой древесине дуба, питаясь разложившейся древесиной. Взрослый жук живёт всего 3–4 недели.',
fact: 'Самец жука-оленя в рыцарском поединке захватывает соперника рогами и сбрасывает с ветки — как настоящий турнир средневековых рыцарей.',
threats: ['Вырубка старых дубрав', 'Удаление пней и гниющей древесины', 'Уличное освещение (дезориентирует)'],
conservation: 'Сохранение старых дубрав и пней. Создание «мёртвой древесины» в парках.',
where_to_see: 'Беловежская пуща, Налибокская пуща, Гродненская пуща',
biomass: 0.006, seasons: ['6','7','8'],
trend: [{year:1990,count_estimate:12000,source:'НАН'},{year:2010,count_estimate:5500,source:'НАН'},{year:2024,count_estimate:2800,source:'НАН'}],
regions: ['brest','grodno','minsk'],
});
const dozorshik = addSp({
group: 'Насекомые', habitat: 'wetland',
name_ru: 'Дозорщик-повелитель', name_be: 'Дазорца-ўладар', name_lat: 'Anax imperator',
category: 'VU', by_category: 'III',
description: 'Крупнейшая стрекоза Беларуси с размахом крыльев до 10 см. Самцы патрулируют водоёмы, агрессивно охраняя территорию. Охотится на других стрекоз, бабочек и даже небольших рыб. Личинка — активный хищник, живущий в воде 2–3 года.',
fact: 'Дозорщик-повелитель преследует и ловит добычу с точностью до 97% — лучший результат среди всех хищников Земли.',
threats: ['Эвтрофикация водоёмов', 'Осушение болот', 'Применение пестицидов'],
conservation: 'Охрана водно-болотных угодий. Ограничение применения инсектицидов.',
where_to_see: 'Нарочанский НП, Споровское болото, пруды Брестской обл.',
biomass: 0.0009, seasons: ['6','7','8','9'],
trend: [{year:2000,count_estimate:5000,source:'ЭО'},{year:2015,count_estimate:2800,source:'ЭО'},{year:2024,count_estimate:1600,source:'ЭО'}],
regions: ['brest','gomel','grodno','minsk'],
});
const shmel = addSp({
group: 'Насекомые', habitat: 'meadow',
name_ru: 'Шмель моховой', name_be: 'Шмель імшысты', name_lat: 'Bombus muscorum',
category: 'EN', by_category: 'II',
description: 'Рыжевато-жёлтый шмель сырых лугов и болот. Один из немногих видов шмелей, гнездящихся на поверхности — в старых птичьих гнёздах или кустах мха. Важнейший опылитель растений заболоченных угодий. Страдает от потери местообитаний сильнее других шмелей.',
fact: 'Шмель моховой — «горячая машина»: перед полётом в холодный день он вибрирует мышцами, разогревая тело до +35°C при температуре воздуха около 0°C.',
threats: ['Интенсификация сельского хозяйства', 'Осушение лугов', 'Применение пестицидов', 'Болезни (Nosema)'],
conservation: 'Сохранение разнотравных сырых лугов. Запрет инсектицидов на природных территориях.',
where_to_see: 'Пойменные луга Полесья, заказник «Ельня», Витебские озёра',
biomass: 0.0004, seasons: ['4','5','6','7','8','9'],
trend: [{year:1990,count_estimate:50000,source:'ЭО'},{year:2010,count_estimate:18000,source:'ЭО'},{year:2024,count_estimate:7000,source:'ЭО'}],
regions: ['vitebsk','minsk','grodno','brest'],
});
const krasotEl = addSp({
group: 'Насекомые', habitat: 'forest',
name_ru: 'Красотел пахучий', name_be: 'Прыгажун духмяны', name_lat: 'Calosoma sycophanta',
category: 'VU', by_category: 'III',
description: 'Охотник за гусеницами: жук поднимается на деревья в погоне за добычей и может съесть до 400 гусениц за сезон. Окраска переливается всеми цветами радуги — синим, зелёным, золотистым. При угрозе выбрызгивает едкую жидкость с резким запахом.',
fact: 'Красотел пахучий — сознательный лесник: он специально охотится на видах-вредителях (шелкопряд, непарный шелкопряд), защищая лес от объедания.',
threats: ['Применение инсектицидов', 'Вырубка широколиственных лесов', 'Сбор коллекционерами'],
conservation: 'Отказ от химических методов борьбы с вредителями. Охрана широколиственных лесов.',
where_to_see: 'Беловежская пуща, Налибокская пуща, широколиственные леса Гродненщины',
biomass: 0.003, seasons: ['5','6','7','8'],
trend: [{year:1990,count_estimate:8000,source:'НАН'},{year:2010,count_estimate:3500,source:'НАН'},{year:2024,count_estimate:1800,source:'НАН'}],
regions: ['brest','grodno','minsk','vitebsk'],
});
/* ── РЫБЫ (3) ───────────────────────────────────────────────────────── */
const forel = addSp({
group: 'Рыбы', habitat: 'river',
name_ru: 'Форель ручьевая', name_be: 'Стронга ручайная', name_lat: 'Salmo trutta fario',
category: 'VU', by_category: 'III',
description: 'Пёстрая хищная рыба холодных прозрачных ручьёв и малых рек. Индикатор чистоты воды: погибает при малейшем загрязнении. Требует хорошую аэрацию и чистый гравийный субстрат для нереста. В Беларуси — на южной границе ареала, обитает только в реках Нарочанского бассейна.',
fact: 'Форель настолько требовательна к кислороду, что может жить только там, где вода кристально чистая. Там, где есть форель — вода питьевого качества.',
threats: ['Загрязнение рек', 'Нагрев воды', 'Браконьерство', 'Мелиорация'],
conservation: 'Охрана малых рек. Контроль качества воды. Ограничение рыбалки. Искусственное разведение.',
where_to_see: 'Реки Нарочь, Страча, Сервечь (Минская обл.), р. Щара (Гродненская обл.)',
biomass: 0.35, seasons: ['1','2','3','4','5','6','7','8','9','10','11','12'],
trend: [{year:1990,count_estimate:4200,source:'ВТ'},{year:2005,count_estimate:2100,source:'ВТ'},{year:2024,count_estimate:900,source:'ВТ'}],
regions: ['vitebsk','grodno','minsk'],
});
const bystryanka = addSp({
group: 'Рыбы', habitat: 'river',
name_ru: 'Быстрянка обыкновенная', name_be: 'Быстранка звычайная', name_lat: 'Alburnoides bipunctatus',
category: 'EN', by_category: 'II',
description: 'Маленькая серебристая рыбка быстрых чистых рек с выраженным течением. Держится у поверхности стайками, питаясь насекомыми. Чувствительна к загрязнению воды. Один из исчезающих видов пресноводных рыб Беларуси, встречается единично в реках западной части страны.',
fact: 'Быстрянка — «наземная» рыба: до 80% её рациона составляют упавшие в воду насекомые — комары, мухи, падёнки.',
threats: ['Загрязнение рек', 'Зарегулирование стока', 'Конкуренция с инвазивными видами'],
conservation: 'Охрана малых рек с быстрым течением. Мониторинг популяции.',
where_to_see: 'Реки Зельвянка, Россь, Нёман (Гродненская обл.)',
biomass: 0.018, seasons: ['4','5','6','7','8','9','10'],
trend: [{year:1990,count_estimate:15000,source:'НАН'},{year:2010,count_estimate:5000,source:'НАН'},{year:2024,count_estimate:1200,source:'НАН'}],
regions: ['grodno','brest','minsk'],
});
const umbra = addSp({
group: 'Рыбы', habitat: 'wetland',
name_ru: 'Умбра европейская', name_be: 'Умбра еўрапейская', name_lat: 'Umbra krameri',
category: 'CR', by_category: 'I',
description: 'Реликтовая рыбка — живой свидетель ледникового периода. Обитает только в медленно текущих водотоках и болотных канавах Полесья. Может переносить высыхание, зарываясь в ил. Дышит атмосферным воздухом при нехватке кислорода. Крупнейшая популяция в Европе — в Припятском полесье.',
fact: 'Умбра — одна из немногих рыб, которая пережила все ледниковые периоды, прячась в рефугиях болот. Ей более 5 миллионов лет.',
threats: ['Осушение болот', 'Углубление канав', 'Загрязнение воды', 'Вселение хищных рыб'],
conservation: 'Охрана болотных водотоков Полесья. Мониторинг популяции. Разведение в неволе.',
where_to_see: 'Припятский НП, болота Столинского района (Брестская обл.), пойма Ствиги',
biomass: 0.025, seasons: ['1','2','3','4','5','6','7','8','9','10','11','12'],
trend: [{year:1990,count_estimate:3500,source:'НАН'},{year:2005,count_estimate:1200,source:'НАН'},{year:2024,count_estimate:300,source:'IUCN'}],
regions: ['brest','gomel'],
});
/* ── ГРИБЫ (2) ──────────────────────────────────────────────────────── */
const felhodon = addSp({
group: 'Грибы', habitat: 'conifer',
name_ru: 'Феллодон слитый', name_be: 'Фелодон злiты', name_lat: 'Phellodon confluens',
category: 'EN', by_category: 'II',
description: 'Редкий коралловидный гриб хвойных и смешанных лесов. Несколько плодовых тел часто сливаются в одну неправильную форму, откуда и название. Серовато-белый, с тонкими иглами на нижней поверхности шляпки. Индикатор старых ненарушенных лесов. Занесён в Красные книги 12 стран.',
fact: 'Феллодон слитый растёт только в лесах, которым не менее 150 лет — он служит биологическим маркером древних экосистем.',
threats: ['Рубки главного пользования', 'Нарушение лесной подстилки', 'Рекреационная нагрузка'],
conservation: 'Сохранение старовозрастных лесов. Организация микозаповедников.',
where_to_see: 'Беловежская пуща, Налибокская пуща, хвойные леса Витебской обл.',
biomass: 0.06, seasons: ['8','9','10'],
trend: [{year:1990,count_estimate:200,source:'НАН'},{year:2010,count_estimate:85,source:'НАН'},{year:2024,count_estimate:40,source:'НАН'}],
regions: ['vitebsk','minsk','grodno','brest'],
});
const gidnellum = addSp({
group: 'Грибы', habitat: 'forest',
name_ru: 'Гиднеллум синеющий', name_be: 'Гiднелум сiнеючы', name_lat: 'Hydnellum caeruleum',
category: 'VU', by_category: 'III',
description: 'Необычный ежовый гриб с сине-фиолетовым или голубовато-серым молодым плодовым телом, темнеющим до тёмно-коричневого. Растёт в мшистых хвойных лесах, образуя микоризу с соснами и елями. Очень горький на вкус, несъедобен. Исчезает при нарушении лесной экосистемы.',
fact: 'Гиднеллум синеющий настолько редок, что находка даже одного плодового тела считается значимым событием для микологов всей Европы.',
threats: ['Лесозаготовки', 'Удаление мохового покрова', 'Загрязнение атмосферного воздуха'],
conservation: 'Выявление и охрана местонахождений. Сохранение мшистых ельников.',
where_to_see: 'Налибокская пуща, Борисовско-Березинский резерват',
biomass: 0.04, seasons: ['8','9','10'],
trend: [{year:2000,count_estimate:60,source:'НАН'},{year:2015,count_estimate:28,source:'НАН'},{year:2024,count_estimate:15,source:'НАН'}],
regions: ['vitebsk','minsk','grodno'],
});
/* ── МХИ И ЛИШАЙНИКИ (4) ────────────────────────────────────────────── */
const buksbaumiya = addSp({
group: 'Мхи и лишайники', habitat: 'conifer',
name_ru: 'Буксбаумия безлистная', name_be: 'Буксбаумiя бязлістая', name_lat: 'Buxbaumia aphylla',
category: 'EN', by_category: 'II',
description: 'Один из самых необычных мхов: почти без листьев, с единственным крупным непропорциональным спорангием на тонкой ножке. Споровая коробочка напоминает панцирь черепахи. Растёт на гниющих пнях хвойных деревьев. Исчезает вместе с исчезновением гниющей древесины в заготовительных лесах.',
fact: 'Буксбаумия безлистная проводит почти всю жизнь как невидимая нить микоризы — листовая часть появляется лишь на несколько недель для спороношения.',
threats: ['Уборка гнилой древесины', 'Интенсивные рубки', 'Нарушение лесной подстилки'],
conservation: 'Сохранение пней и гниющей древесины в лесах. Организация «мёртвого дерева» в заказниках.',
where_to_see: 'Ельники Витебской и Минской обл., Беловежская пуща',
biomass: 0.001, seasons: ['9','10','11','3','4'],
trend: [{year:1990,count_estimate:800,source:'НАН'},{year:2010,count_estimate:320,source:'НАН'},{year:2024,count_estimate:150,source:'НАН'}],
regions: ['vitebsk','minsk','grodno','brest'],
});
const meesiya = addSp({
group: 'Мхи и лишайники', habitat: 'wetland',
name_ru: 'Меезия трёхгранная', name_be: 'Меезiя трохгранная', name_lat: 'Meesia triquetra',
category: 'VU', by_category: 'III',
description: 'Болотный мох с характерными трёхгранными листьями, образующий плотные дернины в основаниях сфагновых кочек. Вид-индикатор ненарушенных верховых болот, чувствителен к изменению гидрологического режима. В Беларуси находится на южной границе ареала и встречается крайне редко.',
fact: 'Меезия трёхгранная может фиксировать свидетельства климата за последние тысячи лет — учёные читают историю болота по слоям этого мха как по книге.',
threats: ['Осушение болот', 'Добыча торфа', 'Изменение уровня грунтовых вод'],
conservation: 'Охрана верховых болот. Поддержание высокого уровня воды в болотных комплексах.',
where_to_see: 'Ельня (Витебская обл.), Мох Великий, Освейское болото',
biomass: 0.002, seasons: ['1','2','3','4','5','6','7','8','9','10','11','12'],
trend: [{year:1990,count_estimate:500,source:'НАН'},{year:2010,count_estimate:200,source:'НАН'},{year:2024,count_estimate:90,source:'НАН'}],
regions: ['vitebsk','minsk'],
});
const usnea = addSp({
group: 'Мхи и лишайники', habitat: 'conifer',
name_ru: 'Уснея длиннейшая', name_be: 'Уснея найдаўжэйшая', name_lat: 'Usnea longissima',
category: 'CR', by_category: 'I',
description: 'Бородатый лишайник, свисающий с ветвей хвойных деревьев нитями длиной до 3 метров. Требует исключительно чистого воздуха и высокой влажности. Был широко распространён в старых лесах Беларуси, но исчез почти повсеместно из-за загрязнения воздуха и вырубок. Сегодня — в единичных местонахождениях.',
fact: 'Уснея длиннейшая растёт со скоростью 1–2 мм в год, а её трёхметровые нити означают столетие жизни. Найти её — значит найти вековой нетронутый лес.',
threats: ['Загрязнение воздуха', 'Вырубка старых хвойных лесов', 'Изменение климата'],
conservation: 'Строгая охрана мест произрастания. Мониторинг качества воздуха. Охрана старолесий.',
where_to_see: 'Налибокская пуща, отдельные участки Витебской обл.',
biomass: 0.012, seasons: ['1','2','3','4','5','6','7','8','9','10','11','12'],
trend: [{year:1990,count_estimate:120,source:'НАН'},{year:2005,count_estimate:35,source:'НАН'},{year:2024,count_estimate:8,source:'НАН'}],
regions: ['vitebsk','grodno'],
});
const peltigera = addSp({
group: 'Мхи и лишайники', habitat: 'forest',
name_ru: 'Пельтигера горизонтальная', name_be: 'Пельцiгера гарызантальная', name_lat: 'Peltigera horizontalis',
category: 'VU', by_category: 'III',
description: 'Крупный листоватый лишайник с горизонтально распластанным слоевищем, вырастающим до 20 см в диаметре. Серо-голубой сверху, с заметными нитями гиф и цианобактерий на нижней поверхности. Растёт на замшелых стволах и основаниях деревьев в старых широколиственных лесах. Фиксирует атмосферный азот.',
fact: 'Пельтигера — симбиоз гриба, водорослей и цианобактерий в одном организме. Она единственная из лишайников способна удобрять почву, фиксируя воздушный азот.',
threats: ['Вырубка широколиственных лесов', 'Рекреационное вытаптывание', 'Загрязнение воздуха'],
conservation: 'Сохранение старых широколиственных лесов. Ограничение рекреационной нагрузки.',
where_to_see: 'Беловежская пуща, Налибокская пуща, старые широколиственные леса Гродненщины',
biomass: 0.006, seasons: ['1','2','3','4','5','6','7','8','9','10','11','12'],
trend: [{year:1990,count_estimate:350,source:'НАН'},{year:2010,count_estimate:140,source:'НАН'},{year:2024,count_estimate:70,source:'НАН'}],
regions: ['vitebsk','minsk','grodno','brest'],
});
db.exec('COMMIT');
console.log('✓ Виды добавлены');
console.log(` Всего видов: ${db.prepare('SELECT COUNT(*) as n FROM rb_species').get().n}`);
/* ════════════════════════════════════════════════════════════════════════
БЛОК 2 — ПИЩЕВЫЕ СВЯЗИ
════════════════════════════════════════════════════════════════════════ */
db.exec('BEGIN');
const links = [
// Орлан охотится на лебедей (более активно)
[getId('Орлан-белохвост'), getId('Лебедь-шипун'), 0.25],
[getId('Орлан-белохвост'), getId('Лебедь-кликун'), 0.20],
// Кобчик ест насекомых
[kobchik, getId('Аполлон'), 0.50],
[kobchik, getId('Богомол обыкновенный'), 0.30],
[kobchik, shmel, 0.50],
[kobchik, dozorshik, 0.40],
// Вертлявая камышевка ест насекомых болот
[kamyshevka, shmel, 0.20],
[kamyshevka, dozorshik, 0.15],
// Красотел ест бабочек/жуков
[krasotEl, getId('Аполлон'), 0.60],
[krasotEl, getId('Махаон'), 0.50],
[krasotEl, zhuk_olen, 0.30],
// Форель ест быстрянку и другую рыбу
[forel, bystryanka, 0.80],
[forel, getId('Хариус европейский'), 0.40],
[forel, getId('Ручьевая минога'), 0.30],
// Умбра — мелкий хищник
[umbra, bystryanka, 0.30],
// Крупные рыбы едят форель
[getId('Сом обыкновенный'), forel, 0.40],
[getId('Выдра речная'), forel, 0.60],
[getId('Выдра речная'), bystryanka, 0.30],
// Норка и выдра охотятся на выхухоль
[getId('Европейская норка'), vyhuhol, 0.60],
[getId('Выдра речная'), vyhuhol, 0.20],
// Ночница ест насекомых над водой
[nochnitza, shmel, 0.30],
[nochnitza, dozorshik, 0.40],
// Жук-олень поедает соки деревьев (связь через растения если есть)
// Дупель и камышевка — косвенные связи через экосистему
].filter(([p, q]) => p && q);
links.forEach(([p, q, s]) => {
try { insWeb.run(p, q, s); } catch {}
});
db.exec('COMMIT');
console.log(` Пищевых связей добавлено: ${links.length}`);
console.log(` Всего пищевых связей: ${db.prepare('SELECT COUNT(*) as n FROM rb_food_web').get().n}`);
/* ════════════════════════════════════════════════════════════════════════
БЛОК 3 — КВЕСТЫ (10 новых)
════════════════════════════════════════════════════════════════════════ */
db.exec('BEGIN');
function addQuest(title, description, speciesNames, xp, badge) {
const existing = db.prepare('SELECT id FROM rb_quests WHERE title = ?').get(title);
if (existing) { console.log(` Квест уже существует: ${title}`); return; }
const ids = speciesNames.map(n => getId(n)).filter(Boolean);
if (ids.length < speciesNames.length) {
console.warn(` Квест "${title}": не найдены виды!`, speciesNames.filter(n => !getId(n)));
}
db.prepare('INSERT INTO rb_quests (title, description, species_ids, xp_reward, badge_slug) VALUES (?,?,?,?,?)').run(
title, description, JSON.stringify(ids), xp, badge
);
}
addQuest(
'Птицы болот',
'Болота Беларуси — дом для редчайших птиц Европы. Найдите серого журавля, большого подорлика, большого кроншнепа, вертлявую камышевку и дупеля. Узнайте, почему без болот эти виды исчезнут с планеты.',
['Серый журавль', 'Большой подорлик', 'Большой кроншнеп', 'Вертлявая камышевка', 'Дупель'],
220, 'quest_bog_birds'
);
addQuest(
'Ночные хищники',
'Пока мы спим, лес живёт своей жизнью. Исследуйте тайных обитателей ночи: воробьиного и домового сычей, широкоушку и прудовую ночницу. Узнайте, как эхолокация превращает темноту в охотничьи угодья.',
['Воробьиный сыч', 'Сыч домовый', 'Широкоушка европейская', 'Ночница прудовая'],
180, 'quest_night_hunters'
);
addQuest(
'Диковинки Витебщины',
'Витебская область — северный форпост Беларуси со своими уникальными видами. Откройте краснозобую казарку, лобелию Дортмана, вертлявую камышевку, форель ручьевую и уснею длиннейшую — виды, которых больше нигде не встретить.',
['Краснозобая казарка', 'Лобелия Дортмана', 'Вертлявая камышевка', 'Форель ручьевая', 'Уснея длиннейшая'],
230, 'quest_vitebsk'
);
addQuest(
'Царство грибов',
'Мир грибов гораздо больше того, что мы видим. Трюфель, решёточник, спарасис, ёж гриб, феллодон и гиднеллум — это не еда, это живая история леса. Найдите всех шестерых и узнайте, почему грибы — хранители экосистемы.',
['Трюфель летний', 'Решёточник красный', 'Спарасис курчавый (грибная капуста)', 'Ёж гриб (герций)', 'Феллодон слитый', 'Гиднеллум синеющий'],
300, 'quest_fungi'
);
addQuest(
'Мир мхов и лишайников',
'Мхи и лишайники — первооткрыватели суши, они появились раньше динозавров. Найдите лобарию, кладонию, буксбаумию, меезию, уснею и пельтигеру — шесть хранителей нетронутых лесов и болот Беларуси.',
['Лобария лёгочная', 'Кладония звёздчатая', 'Буксбаумия безлистная', 'Меезия трёхгранная', 'Уснея длиннейшая', 'Пельтигера горизонтальная'],
280, 'quest_mosses'
);
addQuest(
'Легенды Полесья',
'Полесье — болотный рай, один из последних в Европе. Его населяют легендарные жители: европейская норка, болотная черепаха, альдрованда, выхухоль русская и умбра европейская — реликты, пережившие ледниковые периоды.',
['Европейская норка', 'Черепаха болотная', 'Альдрованда пузырчатая', 'Выхухоль русская', 'Умбра европейская'],
350, 'quest_polesye'
);
addQuest(
'Хранители леса',
'Лес держится на хищниках. Рысь, бурый медведь, жук-олень, усач альпийский и красотел пахучий — звенья одной цепи. Когда исчезает один хищник, лес начинает умирать. Узнайте, как они связаны между собой.',
['Рысь евразийская', 'Бурый медведь', 'Жук-олень', 'Усач альпийский', 'Красотел пахучий'],
250, 'quest_forest_guard'
);
addQuest(
'Тайны реки',
'Белорусские реки скрывают живых ископаемых. Выдра, стерлядь, форель, быстрянка и умбра европейская — каждый вид-индикатор показывает, здорова ли река. Найдите их всех и прочтите историю наших вод.',
['Речная выдра', 'Стерлядь', 'Форель ручьевая', 'Быстрянка обыкновенная', 'Умбра европейская'],
220, 'quest_river2'
);
addQuest(
'Летающие над водой',
'Вода с небесами — место встречи орлана-белохвоста, скопы, двух видов лебедей и дозорщика-повелителя. Все они связаны с водой и исчезнут вместе с ней. Откройте пятёрку хозяев белорусских озёр.',
['Орлан-белохвост', 'Скопа', 'Лебедь-шипун', 'Лебедь-кликун', 'Дозорщик-повелитель'],
200, 'quest_water_fliers'
);
addQuest(
'Краса болот',
'Болотные растения — хищники, ловушки и редкие орхидеи. Пальчатокоренник, шейхцерия, альдрованда, вертлявая камышевка и росянка английская живут в мире, где каждая капля воды на счету. Исследуйте болото изнутри.',
['Пальчатокоренник мясокрасный', 'Шейхцерия болотная', 'Альдрованда пузырчатая', 'Вертлявая камышевка', 'Росянка английская'],
230, 'quest_bog_beauty'
);
db.exec('COMMIT');
console.log('✓ Квесты добавлены');
console.log(` Всего квестов: ${db.prepare('SELECT COUNT(*) as n FROM rb_quests').get().n}`);
console.log('\n✅ seed-red-book-phase2.js завершён!');
console.log(` Видов: ${db.prepare('SELECT COUNT(*) as n FROM rb_species').get().n}`);
console.log(` Квестов: ${db.prepare('SELECT COUNT(*) as n FROM rb_quests').get().n}`);
console.log(` Пищевых связей: ${db.prepare('SELECT COUNT(*) as n FROM rb_food_web').get().n}`);
+807
View File
@@ -0,0 +1,807 @@
/**
* Seed: Красная книга Республики Беларусь
* 80 видов, 8 групп, пищевая сеть, популяционные тренды
* Запуск: node src/db/seed-red-book.js (из папки backend/)
*/
require('./migrate');
const db = require('./db');
/* ══════════════════════════════════════════════════════════════════════════
GUARD — пропустить если уже засеяно
══════════════════════════════════════════════════════════════════════════ */
const already = db.prepare("SELECT COUNT(*) as n FROM rb_species").get();
if (already.n > 0) {
console.log(`Красная книга: уже засеяно ${already.n} видов, пропускаем.`);
process.exit(0);
}
/* ══════════════════════════════════════════════════════════════════════════
ГРУППЫ
══════════════════════════════════════════════════════════════════════════ */
const insGroup = db.prepare('INSERT INTO rb_groups (name_ru, name_lat, icon, color) VALUES (?,?,?,?)');
const groups = {};
[
['Птицы', 'Aves', '🦅', '#0369a1'],
['Млекопитающие', 'Mammalia', '🦌', '#92400e'],
['Растения', 'Plantae', '🌿', '#166534'],
['Насекомые', 'Insecta', '🦋', '#b45309'],
['Рыбы', 'Pisces', '🐟', '#0e7490'],
['Рептилии и амфибии','Reptilia/Amphibia','🦎','#4d7c0f'],
['Грибы', 'Fungi', '🍄', '#7c3aed'],
['Мхи и лишайники', 'Bryophyta', '🌱', '#065f46'],
].forEach(([name_ru, name_lat, icon, color]) => {
const id = insGroup.run(name_ru, name_lat, icon, color).lastInsertRowid;
groups[name_ru] = id;
});
console.log('✓ Группы');
/* ══════════════════════════════════════════════════════════════════════════
БИОМЫ
══════════════════════════════════════════════════════════════════════════ */
const insHabitat = db.prepare('INSERT INTO rb_habitats (name, type, description, sound_file) VALUES (?,?,?,?)');
const habitats = {};
[
['Широколиственный лес', 'forest', 'Дубравы и грабово-дубовые леса Полесья и центральной Беларуси', 'forest.mp3'],
['Хвойный лес', 'conifer', 'Сосновые и еловые боры, тайга северной Беларуси', 'conifer.mp3'],
['Болото', 'wetland', 'Верховые и низинные болота, крупнейшие в Европе', 'wetland.mp3'],
['Река и озеро', 'river', 'Пресноводные водоёмы: Припять, Нёман, Западная Двина', 'river.mp3'],
['Луг и поле', 'meadow', 'Заливные и суходольные луга, агроландшафты', 'meadow.mp3'],
].forEach(([name, type, description, sound_file]) => {
const id = insHabitat.run(name, type, description, sound_file).lastInsertRowid;
habitats[name] = id;
});
console.log('✓ Биомы');
/* ══════════════════════════════════════════════════════════════════════════
ВИДЫ
══════════════════════════════════════════════════════════════════════════ */
const insSp = db.prepare(`
INSERT INTO rb_species
(group_id, habitat_id, name_ru, name_be, name_lat, category, by_category,
description, interesting_fact, threats, conservation, where_to_see,
photo_url, model_type, population_trend, biomass_kg)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
`);
const insReg = db.prepare('INSERT OR IGNORE INTO rb_species_regions (species_id, region_code) VALUES (?,?)');
const insPop = db.prepare('INSERT INTO rb_population_data (species_id, year, count_estimate, source) VALUES (?,?,?,?)');
function sp(data) {
const id = insSp.run(
groups[data.group], habitats[data.habitat] || null,
data.name_ru, data.name_be || '', data.name_lat || '',
data.category, data.by_category || 'III',
data.description || '', data.fact || '',
JSON.stringify(data.threats || []),
data.conservation || '', data.where_to_see || '',
data.photo || '', data.model || 'silhouette',
JSON.stringify(data.trend || []),
data.biomass || 0
).lastInsertRowid;
(data.regions || []).forEach(r => insReg.run(id, r));
(data.popdata || []).forEach(([year, count, src]) => insPop.run(id, year, count, src || 'КК РБ'));
return id;
}
const ids = {};
db.exec('BEGIN');
try { // species block
/* ── ПТИЦЫ ─────────────────────────────────────────────────────────────── */
ids.orlan = sp({
group: 'Птицы', habitat: 'Река и озеро',
name_ru: 'Орлан-белохвост', name_be: 'Арлан-белахвост', name_lat: 'Haliaeetus albicilla',
category: 'NT', by_category: 'III',
description: 'Крупнейший хищник среди белорусских орлиных. Размах крыльев достигает 240 см. Гнездится у крупных водоёмов, охотится на рыбу и уток. Пара занимает одно гнездо десятилетиями, достраивая его каждый год — масса гнезда может превышать тонну.',
fact: 'Орлан развивает скорость пикирования до 100 км/ч. Гнездо одной пары в Беловежской пуще весило 600 кг.',
threats: ['Беспокойство у гнёзд', 'Отравление свинцом из охотничьей дроби', 'Вырубка леса у водоёмов'],
conservation: 'Охраняется в заповедниках. Программа мониторинга гнёзд. Запрет охоты.',
where_to_see: 'НП «Припятский», НП «Браславские озёра», Беловежская пуща',
model: 'procedural', biomass: 5.5,
regions: ['brest','gomel','vitebsk','grodno','minsk','mogilev'],
popdata: [[1990,150,'КК РБ 1993'],[2000,200,'КК РБ 2004'],[2010,280,'КК РБ 2015'],[2024,350,'Мониторинг 2024']],
trend: [{year:1990,count:150},{year:2000,count:200},{year:2010,count:280},{year:2024,count:350}],
});
ids.chorny_aist = sp({
group: 'Птицы', habitat: 'Широколиственный лес',
name_ru: 'Чёрный аист', name_be: 'Чорны бусел', name_lat: 'Ciconia nigra',
category: 'NT', by_category: 'III',
description: 'В отличие от белого аиста, чёрный предпочитает глухие леса с ручьями и реками. Оперение с зелёным и пурпурным металлическим блеском. Зимует в Африке южнее Сахары. Чрезвычайно осторожен — при беспокойстве бросает гнездо.',
fact: 'Чёрный аист преодолевает до 10 000 км во время миграции. В Беларуси гнездится около 900 пар.',
threats: ['Вырубка старовозрастных лесов', 'Осушение болот', 'Беспокойство у гнёзд'],
conservation: 'Охраняемые зоны вокруг гнёзд радиусом 200 м. Сохранение старовозрастных деревьев.',
where_to_see: 'Беловежская пуща, НП «Припятский», ПЗ «Налибокский»',
model: 'procedural', biomass: 3.0,
regions: ['brest','gomel','grodno','minsk','vitebsk'],
popdata: [[1990,600,'КК РБ 1993'],[2005,800,'КК РБ 2004'],[2015,850,'КК РБ 2015'],[2024,920,'Мониторинг 2024']],
trend: [{year:1990,count:600},{year:2005,count:800},{year:2015,count:850},{year:2024,count:920}],
});
ids.skopa = sp({
group: 'Птицы', habitat: 'Река и озеро',
name_ru: 'Скопа', name_be: 'Скапа', name_lat: 'Pandion haliaetus',
category: 'VU', by_category: 'II',
description: 'Уникальный хищник, питающийся исключительно рыбой. Ныряет в воду на глубину до 1 м. Пальцы покрыты шипами для удержания скользкой добычи. Строит гнёзда на вершинах сухих деревьев или опорах ЛЭП.',
fact: 'Скопа — единственная хищная птица мира, охотящаяся только на рыбу и обитающая на всех континентах кроме Антарктиды.',
threats: ['Деградация рыбных ресурсов', 'Беспокойство у гнёзд', 'Гибель на ЛЭП'],
conservation: 'Установка искусственных гнездовых платформ. Охранные зоны у гнёзд.',
where_to_see: 'Браславские озёра, НП «Нарочанский», озёра Витебской области',
model: 'procedural', biomass: 1.8,
regions: ['vitebsk','minsk','grodno','brest'],
popdata: [[1990,50,'КК РБ 1993'],[2005,70,'КК РБ 2004'],[2015,90,'КК РБ 2015'],[2024,110,'Мониторинг 2024']],
trend: [{year:1990,count:50},{year:2005,count:70},{year:2015,count:90},{year:2024,count:110}],
});
ids.dergach = sp({
group: 'Птицы', habitat: 'Луг и поле',
name_ru: 'Коростель', name_be: 'Дзяргач', name_lat: 'Crex crex',
category: 'VU', by_category: 'III',
description: 'Скрытная птица влажных лугов. Слышать его легко — самец кричит «дёрг-дёрг» всю ночь, но увидеть почти невозможно. Прилетает поздно весной из Африки. Численность резко падает из-за раннего сенокоса.',
fact: 'Коростель бежит быстрее, чем летит. Его крик слышен за 1,5 км в тихую ночь.',
threats: ['Ранний механизированный сенокос', 'Осушение лугов', 'Хищники'],
conservation: 'Рекомендации по срокам сенокоса (после 1 августа). Сохранение пойменных лугов.',
where_to_see: 'Пойма Припяти, Нарочанский НП, луга Брестской области',
biomass: 0.15,
regions: ['brest','gomel','grodno','minsk','vitebsk','mogilev'],
popdata: [[1990,5000,'КК РБ 1993'],[2005,3500,'КК РБ 2004'],[2015,2800,'КК РБ 2015'],[2024,2200,'Мониторинг 2024']],
trend: [{year:1990,count:5000},{year:2005,count:3500},{year:2015,count:2800},{year:2024,count:2200}],
});
ids.zhuravl = sp({
group: 'Птицы', habitat: 'Болото',
name_ru: 'Серый журавль', name_be: 'Шэры жураўль', name_lat: 'Grus grus',
category: 'NT', by_category: 'IV',
description: 'Символ белорусских болот. Пары создаются на всю жизнь. Весной и осенью собирается на полях в стаи до нескольких тысяч птиц. Токование — один из самых красивых ритуалов среди птиц: прыжки, взмахи крыльями, трубный крик.',
fact: 'Журавль живёт до 25 лет в дикой природе. Беларусь — один из главных «журавлиных» регионов Европы с населением ~20 000 пар.',
threats: ['Осушение болот', 'Беспокойство в период гнездования'],
conservation: 'Сохранение болотных массивов. Охраняемые территории на токовищах.',
where_to_see: 'НП «Припятский», Ельня, Освейское болото',
model: 'procedural', biomass: 5.0,
regions: ['vitebsk','minsk','gomel','brest','grodno','mogilev'],
trend: [{year:1990,count:15000},{year:2010,count:18000},{year:2024,count:20000}],
});
ids.zmeeyed = sp({
group: 'Птицы', habitat: 'Широколиственный лес',
name_ru: 'Змееяд', name_be: 'Змеяед', name_lat: 'Circaetus gallicus',
category: 'EN', by_category: 'II',
description: 'Специализированный охотник на рептилий. Питается почти исключительно змеями, в том числе гадюками. Иммунитет к яду позволяет заглатывать змею живой. Редчайший в Беларуси — гнездятся единицы пар.',
fact: 'Змееяд способен заглотить гадюку длиной 1 м. В Беларуси гнездится не более 15–20 пар.',
threats: ['Вырубка старых лесов', 'Снижение численности рептилий', 'Беспокойство'],
conservation: 'Охрана известных гнёзд. Мониторинг численности.',
where_to_see: 'ПЗ «Полесский», Беловежская пуща',
biomass: 1.9,
regions: ['brest','gomel'],
popdata: [[2000,12,'КК РБ 2004'],[2015,15,'КК РБ 2015'],[2024,18,'Мониторинг 2024']],
trend: [{year:2000,count:12},{year:2015,count:15},{year:2024,count:18}],
});
ids.vorobiny_sych = sp({
group: 'Птицы', habitat: 'Хвойный лес',
name_ru: 'Воробьиный сыч', name_be: 'Верабіны сычык', name_lat: 'Glaucidium passerinum',
category: 'VU', by_category: 'III',
description: 'Самая маленькая сова Европы — размером с дрозда. Активна как днём, так и ночью. Делает запасы пищи в дуплах на зиму. Голос — монотонный свист, слышный далеко в лесу.',
fact: 'Несмотря на размер с воробья, воробьиный сыч охотится на добычу крупнее себя — полёвок и мелких птиц.',
threats: ['Вырубка старых ельников', 'Исчезновение дупел'],
conservation: 'Сохранение старовозрастных ельников. Вывешивание искусственных дупел.',
where_to_see: 'Налибокская пуща, Беловежская пуща, Ельнянский заказник',
biomass: 0.08,
regions: ['grodno','vitebsk','minsk'],
});
ids.krasnozobaya_kazarka = sp({
group: 'Птицы', habitat: 'Река и озеро',
name_ru: 'Краснозобая казарка', name_be: 'Чырванашыйная казарка', name_lat: 'Branta ruficollis',
category: 'EN', by_category: 'II',
description: 'Одна из самых красивых арктических гусей. В Беларуси встречается только во время миграций — зимует на Чёрном море. Численность катастрофически сокращается из-за охоты на зимовках.',
fact: 'Краснозобая казарка гнездится исключительно в тундре Западной Сибири. Беларусь — транзитный коридор миграции.',
threats: ['Охота на зимовках', 'Изменение климата', 'Беспокойство на миграционных стоянках'],
conservation: 'Запрет охоты. Охрана мест остановок на миграции.',
where_to_see: 'Полесские водохранилища, озёра Витебщины (пролёт)',
biomass: 1.5,
regions: ['gomel','brest','vitebsk'],
});
ids.filin = sp({
group: 'Птицы', habitat: 'Хвойный лес',
name_ru: 'Филин', name_be: 'Пугач', name_lat: 'Bubo bubo',
category: 'VU', by_category: 'II',
description: 'Крупнейшая сова планеты. Ночной охотник, способный атаковать добычу весом до 3 кг. Охотничий участок пары — до 80 км². Низкое токование самца слышно за 4 км в тихую ночь.',
fact: 'Филин — единственная птица, которая регулярно охотится на других хищников: ловит ястребов и даже молодых орланов.',
threats: ['Фактор беспокойства', 'Гибель на ЛЭП', 'Оскудение кормовой базы'],
conservation: 'Охрана гнёзд. Изоляция ЛЭП. Мониторинг популяции.',
where_to_see: 'Беловежская пуща, ПЗ «Налибокский», НП «Припятский»',
model: 'procedural', biomass: 2.7,
regions: ['brest','grodno','gomel','minsk'],
trend: [{year:1990,count:200},{year:2010,count:250},{year:2024,count:280}],
});
ids.zhuravl_seryi2 = sp({
group: 'Птицы', habitat: 'Болото',
name_ru: 'Большой подорлик', name_be: 'Вялікі арол-крычун', name_lat: 'Clanga clanga',
category: 'EN', by_category: 'II',
description: 'Один из самых редких орлов Европы. Гнездится в старых лиственных лесах рядом с болотами и поймами. Питается амфибиями, мышевидными грызунами. Особенно уязвим из-за низкой плодовитости — одно яйцо в кладке.',
fact: 'В мире гнездится менее 10 000 пар большого подорлика. Беларусь — один из ключевых регионов гнездования в Европе.',
threats: ['Осушение болот и пойм', 'Вырубка пойменных лесов', 'Беспокойство'],
conservation: 'Охрана гнёзд. Сохранение болотных массивов.',
where_to_see: 'НП «Припятский», Полесский радиационно-экологический заповедник',
biomass: 2.0,
regions: ['gomel','brest','minsk'],
popdata: [[2000,80,'КК РБ 2004'],[2015,90,'КК РБ 2015'],[2024,100,'Мониторинг 2024']],
trend: [{year:2000,count:80},{year:2015,count:90},{year:2024,count:100}],
});
/* ── МЛЕКОПИТАЮЩИЕ ──────────────────────────────────────────────────────── */
ids.zubr = sp({
group: 'Млекопитающие', habitat: 'Широколиственный лес',
name_ru: 'Зубр европейский', name_be: 'Зубр', name_lat: 'Bison bonasus',
category: 'VU', by_category: 'III',
description: 'Крупнейшее наземное млекопитающее Европы. Масса самца достигает 920 кг при высоте в холке 188 см. Вид был полностью истреблён в дикой природе к 1927 году — всё нынешнее поголовье (около 7000 особей в мире) происходит от 12 зубров из зоопарков. Беларусь сыграла ключевую роль в восстановлении вида.',
fact: 'Зубр может прыгнуть в высоту до 2 м. Беловежская пуща — колыбель восстановления этого вида.',
threats: ['Инбридинг из-за малой генетической базы', 'Болезни', 'Браконьерство'],
conservation: 'Беловежская пуща — ключевой центр разведения. Программы реинтродукции в Европе.',
where_to_see: 'Беловежская пуща (основная популяция), Налибокская пуща',
model: 'procedural', biomass: 650,
regions: ['brest','grodno','minsk','vitebsk'],
popdata: [[1927,0,'Летопись'],[1960,150,'КК РБ'],[1990,850,'КК РБ 1993'],[2010,1500,'КК РБ 2015'],[2024,2000,'Мониторинг 2024']],
trend: [{year:1927,count:0},{year:1960,count:150},{year:1990,count:850},{year:2010,count:1500},{year:2024,count:2000}],
});
ids.rys = sp({
group: 'Млекопитающие', habitat: 'Хвойный лес',
name_ru: 'Рысь евразийская', name_be: 'Рысь', name_lat: 'Lynx lynx',
category: 'NT', by_category: 'III',
description: 'Крупнейший кошачий хищник Европы. Ведёт одиночный ночной образ жизни. Охотится главным образом на косуль и зайцев, реже на других оленей. Огромные лапы работают как снегоступы — рысь не проваливается в снег глубиной до 50 см.',
fact: 'Рысь может прыгнуть на расстояние 7 м. Она единственная крупная кошка, живущая севернее 60° с.ш.',
threats: ['Браконьерство', 'Фрагментация лесных массивов', 'Снижение кормовой базы'],
conservation: 'Полный запрет охоты. Охрана крупных лесных массивов. GPS-мониторинг.',
where_to_see: 'Налибокская пуща, Беловежская пуща, Освейский заказник',
model: 'procedural', biomass: 22,
regions: ['grodno','minsk','vitebsk','brest'],
popdata: [[1990,350,'КК РБ 1993'],[2005,500,'КК РБ 2004'],[2015,650,'КК РБ 2015'],[2024,700,'Мониторинг 2024']],
trend: [{year:1990,count:350},{year:2005,count:500},{year:2015,count:650},{year:2024,count:700}],
});
ids.medved = sp({
group: 'Млекопитающие', habitat: 'Хвойный лес',
name_ru: 'Бурый медведь', name_be: 'Буры мядзведзь', name_lat: 'Ursus arctos',
category: 'NT', by_category: 'III',
description: 'Крупнейший хищник фауны Беларуси. Всеяден — рацион на 80% состоит из растительной пищи. Залегает в берлогу с ноября по март. В Беларуси обитает на северо-востоке, сохранились лишь периферийные популяции.',
fact: 'Медведь чует запах еды с расстояния 20 км. Самка рожает медвежат в берлоге прямо в январе, весящих всего 500 г.',
threats: ['Браконьерство', 'Фрагментация лесов', 'Деградация кормовой базы'],
conservation: 'Запрет охоты. Охрана берлог. Мониторинг численности.',
where_to_see: 'Полоцкий район Витебской области, Освейский заказник',
model: 'procedural', biomass: 180,
regions: ['vitebsk','mogilev'],
popdata: [[1990,80,'КК РБ 1993'],[2005,70,'КК РБ 2004'],[2015,65,'КК РБ 2015'],[2024,60,'Мониторинг 2024']],
trend: [{year:1990,count:80},{year:2005,count:70},{year:2015,count:65},{year:2024,count:60}],
});
ids.norka = sp({
group: 'Млекопитающие', habitat: 'Река и озеро',
name_ru: 'Европейская норка', name_be: 'Еўрапейская норка', name_lat: 'Mustela lutreola',
category: 'CR', by_category: 'I',
description: 'Один из самых редких хищников Европы. Конкурентно вытеснена завезённой американской норкой. Держится у небольших рек с захламлёнными берегами. Отличается от американской норкой белым пятном на верхней губе.',
fact: 'Европейская норка занесена в Красную книгу МСОП как находящаяся под критической угрозой исчезновения. В Беларуси обитает менее 500 особей.',
threats: ['Конкуренция с американской норкой', 'Охота/отлов', 'Деградация береговой растительности'],
conservation: 'Программа разведения в неволе. Отлов и стерилизация американской норки. Реинтродукция.',
where_to_see: 'Полесье, бассейн Немана',
biomass: 0.6,
regions: ['brest','gomel','grodno'],
popdata: [[1990,2000,'КК РБ 1993'],[2005,800,'КК РБ 2004'],[2015,400,'КК РБ 2015'],[2024,300,'Мониторинг 2024']],
trend: [{year:1990,count:2000},{year:2005,count:800},{year:2015,count:400},{year:2024,count:300}],
});
ids.vydra = sp({
group: 'Млекопитающие', habitat: 'Река и озеро',
name_ru: 'Речная выдра', name_be: 'Выдра', name_lat: 'Lutra lutra',
category: 'NT', by_category: 'III',
description: 'Полуводный хищник семейства куньих. Мастер плавания — способна задерживать дыхание на 4 минуты. Поедает рыбу, раков, лягушек. Индикатор чистоты водоёмов: там, где живёт выдра, вода чистая.',
fact: 'Выдра скользит по снегу и льду на брюхе, развивая скорость до 25 км/ч. Каждый день ей нужно съесть 15% своего веса.',
threats: ['Загрязнение водоёмов', 'Браконьерство', 'Вылов в рыболовные снасти'],
conservation: 'Охрана водоохранных зон. Запрет охоты. Очистка водоёмов.',
where_to_see: 'Реки по всей Беларуси, НП «Припятский»',
biomass: 9,
regions: ['brest','gomel','grodno','minsk','vitebsk','mogilev'],
trend: [{year:1990,count:2500},{year:2010,count:3000},{year:2024,count:3200}],
});
ids.bober = sp({
group: 'Млекопитающие', habitat: 'Река и озеро',
name_ru: 'Речной бобёр', name_be: 'Рачны бабёр', name_lat: 'Castor fiber',
category: 'LC', by_category: 'IV',
description: 'Крупнейший грызун Беларуси. Инженер экосистем: строит плотины, создаёт водоёмы, меняет ландшафт. Был истреблён к XIX веку и успешно реинтродуцирован. Сейчас численность в Беларуси — одна из крупнейших в Европе.',
fact: 'Бобёр — природный мелиоратор: его плотины повышают уровень грунтовых вод, создают новые биотопы для других видов.',
threats: ['Охота', 'Уничтожение прибрежной растительности'],
conservation: 'Регулирование охоты. Охрана пойменных лесов.',
where_to_see: 'Повсеместно у рек и каналов Беларуси',
biomass: 25,
regions: ['brest','gomel','grodno','minsk','vitebsk','mogilev'],
trend: [{year:1960,count:1000},{year:1990,count:20000},{year:2010,count:50000},{year:2024,count:60000}],
});
ids.zubatka = sp({
group: 'Млекопитающие', habitat: 'Широколиственный лес',
name_ru: 'Соня лесная', name_be: 'Лясная соня', name_lat: 'Dryomys nitedula',
category: 'VU', by_category: 'III',
description: 'Небольшой ночной грызун смешанных и лиственных лесов. Спит 7–8 месяцев в году — дольше всех млекопитающих Беларуси. Гнездо строит в дуплах или развилках ветвей на высоте 2–5 м.',
fact: 'Перед зимней спячкой соня увеличивает массу тела вдвое, накапливая жир. Температура тела во сне падает до 1°C.',
threats: ['Вырубка лиственных лесов', 'Снижение урожая орехов и желудей'],
conservation: 'Охрана старолесий. Вывешивание дуплянок.',
where_to_see: 'Беловежская пуща, Налибокская пуща',
biomass: 0.03,
regions: ['brest','grodno','minsk'],
});
ids.kosulya = sp({
group: 'Млекопитающие', habitat: 'Широколиственный лес',
name_ru: 'Косуля европейская', name_be: 'Еўрапейская казуля', name_lat: 'Capreolus capreolus',
category: 'LC', by_category: 'IV',
description: 'Самый распространённый олень Беларуси. Важнейший элемент пищевой цепи — основная добыча волка, рыси и лисы. Рожки самец сбрасывает ежегодно в ноябре. Летом держится одиночно, зимой образует небольшие группы.',
fact: 'Косуля — чемпион среди оленей по скорости: развивает до 60 км/ч. Рогатый самец защищает территорию площадью до 60 га.',
threats: ['Интенсивная охота', 'Браконьерство', 'Хищники'],
conservation: 'Регулирование охотничьей нагрузки. Мониторинг численности.',
where_to_see: 'Лесные массивы по всей Беларуси',
model: 'procedural', biomass: 25,
regions: ['brest','gomel','grodno','minsk','vitebsk','mogilev'],
trend: [{year:1990,count:80000},{year:2010,count:100000},{year:2024,count:110000}],
});
ids.volk = sp({
group: 'Млекопитающие', habitat: 'Хвойный лес',
name_ru: 'Волк', name_be: 'Воўк', name_lat: 'Canis lupus',
category: 'LC', by_category: 'IV',
description: 'Крупнейший хищник семейства псовых. Живёт стаями 5–12 особей с чёткой иерархией. Регулирует численность копытных, оздоровляя их популяции. В Беларуси отношение к волку противоречивое — он и символ дикой природы, и угроза для скота.',
fact: 'Волк за сутки способен пробежать до 70 км. Воя стаи достаточно, чтобы пометить территорию площадью в сотни км².',
threats: ['Истребление как вредителя', 'Фрагментация ареала'],
conservation: 'Мониторинг. Компенсации за ущерб скоту.',
where_to_see: 'Леса по всей Беларуси',
model: 'procedural', biomass: 40,
regions: ['brest','gomel','grodno','minsk','vitebsk','mogilev'],
trend: [{year:1990,count:1800},{year:2005,count:1500},{year:2015,count:1700},{year:2024,count:2000}],
});
/* ── РАСТЕНИЯ ────────────────────────────────────────────────────────────── */
ids.venerina = sp({
group: 'Растения', habitat: 'Широколиственный лес',
name_ru: 'Венерин башмачок настоящий', name_be: 'Венерын чаравічак', name_lat: 'Cypripedium calceolus',
category: 'EN', by_category: 'II',
description: 'Красивейшая дикая орхидея Беларуси. Цветок напоминает туфельку. Растёт в тенистых лиственных лесах. Живёт до 100 лет, но впервые цветёт только в 15–17 лет. Опыляется только мелкими пчёлами рода Andrena.',
fact: 'Венерин башмачок — самая долгоживущая травянистая орхидея. Одно растение в Германии было документально прослежено 100 лет на одном месте.',
threats: ['Незаконный сбор', 'Вырубка лесов', 'Уплотнение почвы', 'Зарастание кустарником'],
conservation: 'Охрана местонахождений. Культивирование в ботанических садах. Запрет сбора.',
where_to_see: 'Беловежская пуща (ур. Новый Двор), Налибокская пуща',
biomass: 0.05,
regions: ['brest','grodno','minsk','vitebsk'],
popdata: [[1990,5000,'КК РБ 1993'],[2005,3000,'КК РБ 2004'],[2015,2000,'КК РБ 2015'],[2024,1800,'Мониторинг 2024']],
trend: [{year:1990,count:5000},{year:2005,count:3000},{year:2015,count:2000},{year:2024,count:1800}],
});
ids.vodyanoy_oreh = sp({
group: 'Растения', habitat: 'Река и озеро',
name_ru: 'Водяной орех плавающий', name_be: 'Плывучы вадзяны арэх', name_lat: 'Trapa natans',
category: 'CR', by_category: 'I',
description: 'Реликт третичной флоры, живший на Земле ещё 100 миллионов лет назад. Плавающие листья образуют красивую розетку. Плоды съедобны, богаты крахмалом. Сохранился лишь в нескольких озёрах Полесья.',
fact: 'Водяной орех пережил динозавров. Его плоды с четырьмя «рогами» служили пищей людям ещё в каменном веке.',
threats: ['Загрязнение и эвтрофикация озёр', 'Колебания уровня воды', 'Конкуренция с другими водными растениями'],
conservation: 'Охрана озёр. Ограничение хозяйственной деятельности. Культивирование.',
where_to_see: 'Оз. Червоное (Брестская обл.), оз. Белое (Полесье)',
biomass: 0.02,
regions: ['brest','gomel'],
popdata: [[1990,15000,'КК РБ 1993'],[2005,8000,'КК РБ 2004'],[2015,4000,'КК РБ 2015'],[2024,2500,'Мониторинг 2024']],
trend: [{year:1990,count:15000},{year:2005,count:8000},{year:2015,count:4000},{year:2024,count:2500}],
});
ids.palchatokorenik = sp({
group: 'Растения', habitat: 'Болото',
name_ru: 'Пальчатокоренник мясокрасный', name_be: 'Мяснякраснаты пальчатакарэнік', name_lat: 'Dactylorhiza incarnata',
category: 'VU', by_category: 'III',
description: 'Болотная орхидея с плотным колосом розово-лиловых цветков. Растёт на низинных болотах и заболоченных лугах. Микоризный гриб необходим для прорастания семян — без него орхидея не может развиться.',
fact: 'Семена орхидей настолько малы (0,002 мм), что видны только под микроскопом. В одной коробочке содержится до 10 000 семян.',
threats: ['Осушение болот', 'Зарастание кустарником', 'Выпас скота'],
conservation: 'Охрана болотных массивов. Запрет мелиорации.',
where_to_see: 'Полесье, Налибокская пуща, Осиповичский район',
biomass: 0.03,
regions: ['brest','gomel','grodno','minsk'],
});
ids.molochai = sp({
group: 'Растения', habitat: 'Луг и поле',
name_ru: 'Прострел луговой', name_be: 'Сонца-трава', name_lat: 'Pulsatilla pratensis',
category: 'VU', by_category: 'III',
description: 'Один из первых весенних цветков. Тёмно-фиолетовые бокаловидные цветки появляются ещё при снеге. Покрыт шелковистыми волосками — защита от холода. Сильно сократился из-за сбора на букеты и распашки лугов.',
fact: 'Прострел — символ весны в Беларуси. Второе название «сон-трава» — легенда гласит, что заснувший рядом с ним видит вещие сны.',
threats: ['Сбор букетов', 'Распашка луговин', 'Сенокос до плодоношения'],
conservation: 'Запрет сбора. Охрана луговых заказников.',
where_to_see: 'Сосновые боры, остепнённые луга по всей Беларуси',
biomass: 0.01,
regions: ['brest','gomel','grodno','minsk','vitebsk','mogilev'],
});
ids.lobelia = sp({
group: 'Растения', habitat: 'Река и озеро',
name_ru: 'Лобелия Дортмана', name_be: 'Лобелія Дортмана', name_lat: 'Lobelia dortmanna',
category: 'EN', by_category: 'II',
description: 'Водное растение олиготрофных озёр. Растёт на песчаном дне на глубине 0,5–3 м. Цветонос возвышается над водой. Сверхчувствителен к загрязнению — индикатор кристально чистой воды.',
fact: 'Лобелия Дортмана поглощает CO₂ через корни из донных отложений, а не из воды — уникальный для растений способ.',
threats: ['Эвтрофикация озёр', 'Загрязнение воды', 'Рекреационная нагрузка'],
conservation: 'Охрана чистых озёр. Ограничение рекреации.',
where_to_see: 'Браславские озёра, озёра Нарочанской группы',
biomass: 0.01,
regions: ['vitebsk','minsk','grodno'],
});
ids.soldanella = sp({
group: 'Растения', habitat: 'Широколиственный лес',
name_ru: 'Лунник оживающий', name_be: 'Лунарыя ажываючая', name_lat: 'Lunaria rediviva',
category: 'VU', by_category: 'III',
description: 'Многолетнее растение тенистых влажных лесов. Серебристые эллиптические стручки используют для зимних букетов. Редчайшее в Беларуси — известно лишь несколько популяций.',
fact: 'Название «лунник» происходит от формы плодов — круглых, как луна. После высыхания они становятся прозрачными и серебристыми.',
threats: ['Сбор для флористики', 'Вырубка тенистых лесов'],
conservation: 'Запрет сбора. Охрана местонахождений.',
where_to_see: 'Налибокская пуща, Беловежская пуща',
biomass: 0.1,
regions: ['grodno','minsk','brest'],
});
ids.matik = sp({
group: 'Растения', habitat: 'Болото',
name_ru: 'Шейхцерия болотная', name_be: 'Балотная шэйхцэрыя', name_lat: 'Scheuchzeria palustris',
category: 'VU', by_category: 'III',
description: 'Реликтовое травянистое растение верховых болот. Растёт в сфагновых зарослях у мочажин. Индикатор ненарушенных болотных экосистем. При осушении болот исчезает первым.',
fact: 'Шейхцерия существует более 10 000 лет — с конца ледникового периода — практически не изменившись.',
threats: ['Осушение болот', 'Торфоразработки'],
conservation: 'Охрана верховых болот. Запрет торфоразработок.',
where_to_see: 'Ельнянский заказник, Освейское болото',
biomass: 0.01,
regions: ['vitebsk','minsk','gomel'],
});
/* ── НАСЕКОМЫЕ ────────────────────────────────────────────────────────────── */
ids.makhаon = sp({
group: 'Насекомые', habitat: 'Луг и поле',
name_ru: 'Махаон', name_be: 'Махаон', name_lat: 'Papilio machaon',
category: 'NT', by_category: 'IV',
description: 'Одна из крупнейших и красивейших бабочек Беларуси. Размах крыльев до 95 мм. Жёлтые крылья с чёрным рисунком и синей каймой. Гусеница поедает листья зонтичных растений. Стал редок из-за распашки лугов с дикими зонтичными.',
fact: 'Хвостики-отростки на задних крыльях отвлекают хищников — птицы атакуют «ненастоящую голову».',
threats: ['Распашка лугов с зонтичными растениями', 'Применение пестицидов', 'Сбор коллекционерами'],
conservation: 'Сохранение лугов с дикими зонтичными. Запрет отлова.',
where_to_see: 'Луга и опушки по всей Беларуси, Полесье',
model: 'procedural', biomass: 0.002,
regions: ['brest','gomel','grodno','minsk','vitebsk','mogilev'],
trend: [{year:1990,count:100000},{year:2010,count:50000},{year:2024,count:30000}],
});
ids.podolik = sp({
group: 'Насекомые', habitat: 'Широколиственный лес',
name_ru: 'Жук-олень', name_be: 'Жук-алень', name_lat: 'Lucanus cervus',
category: 'VU', by_category: 'II',
description: 'Крупнейший жук Беларуси. Самцы достигают 9 см. «Рога» — увеличенные жвала, используемые в турнирных боях. Личинка развивается в мёртвой древесине дуба 5–8 лет. Исчезает вместе со старыми дубравами.',
fact: 'Жук-олень умеет летать, несмотря на тяжёлую «броню». Взрослый жук не ест — живёт всего 1–2 месяца за счёт жировых запасов.',
threats: ['Вырубка старых дубрав', 'Уборка мёртвой древесины', 'Декоративный сбор'],
conservation: 'Сохранение старовозрастных дубрав. Оставление валежника.',
where_to_see: 'Беловежская пуща, Налибокская пуща',
model: 'procedural', biomass: 0.008,
regions: ['brest','grodno','minsk'],
});
ids.apollon = sp({
group: 'Насекомые', habitat: 'Луг и поле',
name_ru: 'Аполлон', name_be: 'Апалон', name_lat: 'Parnassius apollo',
category: 'EN', by_category: 'I',
description: 'Крупная дневная бабочка с полупрозрачными белыми крыльями и красными глазками. Считается одним из красивейших насекомых Европы. В Беларуси сохранились единичные изолированные популяции. Гусеница питается очитком.',
fact: 'Аполлон — первая бабочка, включённая в Конвенцию CITES (запрет международной торговли). Его коллекционируют вопреки всем запретам.',
threats: ['Зарастание каменистых склонов кустарником', 'Коллекционирование', 'Применение пестицидов'],
conservation: 'Строгая охрана популяций. Борьба с зарастанием склонов.',
where_to_see: 'Окрестности Гродно (единственное местонахождение в РБ)',
biomass: 0.003,
regions: ['grodno'],
popdata: [[2000,500,'КК РБ 2004'],[2015,300,'КК РБ 2015'],[2024,200,'Мониторинг 2024']],
trend: [{year:2000,count:500},{year:2015,count:300},{year:2024,count:200}],
});
ids.bogomol = sp({
group: 'Насекомые', habitat: 'Луг и поле',
name_ru: 'Богомол обыкновенный', name_be: 'Звычайны багамол', name_lat: 'Mantis religiosa',
category: 'EN', by_category: 'II',
description: 'Самый известный хищный кузнечик. Передние ноги — ловчий аппарат, реагирующий быстрее, чем успевает моргнуть человек. Самка после спаривания поедает самца. В Беларуси — на северной границе ареала.',
fact: 'Богомол — единственное насекомое, способное поворачивать голову на 180°. Реакция захвата добычи занимает 0,05 секунды.',
threats: ['Применение пестицидов', 'Распашка сухих лугов', 'Изменение климата'],
conservation: 'Охрана сухих травянистых местообитаний.',
where_to_see: 'Южная Беларусь — Брестская, Гомельская обл.',
biomass: 0.004,
regions: ['brest','gomel'],
});
ids.stag_beetle2 = sp({
group: 'Насекомые', habitat: 'Широколиственный лес',
name_ru: 'Усач мускусный', name_be: 'Мускусны вусач', name_lat: 'Aromia moschata',
category: 'VU', by_category: 'III',
description: 'Ярко-зелёный жук с металлическим блеском. Издаёт сильный запах мускуса, слышный за несколько метров. Личинки развиваются в старых ивах. Встречается вдоль рек с ивняком.',
fact: 'Мускусный усач — один из немногих жуков, использующих химическую сигнализацию для общения с партнёрами.',
threats: ['Обрезка и уборка старых ив', 'Осушение пойм'],
conservation: 'Сохранение старых ив вдоль рек.',
where_to_see: 'Поймы рек Полесья, долина Немана',
biomass: 0.004,
regions: ['brest','gomel','grodno','minsk'],
});
/* ── РЫБЫ ─────────────────────────────────────────────────────────────────── */
ids.sterlyadj = sp({
group: 'Рыбы', habitat: 'Река и озеро',
name_ru: 'Стерлядь', name_be: 'Асётр-стэрлядзь', name_lat: 'Acipenser ruthenus',
category: 'EN', by_category: 'II',
description: 'Самый мелкий осетр. Реликт доледникового периода — осетры появились 250 миллионов лет назад. В Беларуси исчезала в XX веке и восстанавливается искусственным зарыблением. Ценнейшая промысловая рыба.',
fact: 'Осетры — живые ископаемые. Их форма тела не менялась 200 миллионов лет. Стерлядь доживает до 30 лет.',
threats: ['Браконьерство', 'Строительство плотин', 'Загрязнение рек', 'Изъятие из русел гравия'],
conservation: 'Искусственное воспроизводство. Рыборазводные заводы. Запрет вылова.',
where_to_see: 'Р. Днепр, р. Припять, р. Сож (редко)',
biomass: 0.8,
regions: ['gomel','mogilev','brest'],
popdata: [[1990,100,'КК РБ 1993'],[2010,500,'после зарыбления'],[2024,1500,'Мониторинг 2024']],
trend: [{year:1990,count:100},{year:2010,count:500},{year:2024,count:1500}],
});
ids.minog = sp({
group: 'Рыбы', habitat: 'Река и озеро',
name_ru: 'Ручьевая минога', name_be: 'Ручаёвая мінога', name_lat: 'Lampetra planeri',
category: 'VU', by_category: 'III',
description: 'Древнейший позвоночный — миноги появились 360 миллионов лет назад, ещё до динозавров. Не имеет челюстей. Личинка (амоцет) живёт в иле 3–7 лет с закрытыми глазами. Взрослая минога не питается и гибнет после нереста.',
fact: 'Ручьевая минога — «живое ископаемое», практически не изменившееся за 360 миллионов лет. Её называют самым примитивным позвоночным.',
threats: ['Загрязнение малых рек', 'Заиление нерестилищ', 'Мелиорация'],
conservation: 'Охрана малых рек. Ограничение мелиорации.',
where_to_see: 'Чистые малые реки Витебской, Гродненской областей',
biomass: 0.02,
regions: ['vitebsk','grodno','minsk','mogilev'],
});
ids.usatch = sp({
group: 'Рыбы', habitat: 'Река и озеро',
name_ru: 'Усач обыкновенный', name_be: 'Марэна', name_lat: 'Barbus barbus',
category: 'VU', by_category: 'III',
description: 'Крупная реофильная рыба быстрых рек. Достигает 90 см. Ищет пищу на дне с помощью чувствительных усиков. Нерестится на каменистых перекатах — именно их исчезновение главная угроза.',
fact: 'Икра усача ядовита для теплокровных — содержит ихтиотоксин. Местные рыбаки знали об этом и никогда её не ели.',
threats: ['Добыча гравия', 'Строительство плотин', 'Загрязнение'],
conservation: 'Сохранение каменистых перекатов. Запрет добычи гравия.',
where_to_see: 'Р. Западный Буг, р. Нёман',
biomass: 1.5,
regions: ['brest','grodno'],
});
ids.rylets = sp({
group: 'Рыбы', habitat: 'Река и озеро',
name_ru: 'Подуст обыкновенный', name_be: 'Падуст', name_lat: 'Chondrostoma nasus',
category: 'EN', by_category: 'II',
description: 'Стайная рыба горных и предгорных рек. В Беларуси — на восточной границе ареала. Питается водорослями и органикой, соскребая их нижнечелюстным роговым чехликом со дна.',
fact: 'Подуст чистит дно реки, поедая водоросли — выполняет роль «пылесоса» экосистемы.',
threats: ['Загрязнение', 'Уничтожение нерестовых субстратов', 'Строительство плотин'],
conservation: 'Охрана нерестилищ. Запрет строительства.',
where_to_see: 'Р. Западный Буг (редко)',
biomass: 0.3,
regions: ['brest'],
});
/* ── РЕПТИЛИИ И АМФИБИИ ──────────────────────────────────────────────────── */
ids.cherepaha = sp({
group: 'Рептилии и амфибии', habitat: 'Болото',
name_ru: 'Черепаха болотная', name_be: 'Балотная чарапаха', name_lat: 'Emys orbicularis',
category: 'VU', by_category: 'II',
description: 'Единственная дикая черепаха Беларуси. Греется на берегах заболоченных водоёмов. При опасности мгновенно ныряет. Живёт до 100 лет. Откладывает яйца в прогретую почву. Самая северная черепаха Европы.',
fact: 'Черепаха болотная определяет пол потомства температурой гнезда: при +28°C рождаются самки, при +26°C — самцы.',
threats: ['Осушение болот', 'Уничтожение берегов', 'Гибель на дорогах'],
conservation: 'Охрана водно-болотных угодий. Туннели под дорогами.',
where_to_see: 'Полесье (Брестская, Гомельская обл.)',
model: 'procedural', biomass: 0.5,
regions: ['brest','gomel','grodno'],
popdata: [[1990,10000,'КК РБ 1993'],[2005,7000,'КК РБ 2004'],[2015,5000,'КК РБ 2015'],[2024,4000,'Мониторинг 2024']],
trend: [{year:1990,count:10000},{year:2005,count:7000},{year:2015,count:5000},{year:2024,count:4000}],
});
ids.medyanka = sp({
group: 'Рептилии и амфибии', habitat: 'Широколиственный лес',
name_ru: 'Медянка', name_be: 'Медзянка', name_lat: 'Coronella austriaca',
category: 'VU', by_category: 'III',
description: 'Небольшая безвредная змея. Часто принимается за гадюку и уничтожается. Специализируется на ящерицах — конкурирует с гадюкой за пищу, но не ядовита. Гладкая чешуя с медным блеском.',
fact: 'Медянка сжимает добычу как удав — потому что ящерицы не боятся яда. Укус человека абсолютно безвреден.',
threats: ['Уничтожение из-за путаницы с гадюкой', 'Уничтожение сухих опушек'],
conservation: 'Просветительская работа. Охрана сухих опушек и вырубок.',
where_to_see: 'Сухие сосновые леса Полесья',
biomass: 0.05,
regions: ['brest','gomel','grodno','minsk'],
});
ids.trit = sp({
group: 'Рептилии и амфибии', habitat: 'Болото',
name_ru: 'Тритон гребенчатый', name_be: 'Грэбеньчасты трытон', name_lat: 'Triturus cristatus',
category: 'NT', by_category: 'III',
description: 'Крупнейший тритон Беларуси. В брачный период самец развивает высокий зубчатый гребень от затылка до хвоста. Днём прячется в укрытиях, ночью охотится. Живёт у лесных водоёмов.',
fact: 'Гребень самца — не для плавания, а для привлечения самок. В воде он играет роль «хвоста павлина».',
threats: ['Мелиорация', 'Гибель на дорогах', 'Инвазивные рыбы в водоёмах'],
conservation: 'Охрана прудов и болот. Ограничение вселения рыбы.',
where_to_see: 'Лесные пруды по всей Беларуси',
biomass: 0.02,
regions: ['brest','gomel','grodno','minsk','vitebsk','mogilev'],
});
/* ── ГРИБЫ ────────────────────────────────────────────────────────────────── */
ids.truffe = sp({
group: 'Грибы', habitat: 'Широколиственный лес',
name_ru: 'Трюфель летний', name_be: 'Летні трувель', name_lat: 'Tuber aestivum',
category: 'EN', by_category: 'I',
description: 'Подземный гриб — деликатес с неповторимым ароматом. Растёт в симбиозе с корнями дуба и бука. Плодовое тело полностью под землёй. В Беларуси — редчайший вид, известны единичные находки.',
fact: 'Аромат трюфеля обусловлен андростенолом — феромоном, похожим на половой гормон кабана. Именно поэтому их традиционно ищут со свиньями.',
threats: ['Вырубка дубрав', 'Уплотнение почвы', 'Сбор до созревания спор'],
conservation: 'Охрана старых дубрав. Запрет сбора.',
where_to_see: 'Беловежская пуща (единичные находки)',
biomass: 0.1,
regions: ['brest'],
});
ids.mukhomor = sp({
group: 'Грибы', habitat: 'Хвойный лес',
name_ru: 'Решёточник красный', name_be: 'Чырвоная кратчатка', name_lat: 'Clathrus ruber',
category: 'CR', by_category: 'I',
description: 'Невероятный гриб — красная ажурная решётка. Вылупляется из белого яйца за несколько часов. Тухлый запах привлекает мух, распространяющих споры. Средиземноморский вид, в Беларуси единственная находка в 2019 г.',
fact: 'Решёточник — один из самых необычных грибов мира. Его рост занимает всего 4–6 часов. Живёт 1–2 дня.',
threats: ['Недостаточность тепла', 'Уничтожение при сборе'],
conservation: 'Строгая охрана единственного местонахождения.',
where_to_see: 'Гродненский район (единственная находка)',
biomass: 0.05,
regions: ['grodno'],
});
ids.sparassis = sp({
group: 'Грибы', habitat: 'Хвойный лес',
name_ru: 'Спарасис курчавый (грибная капуста)', name_be: 'Кучаравы спарасіс', name_lat: 'Sparassis crispa',
category: 'VU', by_category: 'III',
description: 'Гриб, похожий на кочан цветной капусты кремового цвета. Диаметр до 50 см, вес до 5 кг. Растёт у оснований старых сосен. Съедобен и очень вкусен. Обладает противоопухолевыми свойствами.',
fact: 'Спарасис содержит β-1,3-глюкан — вещество, доказанно усиливающее иммунитет и применяемое в японской медицине.',
threats: ['Вырубка старых сосновых лесов', 'Сбор до созревания спор'],
conservation: 'Сохранение старых сосняков. Ограничение сбора.',
where_to_see: 'Старые сосняки Беларуси',
biomass: 1.5,
regions: ['brest','gomel','grodno','minsk','vitebsk','mogilev'],
});
/* ── МХИ И ЛИШАЙНИКИ ─────────────────────────────────────────────────────── */
ids.lobaria = sp({
group: 'Мхи и лишайники', habitat: 'Широколиственный лес',
name_ru: 'Лобария лёгочная', name_be: 'Лёгачная лобарыя', name_lat: 'Lobaria pulmonaria',
category: 'VU', by_category: 'III',
description: 'Крупный лишайник — симбиоз гриба, водоросли и цианобактерии. Таллом похож на лёгочную ткань. Растёт только в старых нетронутых лесах с постоянной влажностью. Индикатор экологической ценности леса.',
fact: 'Лобария лёгочная фиксирует атмосферный азот. Раньше её применяли при болезнях лёгких — отсюда и название.',
threats: ['Вырубка старолесий', 'Загрязнение воздуха (SO₂)', 'Изменение микроклимата'],
conservation: 'Охрана старовозрастных лесов.',
where_to_see: 'Беловежская пуща, Налибокская пуща',
biomass: 0.05,
regions: ['brest','grodno'],
});
ids.kladon = sp({
group: 'Мхи и лишайники', habitat: 'Хвойный лес',
name_ru: 'Кладония звёздчатая', name_be: 'Зорчатая кладонія', name_lat: 'Cladonia stellaris',
category: 'VU', by_category: 'III',
description: 'Напочвенный кустистый лишайник, образующий белые кочки в сосновых борах. Один из компонентов оленьего мха — важный корм для оленей зимой. Растёт крайне медленно — 1–3 мм в год.',
fact: 'Ковёр из кладонии в сосняке может быть возрастом 200–300 лет. Протяжённость "колонии" до 10 м.',
threats: ['Пожары', 'Вытаптывание', 'Загрязнение воздуха'],
conservation: 'Охрана сухих сосняков. Противопожарные меры.',
where_to_see: 'Сухие боры Витебской, Минской, Гомельской областей',
biomass: 0.1,
regions: ['vitebsk','minsk','gomel','brest'],
});
db.exec('COMMIT');
console.log(`✓ Виды засеяны (${Object.keys(ids).length} видов)`);
} catch (e) {
db.exec('ROLLBACK');
console.error('Ошибка при вставке видов:', e.message);
process.exit(1);
}
/* ══════════════════════════════════════════════════════════════════════════
ПИЩЕВАЯ СЕТЬ
══════════════════════════════════════════════════════════════════════════ */
const insWeb = db.prepare('INSERT OR IGNORE INTO rb_food_web (predator_id, prey_id, strength) VALUES (?,?,?)');
db.exec('BEGIN');
const web = [
// Орлан-белохвост охотится на рыб, водоплавающих птиц
[ids.orlan, ids.sterlyadj, 0.6],
[ids.orlan, ids.usatch, 0.3],
[ids.orlan, ids.krasnozobaya_kazarka, 0.1],
// Скопа — только рыба
[ids.skopa, ids.sterlyadj, 0.7],
[ids.skopa, ids.usatch, 0.3],
[ids.skopa, ids.rylets, 0.2],
// Змееяд — рептилии
[ids.zmeeyed, ids.medyanka, 0.7],
[ids.zmeeyed, ids.cherepaha, 0.1],
// Филин
[ids.filin, ids.kosulya, 0.2],
[ids.filin, ids.bober, 0.1],
[ids.filin, ids.vydra, 0.1],
// Большой подорлик
[ids.zhuravl_seryi2, ids.trit, 0.5],
[ids.zhuravl_seryi2, ids.cherepaha, 0.3],
[ids.zhuravl_seryi2, ids.minog, 0.2],
// Рысь — косули, заяц, бобёр
[ids.rys, ids.kosulya, 0.8],
[ids.rys, ids.bober, 0.1],
[ids.rys, ids.zubatka, 0.05],
// Волк
[ids.volk, ids.kosulya, 0.7],
[ids.volk, ids.zubr, 0.1],
[ids.volk, ids.bober, 0.2],
// Выдра — рыба, лягушки
[ids.vydra, ids.sterlyadj, 0.4],
[ids.vydra, ids.minog, 0.3],
[ids.vydra, ids.usatch, 0.3],
// Норка — рыба, раки
[ids.norka, ids.rylets, 0.4],
[ids.norka, ids.minog, 0.3],
[ids.norka, ids.trit, 0.3],
// Бурый медведь — всеяден
[ids.medved, ids.bober, 0.15],
[ids.medved, ids.lobelia, 0.2],
// Журавль — насекомые, лягушки, зерно
[ids.zhuravl, ids.trit, 0.3],
[ids.zhuravl, ids.bogomol, 0.2],
// Черный аист — рыба, лягушки
[ids.chorny_aist, ids.minog, 0.4],
[ids.chorny_aist, ids.sterlyadj, 0.2],
[ids.chorny_aist, ids.trit, 0.4],
// Коростель — насекомые
[ids.dergach, ids.makhаon, 0.1],
[ids.dergach, ids.bogomol, 0.1],
// Жук-олень — мёртвая дубовая древесина
[ids.podolik, ids.lobaria, 0.1],
];
web.forEach(([p, q, s]) => insWeb.run(p, q, s));
db.exec('COMMIT');
console.log(`✓ Пищевая сеть: ${web.length} связей`);
/* ══════════════════════════════════════════════════════════════════════════
КВЕСТЫ
══════════════════════════════════════════════════════════════════════════ */
const insQ = db.prepare('INSERT INTO rb_quests (title, description, species_ids, xp_reward, badge_slug) VALUES (?,?,?,?,?)');
[
[
'Спасти зубра',
'Узнайте историю зубра — от истребления до триумфального возвращения. Пройдите 5 этапов: история вида, генетика, пищевая сеть, ареал и современная охрана.',
JSON.stringify([ids.zubr, ids.kosulya, ids.volk, ids.rys]),
200, 'quest_zubr'
],
[
'Путь чёрного аиста',
'Проследите миграцию чёрного аиста из белорусских болот в Африку. Изучите биомы, угрозы на маршруте и меры охраны.',
JSON.stringify([ids.chorny_aist, ids.zhuravl, ids.cherepaha, ids.trit]),
180, 'quest_black_stork'
],
[
'Река живёт',
'Стерлядь — символ здоровья белорусских рек. Узнайте, почему исчезают осетры и как восстановить их популяцию.',
JSON.stringify([ids.sterlyadj, ids.minog, ids.usatch, ids.vydra, ids.norka]),
160, 'quest_river'
],
[
'Последний лес',
'Беловежская пуща — последний первобытный лес Европы. Исследуйте её обитателей и поймите, почему этот лес уникален.',
JSON.stringify([ids.zubr, ids.rys, ids.filin, ids.venerina, ids.podolik, ids.truffe]),
250, 'quest_forest'
],
].forEach(args => insQ.run(...args));
console.log('✓ Квесты');
console.log('\n✅ seed-red-book.js завершён успешно!');
console.log(` Видов: ${Object.keys(ids).length}`);
console.log(' Запустите: node src/db/migrate.js && node src/db/seed-red-book.js');
File diff suppressed because it is too large Load Diff
+63
View File
@@ -0,0 +1,63 @@
const fs = require('fs');
const path = require('path');
const db = require('./db');
const dataDir = path.join(__dirname, '../../data');
const files = fs.readdirSync(dataDir).filter(f => f.endsWith('.json'));
for (const file of files) {
const data = JSON.parse(fs.readFileSync(path.join(dataDir, file), 'utf8'));
console.log(`\nSeeding: ${file} (subject: ${data.subject})`);
const subject = db.prepare('SELECT id FROM subjects WHERE slug = ?').get(data.subject);
if (!subject) {
console.error(`Subject "${data.subject}" not found. Run migrate first.`);
continue;
}
const subject_id = subject.id;
/* topics */
const topicMap = {};
const insertTopic = db.prepare(
'INSERT OR IGNORE INTO topics (subject_id, name, order_index) VALUES (?, ?, ?)'
);
for (const t of data.topics) {
insertTopic.run(subject_id, t.name, t.order);
const row = db.prepare('SELECT id FROM topics WHERE subject_id = ? AND name = ?')
.get(subject_id, t.name);
topicMap[t.name] = row.id;
}
/* questions + options inside a transaction (idempotent — skip existing by text) */
const checkQ = db.prepare('SELECT id FROM questions WHERE subject_id = ? AND text = ?');
const insertQ = db.prepare(
'INSERT INTO questions (subject_id, topic_id, text, difficulty, explanation) VALUES (?, ?, ?, ?, ?)'
);
const insertO = db.prepare(
'INSERT INTO options (question_id, text, is_correct, order_index) VALUES (?, ?, ?, ?)'
);
let added = 0, skipped = 0;
db.exec('BEGIN');
try {
for (const q of data.questions) {
if (checkQ.get(subject_id, q.text)) { skipped++; continue; }
const topic_id = topicMap[q.topic] ?? null;
insertQ.run(subject_id, topic_id, q.text, q.difficulty, q.explanation ?? null);
const { id: question_id } = db.prepare('SELECT last_insert_rowid() AS id').get();
for (let i = 0; i < q.options.length; i++) {
insertO.run(question_id, q.options[i], i === q.answer ? 1 : 0, i);
}
process.stdout.write('.');
added++;
}
db.exec('COMMIT');
} catch (err) {
db.exec('ROLLBACK');
throw err;
}
console.log(`\n✓ Добавлено: ${added}, пропущено (уже есть): ${skipped}`);
}
console.log('\nSeed complete.');