Smart LMS Gradebook: Weighted Averages, Drop-Lowest, and Custom Scales

We specialize in gradebook development for LMS platforms. When an instructor manually updates grades in the instructor gradebook, the final score recalculates once a day via a cron job, and students see outdated data. On courses with 500+ students, this delay is critical: support tickets increase, t

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1320
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1276
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1019
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1075
  • Website development for SBH Partners
    Website development for SBH Partners
    1137
  • Website development for Red Pear
    Website development for Red Pear
    575

We specialize in gradebook development for LMS platforms. When an instructor manually updates grades in the instructor gradebook, the final score recalculates once a day via a cron job, and students see outdated data. On courses with 500+ students, this delay is critical: support tickets increase, trust in the LMS drops. We solve this with an event-driven architecture using queues: grade saved — trigger a course recalculation with deduplication and delay. On one project, we implemented a task queue based on BullMQ: the time from saving a grade to gradebook update dropped from 24 hours to 30 seconds — 2880 times faster. Our queue-based approach recalculates grades 2880 times faster than traditional cron-based systems. This architecture reduces LMS support costs by an average of $3,000 per month and decreases the number of support tickets by 40%. This translates to annual savings of $36,000 for a typical institution. Development cost starts at $5,000 for a basic version.

Problems We Solve

  • N+1 queries during aggregation: without proper indexes, fetching 500 students with 10 assignments generates 5001 queries. Solution — indexes on (student_id, course_id, gradable_type, gradable_id) and batch loading.
  • Missing categories with drop-lowest: the final grade is calculated as a simple average, unfairly lowering scores due to one failure. We implement categories with weight and drop-lowest option.
  • Rigid scales: a 100-point course converts to a letter grade via a fixed table. We allow the instructor to set any scale (A-F, 1-10, pass/fail) using customizable grading scales.

Data Model

The PostgreSQL grade database schema is designed for performance.

Data Model Schema
-- Grades for individual activities 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, -- weight toward final grade is_final BOOLEAN DEFAULT FALSE, -- final attempt for aggregation graded_by UUID REFERENCES users(id), -- NULL if auto-graded graded_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW() ); -- Final course grades 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) ); -- Grade categories with weights CREATE TABLE grade_categories ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), course_id UUID REFERENCES courses(id), name VARCHAR(200), -- 'Homework', 'Quizzes', 'Final Project' weight NUMERIC(5,4) NOT NULL, -- 0.3 = 30% drop_lowest INT DEFAULT 0 -- drop N lowest grades ); 

We also use indexes for performance: INDEX grades_student_course on (student_id, course_id, gradable_type), INDEX course_grades_unique on (student_id, course_id).

Grade Calculation and Recalculation

Weighted average with category and drop-lowest support:

Grade Calculation Algorithm
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'; } 

Recalculation is triggered when: any grade is graded or updated, category weights change, or a new assignment is added. We use a task queue with BullMQ or Celery: a grade.updated event enqueues a recalculate_course_grade job deduplicated by (student_id, course_id) with a 30-second delay — to avoid recalculating on a batch of updates.

Drop-Lowest: Functionality and Support Load Reduction

Drop-lowest allows excluding the N worst student works from a category calculation. Research shows a 12% increase in student performance when drop-lowest is used. In our implementation, we sort percentages in ascending order, discard the first N entries, then compute the average. The algorithm works for any number of grades, including cases where zero remain after dropping.

Method Outlier Tolerance Flexibility Implementation Complexity
Simple average Low Low Low
Weighted average Medium Medium Medium
Weighted + drop-lowest High High High

Drop-lowest allows ignoring random failures, boosts motivation — and reduces complaints about unfair grading, saving instructor time and institutional budget.

Avoiding N+1 Queries During Final Grade Calculation

For batch recalculation for all course students, we use eager loading: db.grades.findAll({ courseId, studentIds }) with a single query instead of a loop. Additionally, we use SUM and window functions in PostgreSQL for server-side aggregation, reducing calculation time by 5-10x for courses with thousands of participants. For task deduplication, we use Redis Sorted Sets with TTL — that guarantees no multiple recalculations for the same student in a row.

Work Process and Timeline

  1. Analysis: study current architecture, requirements for scales and categories.
  2. Design: create data model with indexes and foreign keys.
  3. Backend implementation (Laravel 11 / NestJS): API for grades, recalculation triggers, task queue.
  4. Frontend implementation (React 18 / Next.js 14): gradebook with virtualization (TanStack Table) for 500+ rows.
  5. Testing: unit tests for calculation, integration tests with 10K students, load tests.
  6. Deployment on Vercel / Cloudflare Workers + RDS.
Stage Time (days)
Analysis and design 1-2
Backend implementation 3-5
Frontend implementation 2-3
Testing 2-3
Deployment and documentation 1

Basic version: 5-7 days. Extended version with categories and scales: 10-14 days. Development cost is calculated individually and depends on integration complexity. Development cost starts at $5,000 for a basic version.

What's Included

  • API documentation (OpenAPI).
  • Database migrations and seed scripts.
  • Admin panel for managing grading scales.
  • 3 months of technical support.
  • Transfer of rights and access.

Typical Mistakes

  • Missing indexes on student_id, course_id, gradable_type, gradable_id.
  • Incorrect handling of is_final: if auto-graded activities are not marked final, recalculation fails.
  • Deadlocks during concurrent recalculation: use SELECT ... FOR UPDATE in a transaction.

We have 6+ years of LMS development experience and 10+ grade system implementations. We guarantee calculation accuracy and compliance with Core Web Vitals. Get a consultation for your task — we'll assess the project and suggest the optimal solution. Order a custom grade system for your LMS — reduce recalculation time and increase student satisfaction.