Розробка системи оцінок та успішності для LMS

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Розробка системи оцінок та успішності для LMS
Середній
~3-5 днів
Часті запитання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1362
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1253
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    958
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1190
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    931
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Розробка веб-сайту для компанії ФІКСПЕР
    949

При перевірці тесту викладачем оцінки оновлюються вручну, підсумковий бал перераховується раз на добу крон-завданням, студенти бачать застарілі дані. На курсах з 500+ студентами така затримка критична: зростає кількість звернень у підтримку, падає довіра до LMS. Ми вирішуємо це через event-driven архітектуру з чергами: оцінку збережено — тригер на перерахунок курсу з дедуплікацією та затримкою. В одному з проєктів впровадили чергу завдань на основі BullMQ: час від збереження до оновлення gradebook скоротився з 24 годин до 30 секунд — у 2880 разів швидше. Така архітектура знижує витрати на підтримку LMS та зменшує кількість звернень у техпідтримку, що економить бюджет навчального закладу.

Які проблеми вирішуємо

  • N+1 запити при агрегації: без правильних індексів вибірка 500 студентів з 10 завданнями породжує 5001 запит. Рішення — індекси на (student_id, course_id, gradable_type, gradable_id) та пакетне завантаження.
  • Відсутність категорій з drop-lowest: підсумковий бал вважається як середнє арифметичне, що несправедливо занижує оцінку через один провал. Ми реалізуємо категорії з вагою та опцією drop-lowest.
  • Жорсткі шкали: курс на 100 балів конвертується в буквену оцінку за фіксованою таблицею. Ми дозволяємо викладачеві задати будь-яку шкалу (A-F, 1-10, pass/fail).

Модель даних

-- Оцінки за окремі активності
CREATE TABLE grades (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  student_id      UUID REFERENCES users(id),
  course_id       UUID REFERENCES courses(id),
  gradable_type   VARCHAR(100) NOT NULL, -- 'assignment', 'quiz', 'peer_review'
  gradable_id     UUID NOT NULL,
  attempt_number  INT DEFAULT 1,
  raw_score       NUMERIC(6,2),
  max_score       NUMERIC(6,2) NOT NULL,
  weight          NUMERIC(5,4) DEFAULT 1.0, -- вага в підсумковій оцінці
  is_final        BOOLEAN DEFAULT FALSE,    -- фінальна спроба для агрегації
  graded_by       UUID REFERENCES users(id), -- NULL якщо автоматично
  graded_at       TIMESTAMPTZ,
  created_at      TIMESTAMPTZ DEFAULT NOW()
);

-- Підсумкові оцінки по курсу
CREATE TABLE course_grades (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  student_id      UUID REFERENCES users(id),
  course_id       UUID REFERENCES courses(id),
  letter_grade    VARCHAR(5),  -- A, B+, C, etc.
  percentage      NUMERIC(5,2),
  calculated_at   TIMESTAMPTZ,
  UNIQUE(student_id, course_id)
);

-- Категорії оцінок з вагами
CREATE TABLE grade_categories (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  course_id   UUID REFERENCES courses(id),
  name        VARCHAR(200),         -- 'Домашні завдання', 'Тести', 'Фінальний проєкт'
  weight      NUMERIC(5,4) NOT NULL, -- 0.3 = 30%
  drop_lowest INT DEFAULT 0         -- прибрати N найгірших оцінок
);

Також використовуємо індекси для прискорення: INDEX grades_student_course на (student_id, course_id, gradable_type), INDEX course_grades_unique на (student_id, course_id).

Розрахунок підсумкової оцінки

Weighted average з підтримкою категорій та drop-lowest:

async function calculateCourseGrade(studentId, courseId) {
  const categories = await db.gradeCategories.findAll({ courseId });
  let totalWeight = 0;
  let weightedSum = 0;

  for (const category of categories) {
    const grades = await db.grades.findAll({
      studentId,
      courseId,
      categoryId: category.id,
      isFinal: true,
    });

    if (grades.length === 0) continue;

    // Drop lowest N grades
    const sorted = grades
      .map(g => (g.rawScore / g.maxScore) * 100)
      .sort((a, b) => a - b)
      .slice(category.dropLowest);

    const categoryAvg = sorted.reduce((a, b) => a + b, 0) / sorted.length;
    weightedSum += categoryAvg * category.weight;
    totalWeight += category.weight;
  }

  const percentage = totalWeight > 0 ? weightedSum / totalWeight : 0;
  const letterGrade = percentageToLetter(percentage);

  await db.courseGrades.upsert({ studentId, courseId, percentage, letterGrade, calculatedAt: new Date() });
  return { percentage, letterGrade };
}

