Web App · Open Source
AkademData
Status
Live at MVNU
Stack
[ THE THESIS ]
From open-source core to Momentum at MVNU
Universities are drowning in Excel Hell when it comes to federal reporting and compliance. I engineered AkademData as an open-source data architecture for parsing, standardizing, and auditing messy institutional files. I deployed that core to build Momentum at Mount Vernon Nazarene University (MVNU)—a live reporting engine that turns multi-day manual workflows into instant dashboards with data provenance, audit checks, and inspectable open-source foundations.
LIVE · Momentum at MVNU · open-source core v1.0.2
SECTION 01 — DESIGN DECISIONS
AkademData is the core. Momentum is the MVNU deployment.
I built AkademData as an open-source higher-ed data architecture, then deployed that core to build Momentum for Mount Vernon Nazarene University. The system takes messy stats-day Excel files, standardizes them into an auditable data model, and gives MVNU a working reporting engine without starting from a full SIS replacement.
Open-source first — then commercial
akademdata-open-source@1.0.2 is the inspectable core behind Momentum. A school can review the parsing logic, schema decisions, and audit flow before trusting the deployment.
Meet the data where it lives
Schools already have stats-day .xlsx files. The ingest path is a drag-and-drop upload; the schema is inferred from a runtime JSON config. Zero SIS work.
Star-schema, not wide tables
Student is the dimension, Enrollment is the fact table. One student → N enrollments (term × program). The right shape for analytics grouping without denormalised duplication.
Provenance before persuasion
Every metric is designed to trace back to the raw file, the rule applied, and the records underneath it. Momentum is not just a dashboard — it is an audit trail.
SECTION 02 — INGESTION PIPELINE
Excel → SekanData Mapper → Prisma → server-rendered analytics
Upload
drag-drop .xlsx
Stats
students / enrollments / programs
Search
URL searchParams
Export
CSV / xlsx out
ExcelJS streaming parse
- ▹ row-by-row via ws.eachRow
- ▹ memory-safe on institution-scale files
- ▹ typed getString / getInt / getFloat
Graceful row failure
- ▹ missing studentId → warn + skip
- ▹ merged headers ignored
- ▹ hand-entered notes ignored
Multi-program data normalization
One raw Excel row → Student.upsert (dedup by studentId) + Enrollment.create (fact row — one per term/program). No duplicate students, no broken joins.
Student (dimension)
- ▹ studentId @unique
- ▹ name · age · gender · state
- ▹ createdAt
Enrollment (fact)
- ▹ studentId → Student
- ▹ term · program · credits · status
- ▹ 1 student ⇒ N rows
SECTION 03 — MOMENTUM AT MVNU
The delta from manual reporting to auditable dashboards
Before · Excel hell
After · Momentum
Enrollment report
~2 days
→~2 minutes
Programs parsed
hand-managed
→129 · zero errors
FTE records
fragmented across files
→2,100+ unified
Growth tracking
retrospective, manual
→live 24% visibility
Reporting validation
manual cross-checks
→historical anomalies reconciled
Metric provenance
numbers without receipts
→source file + rule + records
IR staff time / month
20–40 hours
→reclaimed for analysis
Labor cost exposure
$15k–$40k / year
→reduced via automation
HLC 2029 readiness
at risk
→on track
SECTION 04 — ADAPT PLAYBOOK
Five steps to deploy to another school
docs/ADAPT.md — a few hours, not a six-month integration project.
01
Edit prisma/schema.prisma
Add or remove fields to match your data model. Generate the migration.
02
Update data/colMappings.json
Point Excel columns (1-based indices) to your schema fields. Runtime config, no rebuild.
03
Tweak lib/parser.ts
If you've added fields, extend the data object. Everything else (streaming, upsert, graceful skip) stays the same.
04
Customise the dashboard
Update getDashboard() and getChartData() in lib/queries.ts. The chart components auto-pick up the new shape.
05
Swap branding
Replace public/logo.svg, adjust tailwind.config.js tokens. Deploy.
SECTION 05 — ENGINEERING HIGHLIGHTS
What this codebase proves
PROVENANCE
Hover-to-prove-it metrics
Every rendered number is designed to carry its receipts: source file, applied rule, matching status, and the underlying student records. Leadership sees the metric; data teams can prove it.
→ audit-ready by default
AUDIT
Automated reconciliation before final review
Momentum cross-references live database calculations against historical reporting artifacts, surfacing anomalies early so the data team can reconcile edge cases before final review.
→ pre-submission validation
SCALE
Tenant rules live outside the core engine
Institutional business rules — program exclusions, cohort definitions, column mappings, and reporting assumptions — are configuration, not rewrites. That is what makes the open-source core reusable beyond MVNU.
→ built for other campuses
SECTION 06 — CODE PROOF
The mapper that turns any stats-day .xlsx into a clean relational DB
Verbatim from the NPM package.
lib/parser.ts
typescript
// lib/parser.ts — excerpt · the SekanData Mapper
const COLS = getColMapping(); // JSON config merged over DEFAULT_COLS
ws.eachRow({ includeEmpty: false }, async (row, n) => {
if (n === 1) return; // header row
const studentId = row.getCell(COLS.studentId).value?.toString();
if (!studentId) { // graceful row skip
console.warn(`Row ${n}: missing studentId — skipping`);
return;
}
const student = {
studentId,
name: getString(row, COLS.name),
age: getInt(row, COLS.age),
gender: getString(row, COLS.gender),
state: getString(row, COLS.state),
};
const enrollment = {
studentId,
term: getString(row, COLS.term),
program: getString(row, COLS.program),
credits: getFloat(row, COLS.credits),
status: getString(row, COLS.status),
};
// 1 student → 1 dimension row (dedup by unique studentId)
await prisma.student.upsert({
where: { studentId },
update: student,
create: student,
});
// 1 student → N fact rows (term × program)
if (enrollment.term && enrollment.program) {
await prisma.enrollment.create({ data: enrollment });
}
});