A desktop exam analysis & report card generation system for Kenyan secondary schools. Offline-first, JavaFX-based GUI with SQLite persistence. Generates professional A4 report cards with subject tables, performance trends, and merit lists.
Version: 2.1.0
Java: 17+
Build: Maven (mvn clean package) → fat shaded JAR
| Layer | Technology | Purpose |
|---|---|---|
| UI | JavaFX 17.0.6 + CSS | All screens, tables, charts |
| Database | SQLite (WAL mode) | Single file exam_analysis.db |
| OpenPDF 1.3.30 | Report cards, merit lists, student lists | |
| Excel | Apache POI 5.3.0 | Template generation, bulk import |
| Build | Maven + shade plugin | Fat JAR at target/exam-analysis-2.0.0.jar |
Threading: All heavy work (PDF gen, Excel parse, ranking compute) runs via
javafx.concurrent.Task< > off the JavaFX Application Thread. ProgressIndicator
shown during processing.
Entry point: com.zaraki.exams.Launcher → Main.java
src/main/java/com/zaraki/exams/
├── Launcher.java — Bootstrap
├── Main.java — JavaFX Application, login → dashboard flow
├── SeedData.java — Demo data generator
├── auth/
│ ├── LoginForm.java
│ ├── UserManagementForm.java
│ └── PasswordUtils.java
├── config/
│ ├── SettingsManager.java
│ └── CurriculumSystem.java (enum: 8-4-4 / CBC)
├── database/
│ └── DatabaseEngine.java (singleton, DDL, connection)
├── forms/
│ ├── AnalysisForm.java (5-tab analysis)
│ ├── BulkMarksForm.java
│ ├── DashboardForm.java (sidebar nav, stats)
│ ├── ExamForm.java
│ ├── GradingScaleForm.java
│ ├── MarksEntryForm.java
│ ├── PublishForm.java (publish workflow, multi-sheet excel)
│ ├── ReportForm.java (report card preview + PDF gen)
│ ├── SchoolSettingsForm.java
│ ├── StudentForm.java
│ ├── SubjectForm.java
│ └── TeacherAssignmentForm.java
├── model/ (5 POJOs: Exam, Student, Subject, Mark, GradingScale)
├── repository/
│ └── MarksRepository.java
├── reporting/
│ └── ReportCardGenerator.java
└── service/
├── ExamAnalysisService.java (rankings, metrics, grades)
└── ExcelService.java (POI templates, import)
All created in DatabaseEngine.executeDDL().
| Table | Key Columns | Notes |
|---|---|---|
students |
id, admission_number (UNIQUE), full_name, form(1-4), stream, status | Indexed on admission, (form,stream) |
subjects |
id, subject_code (UNIQUE), subject_name, department, grouping(Compulsory/Elective) | |
exams |
id, academic_year, term(Term 1/2/3), exam_series, released, released_by, released_at | released column added by migration |
grading_scales |
id, subject_id (nullable=global), minimum_mark, maximum_mark, grade, points, remarks | Per-subject or global fallback |
marks |
exam_id, student_id, subject_id (composite PK), score, grade_achieved, points_achieved | Indexed on exam, student |
users |
id, username(UNIQUE), password_hash, salt, full_name, role(admin/teacher) | Admin seeded by default |
exam_subjects |
id, exam_id, subject_id, out_of(default 100), uploaded_by/at, published, published_by/at | Links exam→subject with publish state |
teacher_subjects |
id, user_id, subject_id, form, stream | Controls teacher subject access |
app_settings |
key(TEXT PK), value(TEXT) | school_name, logo_path, curriculum, etc. |
Foreign keys: marks → exams/students/subjects, exam_subjects/teacher_subjects → exams/subjects/users
- Login with username/password (SHA-256 + salt)
- Two roles: admin (full access) and teacher (limited to assigned subjects)
- Admin can manage users via sidebar → Users (CRUD table)
- Default admin: username
admin, passwordadmin
- Students: Add individually, bulk via Excel template, seed demo data (20/stream × 4 forms × 2 streams)
- Subjects: CRUD with code, name, department, compulsory/elective flag
- Exams: Create by year + term + series
- Grading Scales: Per-subject or global; auto-generate from curriculum preset (8-4-4: A=12pts → E=1pt; CBC: 4-tier)
- Curriculum switcher: Sidebar dropdown toggles between 8-4-4 and CBC (presets change grading tables)
- Manual entry: Sidebar → Marks Entry → select exam + form + stream + subject → editable score table with auto-grade/points on cell edit
- Excel bulk: Sidebar → Bulk Marks → generate class template → upload filled file
- Per-subject upload (via Publish): Each subject uploadable individually with inline table or Excel
- Publish sidebar item → select exam → table of all subjects
- Each subject shows: upload status, marks count, publish status, editable "Out Of" column
- Upload: Click subject → inline editable student table + "Upload Excel" + "Download Template"
- Teacher filter: Non-admin teachers only see subjects assigned to them via Teacher Assignment
- Publish: Teacher clicks "Publish" after uploading to lock subject
- Release: Admin clicks "Release Exam" (orange button) — requires ALL subjects published
- Reports & Analysis blocked until exam is released
- Sidebar → Teacher Subjects → select teacher → add/remove subject+form+stream combos
- Controls which subjects appear in Publish for teachers
- In Publish, admin can upload one
.xlsxwith multiple sheets - Sheet naming convention:
"SubjectName - FormXStream"(e.g."Chemistry - Form 1A") - System parses sheet name, resolves subject + class, imports all sheets in one pass
- Supports single-sheet-per-subject as well
- In Publish, admin selects teacher → downloads Excel with one sheet per assigned subject
- Each sheet pre-populated with students for that form+stream
- Naming matches the multi-sheet upload parser
Accessed via sidebar → Analysis. Requires exam to be released.
| Tab | Content |
|---|---|
| Broadsheet | Class rankings with positions, prev points, delta |
| Subject Metrics | Mean score, grade, std dev, rank, candidates per subject |
| Grade Distribution | Per-subject histogram of grade counts |
| Most Improved/Dropped | Two-exam comparison with position changes |
| Merit List | Per-subject score/deviation/position columns, export to PDF |
Sidebar → Reports. Requires exam to be released.
- Single student: Search by admission or name → preview report card on screen → Generate PDF
- Bulk (stream/form): Select stream or form → single multi-page PDF, each student on own A4
Each A4 page contains:
- Header: School logo (background watermark, 8% opacity centered), school name (from settings), term dates, exam info
- Student info: Admission, name, form, stream
- Subject table: Subject, Score, Grade, Points, Position, Remarks
- Summary: Total Marks, Total Points, Mean Points
- Performance indicator: "Improved by X pts" / "Dropped by X pts" / "First exam"
- Trend chart: SVG-like line chart with labeled axes, legend
- Rubber stamp: (optional) bottom-right placement
Sidebar → Settings → configure:
- School name (used in PDF headers)
- Term opening/closing dates
- School logo image (becomes watermark on reports)
- Rubber stamp image (placed bottom-right on PDF)
- Offline-first: No network required. Single SQLite file
exam_analysis.db. - Threading model:
javafx.concurrent.Taskfor all heavy work. Never block the JavaFX Application Thread. - Grade calculation:
determineGradeAndPoints(score, subjectId, examId)normalizes byout_offromexam_subjects. Grading scales store percentage ranges (0-100). Defaultout_of=100ensures backward compatibility. - Optional subjects: Only students with a
marksrow for a subject are counted in that subject's metrics. Merit list shows "-" for un-taken subjects. Totals count only taken subjects. - Publish workflow: Two-phase gate — teacher uploads+publishes per subject, admin releases entire exam.
- School logo as watermark: Uses
PdfPageEventwithgetDirectContentUnder()and 8% opacity viaPdfGState.setFillOpacity(). - User search: Free-text
TextFieldwith SQLLIKE(no dropdown). NB:LIKE '%…%'does a full table scan — not recommended for very large datasets. - Bulk report PDF: Single
Documentwithdoc.newPage()between students — same layout as single-student PDF.
Main.start()
└─→ showLogin() → LoginForm → DashboardForm(loggedInUser, loggedInUsername, loggedInRole, logoutCallback)
└─→ Sidebar (14 items):
├── Dashboard — stats cards, trend chart, demo tools
├── Students — CRUD + Excel import
├── Subjects — CRUD
├── Exams — CRUD
├── Grading Scales — manage grade tables
├── Users — manage admin/teacher accounts
├── Teacher Subjects — assign subjects to teachers
├── Settings — school info, logo, stamp
├── Publish — upload marks, publish, release
├── Marks Entry — manual per-class marks entry
├── Bulk Marks — Excel template + upload
├── Analysis — 5-tab analysis (released exams only)
└── Reports — preview + PDF (released exams only)
| Action | Admin | Teacher |
|---|---|---|
| Manage users, exams, subjects, grading | ✅ | ❌ |
| Upload marks (all subjects) | ✅ | ✅ (assigned only) |
| Publish subjects | ✅ | ✅ (own only) |
| Release exam | ✅ | ❌ |
| View analysis / generate reports | ✅ | ✅ (released only) |
| View/assign teacher subjects | ✅ | ❌ |
| School settings | ✅ | ❌ |
- Zero test coverage: No unit/integration tests for ranking, grading, PDF gen, or Excel parsing.
- Thread-unsafe connection:
getConnection()has no synchronization; concurrent Tasks can cause "database is locked" errors. - Weak password hashing: Single-round SHA-256 (now upgraded to PBKDF2WithHmacSHA256 in v2.1).
- Student subject registration: Need a table to register which subjects each student takes (especially for electives like Computer Studies). Currently the system infers from presence of marks, but this causes issues for report cards showing subjects the student doesn't take.
- Per-student subject list: Report card should list only subjects the student is registered for, not all subjects with marks.
- Form/Stream subject assignment: Need to specify which subjects are offered to which forms/streams (e.g. Physics only in Form 3-4).
- Backup/Restore: No built-in database backup/restore functionality.
- Student promotions: No bulk promotion of students to next form at year-end.
- Transcript generation: Cumulative report spanning multiple terms/years.
- Subject rank improvements: Show subject rank as fraction (e.g.
5/40) in all views. - Password change: Users can't change their own password from within the app.
- Report card template editor: Allow customizing PDF layout (colors, fonts, sections) from UI.
- DB queries on FX thread: Multiple forms load data on the JavaFX Application Thread (DashboardForm, MarksEntryForm, ReportForm, etc.).
- Duplicate merit list logic: The same algorithm appears in both
AnalysisFormandReportCardGenerator(~300 lines duplicated).
- Print directly from app: Currently saves PDF to file only.
- Email reports: Send PDF report cards via email.
- Multi-language support: UI hardcoded in English.
- Dark mode: No theme toggle.
- Analytics dashboard: Trend charts currently per-student; no aggregate school-wide trends.
- REST API: No network API; would require architectural changes.
- Cloud sync: Would require conflict resolution for offline-first model.
- No pagination on student search: Search limited to 20 results with no "next page" control.
- UI allows creating exams without subjects: No validation that subjects exist before exam creation.
# Compile
mvn clean compile
# Package fat JAR
mvn clean package -DskipTests
# Run
java -jar target/exam-analysis-2.1.0.jar
# The JAR is self-contained (shades all dependencies including JavaFX,
# SQLite, OpenPDF, Apache POI)- Security: Replaced SHA-256 password hashing with PBKDF2WithHmacSHA256 (600K iterations)
- Security: Added column whitelist validation to prevent SQL injection via filter column names
- Bugfix: Fixed thread-unsafe database connection —
getConnection()now properly synchronized - Bugfix: Fixed mutable static PDF watermark causing corrupted images in concurrent PDF generation
- Bugfix: Removed duplicate
INSERT_BATCH_SQLstring; single insert now delegates to batch insert - Bugfix: File upload now validates image extensions before saving
- Quality: Added
equals()/hashCode()to all 5 POJO models - Quality: Added JUnit 5 + Mockito test infrastructure with unit tests for PasswordUtils, DatabaseEngine, ExamAnalysisService
- Quality: Removed dead code (unused
Labelin GradingScaleForm.refresh()) - Docs: Updated README.md with accurate version, features, and project structure
- Docs: Corrected REPORT.md inaccuracies (table count 7→9, LIKE performance caveat, version)
- Chore: Updated
.gitignoreto exclude generated PDFs, school assets, and dependency-reduced-pom.xml
Generated: 2026-06-19 — Update this document when adding major features.