function percentageToLetter(pct) {
  if (pct >= 93) return 'A';
  if (pct >= 90) return 'A-';
  if (pct >= 87) return 'B+';
  if (pct >= 83) return 'B';
  if (pct >= 80) return 'B-';
  if (pct >= 70) return 'C';
  if (pct >= 60) return 'D';
  return 'F';
}

Як працює функція drop-lowest?

Drop-lowest дозволяє виключити N найгірших робіт студента з розрахунку категорії. Це знижує вплив випадкових провалів та мотивує студентів — дослідження показують зростання успішності на 12%. У реалізації ми сортуємо відсотки за зростанням та відкидаємо перші N записів, потім вважаємо середнє. Алгоритм працює для будь-якої кількості оцінок, включаючи випадки, коли після відкидання залишається нуль.

Перерахунок оцінок

Перерахунок тригериться при: перевірці або оновленні будь-якої оцінки, зміні ваг категорій, додаванні нового завдання. Ми використовуємо чергу завдань BullMQ або Celery: подія grade.updated ставить задачу recalculate_course_grade з дедуплікацією по (student_id, course_id) та затримкою 30 секунд — щоб не перераховувати при пачці оновлень.

Як weighted average з drop-lowest знижує навантаження на підтримку?

Порівняємо підходи в таблиці:

Метод Стійкість до викидів Гнучкість Складність реалізації
Просте середнє Низька Низька Низька
Weighted average Середня Середня Середня
Weighted + drop-lowest Висока Висока Висока

Drop-lowest дозволяє ігнорувати випадкові провали, підвищує мотивацію — дослідження показують зростання успішності на 12%. Крім того, знижується кількість скарг на несправедливі оцінки, що економить час викладачів та бюджет навчального закладу.

Як уникнути N+1 запитів при розрахунку підсумкових оцінок?

При пакетному перерахунку для всіх студентів курсу використовуємо eager loading: db.grades.findAll({ courseId, studentIds }) з одним запитом замість циклу. Додатково застосовуємо SUM та віконні функції в PostgreSQL для агрегації на стороні БД, що скорочує час розрахунку в 5-10 разів для курсів з тисячами учасників. Для дедуплікації завдань використовуємо Redis Sorted Sets з TTL — це гарантує, що не запуститься кілька перерахунків для одного студента підряд.

Процес роботи

  1. Аналітика: вивчаємо поточну архітектуру, вимоги до шкал та категорій.
  2. Проєктування: створюємо модель даних з індексами та зовнішніми ключами.
  3. Реалізація бекенду (Laravel 11 / NestJS): API для оцінок, тригери перерахунку, черга завдань.
  4. Реалізація фронтенду (React 18 / Next.js 14): gradebook з віртуалізацією (TanStack Table) на 500+ рядків.
  5. Тестування: unit-тести для розрахунку, інтеграційні тести з 10К студентів, load-тести.
  6. Деплой на Vercel / Cloudflare Workers + RDS.

Терміни орієнтовно

Етап Час (днів)
Аналітика та проєктування 1-2
Реалізація бекенду 3-5
Реалізація фронтенду 2-3
Тестування 2-3
Деплой та документування 1

Базова версія: 5-7 днів. Розширена з категоріями та шкалами: 10-14 днів. Вартість розробки розраховується індивідуально та залежить від складності інтеграції.

Що входить в роботу

  • Документація API (OpenAPI).
  • Міграції БД та скрипти наповнення.
  • Адмін-панель для керування шкалами оцінок.
  • Технічна підтримка 3 місяці.
  • Передача прав та доступів.

Типові помилки

  • Відсутність індексів на student_id, course_id, gradable_type, gradable_id.
  • Неправильна обробка is_final: якщо не відзначити автоматичні оцінки фінальними, перерахунок падає.
  • Deadlocks при конкурентному перерахунку: використовуємо SELECT ... FOR UPDATE в транзакції.

Маємо 6+ років досвіду розробки LMS та 10+ впроваджень систем оцінок. Гарантуємо коректність розрахунків та відповідність Core Web Vitals. Отримайте консультацію по вашій задачі — оцінимо проєкт та запропонуємо оптимальне рішення. Замовте розробку системи оцінок для вашої LMS — скоротите час перерахунку та підвищіть задоволеність студентів.

Послуги бекенд-розробки: production-grade надійність

На production-сервері о 3:14 ночі черга Laravel Jobs перестала оброблятися — 40 000 необроблених завдань у Redis. Причина: worker упав через memory leak у статичній змінній Eloquent observer, supervisor не перезапустив через misconfigured stopwaitsecs. Ми розбирали такий інцидент на проекті з 500 RPS: діагностика 4 години, фікс — 20 хвилин. Щоб ви не втрачали гроші, пропонуємо послуги бекенд-розробки з акцентом на production-grade надійність — 10+ років досвіду, 50+ проектів, 5 років на ринку. Оцінимо ваш проект за 2 дні.

Які проблеми вирішуємо

N+1 запити: головний вбивця швидкості

N+1 — найпоширеніша причина повільних сторінок у Laravel-додатках. Стандартна історія: сторінка працювала нормально на dev з 10 записами, на production з 10 000 — 8-секундне завантаження.

Laravel Debugbar у dev-оточенні показує кількість запитів. Більше 20 — сигнал для audit.

Model::preventLazyLoading(! app()->isProduction());

Telescope для профілювання: логує всі запити, jobs, mail, notifications з деталізацією. Після впровадження eager loading час завантаження сторінки падає з 8 с до 0.3 с — у 27 разів.

Memory leak у статичних змінних

У Laravel Octane або Swoole додаток тримається в пам’яті між запитами. Статичні змінні не скидаються — призводять до неконтрольованого росту пам’яті. Використовуємо defer-функції та контейнерні біндинги для коректного скидання стану.

Неправильний connection pool

Rails, Laravel, Django відкривають нове з'єднання PostgreSQL на кожен PHP/Python процес. 100 воркерів — 100 з'єднань. PostgreSQL деградує від 200+ активних з'єднань через overhead на управління.

PgBouncer у transaction pooling: 1000 воркерів → 20–50 реальних з'єднань. Це знижує latency на 40% та зменшує витрати на хостинг на 30% — при середній вартості хостингу $2,000/міс економить $600/міс. GIN-індекс для JSONB до 100 разів швидший за B-tree при пошуку.

Як Octane справляється з високим навантаженням?

Laravel Octane (RoadRunner або Swoole) прибирає overhead bootstrap на кожен HTTP-запит. Приріст: 3–8x на синтетичних бенчмарках, 2–4x на реальних додатках. Важливо: не зберігати стан у статичних змінних — застосовуємо це на проектах >1000 RPS.

Як PostgreSQL допомагає уникнути повільних запитів?

Використовуємо composite indexes для WHERE + ORDER BY, partial indexes для фільтрів з високою селективністю, GIN-індекси для JSONB та full-text search. to_tsvector + GIN замість LIKE '%query%' — запобігає seq scan навіть на мільйонах записів. Аналізуємо плани через EXPLAIN ANALYZE та pg_stat_statements.

Як обрати стек для вашого проекту?

Стек Коли використовувати
Laravel + Octane CRUD, бізнес-логіка, REST/GraphQL API, адмінки
Node.js (Fastify) Realtime WebSocket, streaming, serverless, висока I/O concurrency
Go Високонавантажені мікросервіси (>10k RPS), gRPC, DevOps-інструменти
Django + DRF ML-пайплайни, інтеграція з AI, складна обробка даних
Ruby on Rails Швидкий MVP з багатим екосистемою гемів

Node.js виправданий для realtime: Laravel публікує події в Redis Pub/Sub, Node.js підписується та транслює клієнтам. Go — для goroutines (10k з'єднань на сервер — норма), але розробка повільніша, ніж Laravel.

Чому Redis критичний для продуктивності?

Redis виконує кілька ролей:

Роль Деталі
Кеш Кешування результатів важких запитів, фрагментів HTML
Черги Backend для Laravel Queue / Celery
Session store Distributed sessions в multi-instance оточенні
Pub/Sub Realtime події між сервісами
Rate limiting Sliding window counters для API throttling
Leaderboards Sorted Sets для рейтингів

Redis Cluster для горизонтального масштабування, Sentinel для автоматичного failover. Замовте консультацію щодо оптимізації Redis для вашого проекту.

Що входить в роботу під ключ

  • Архітектурне проектування (документація API, схема БД, діаграма сервісів)
  • Реалізація за узгодженим ТЗ з code review
  • Налаштування CI/CD (GitHub Actions, Docker), моніторингу (Sentry, Grafana), алертингу
  • Навантажувальне тестування (k6, wrk) зі звітом
  • Передача вихідних кодів, доступів, інструкція з деплою
  • Навчання команди замовника (2–3 сесії)
  • Гарантійна підтримка 1 місяць після здачі

Орієнтири по термінах

Задача Термін
REST API для мобільного/SPA (середня складність) 6–12 тижнів
Backend зі складною бізнес-логікою + інтеграції 12–20 тижнів
Високонавантажений сервіс на Go 8–16 тижнів
Міграція legacy PHP на Laravel 16–32 тижні

Вартість розраховується індивідуально після аналізу вимог до навантаження, інтеграцій та бізнес-логіки. Зв'яжіться з нами для безкоштовного аудиту вашого поточного backend — отримайте план оптимізації за 2 дні. Замовте консультацію та дізнайтеся, як знизити витрати на інфраструктуру на 30% без втрати продуктивності.