Case Study · Education · 2025
Filling the Gaps — a Custom Admin Dashboard
An admin-scoped analytics layer that grows with the gaps —built once, extended whenever leadership hits a new wall.
The problem
The consultancy runs on a vendor-built MIS. It handles what every MIS handles — data entry, student records, stage tracking. The counsellor-facing side works.
The admin side didn't. Leadership kept hitting the same kind of wall: they'd ask a question the vendor MIS couldn't answer, and the answer would come back via a manual exercise. “How are this user's leads distributed across application stages?” — pulled from the database by hand. “Who's actually been active this month?” — counted off spreadsheets. “Which telecaller deserves credit for this lead's first visit?” — argued about in meetings.
Each one was a real operational question. Each one had a real database underneath that could answer it. The vendor MIS just didn't expose those queries — and getting them added to the vendor's roadmap would have taken months per gap, if it happened at all.
Replacing the vendor MIS would have been the wrong answer. It does its data-entry job, the team has learned it, and the cost of switching would dwarf the cost of the gaps. The right answer was to build a separate admin-scoped layer that reads from the same database and answers leadership's questions directly — and keeps growing as new questions surface.
The insight
The vendor MIS is good at what it does. I don't need to beat it at its own game. I need to build what it doesn't do, scoped tightly to the people who actually need it — admin and leadership.
“Sometimes the best solution isn't replacing the existing system. It's building exactly what it's missing — and leaving room to keep building.”
A separate Flask + PostgreSQL dashboard, behind Microsoft SSO, reading from the same database the vendor MIS writes to. Each module is a small focused tool that answers one operational question leadership had been asking. Six modules so far. More will come — every time leadership hits a new wall the vendor system can't answer, that's a candidate for module number seven.
Two systems, one source of truth. The vendor MIS stays the system of record. Mine is the admin's lens on it.
The approach
A Flask + PostgreSQL dashboard sitting alongside the vendor MIS, reading from the same database. Microsoft SSO for auth. Each module is a self-contained vanilla-JS IIFE with its own CSS prefix, so modules can be added without touching what already works.
Six modules so far, each solving a specific gap leadership had been working around manually:
User Lead Stages.Pick a branch, role, and user; pick a country template; see how that staff member's leads are distributed across the application checklist stages. Replaced the “can you pull a list of where so-and-so's leads are sitting” ad-hoc database query that used to land in operations' lap.
Lead Search. Look up any lead by name, phone, or ID. Returns up to 30 ranked results with assigned counsellor, application count, and a full profile view — education, test scores, checklist progress, remarks, activity history. Replaces the form-wall lookup the vendor MIS forced admins through when they needed to investigate a single lead.
Usage Tracker.Scores staff activity over any date range using weighted points — remarks (by character count, so substance counts more than clicks), documents uploaded, follow-ups, leads created. Filterable by branch and role, searchable, sortable. Gives leadership an honest read on who's actually engaging with the system. Not visible to counsellors — it's a management tool, not a public scoreboard.
User Remarks.Audits a specific user's remarks across leads, split between assigned and unassigned. Percentage bar visualisation, CSV export. Replaces the “is this person actually doing the work or just logging in” question that used to require pulling raw database extracts.
Telecaller Visits. The hard one. Awards first-visit credit to whichever telecaller was last assigned to a lead before the visit happened. Uses a PostgreSQL CTE with ROW_NUMBER to find the first-ever visit per lead and walk the assignment history backwards. Replaces meetings where people argued about who deserved credit.
Counsellor Referrals.Tracks handoffs — a counsellor earns a “sent” point when another counsellor is assigned to their lead after them. Uses a CTE that unions the assigned-user and follower tables. Cascading branch → counsellor dropdowns, expandable leaderboard, sent-leads panel, multi-sheet Excel export. Replaces the previously invisible reality that referrals were happening but nobody could measure them.
The whole thing runs behind Microsoft SSO. Admin and leadership see everything; role gating keeps the dashboard scoped to people who should have it. Deployed on the same Windows Server stack as the other two production systems — IIS reverse-proxy to Waitress, process-managed by PM2, Cloudflare Tunnel for public access.
And it's designed to keep growing. Every module is built as a self-contained JS module with a unique CSS prefix (ul-, ls-, ut-, ur-, tv-, cr-) so new modules drop in without touching existing code. Every time leadership hits a new wall the vendor MIS can't answer, that's the brief for module seven.
What it changed
Operational questions that used to land in someone's lap as ad-hoc work started getting answered by leadership themselves, on demand. The “can-you-pull-me-a-list” queue shrank to almost nothing.
The Usage Tracker changed how leadership sees the team. Engagement was always measurable in principle, but never visible in practice — now it is. People who'd been quietly doing the work without recognition got noticed. Conversely, gaps in coverage that used to hide in the data became obvious.
The Telecaller Visits and Counsellor Referrals modules turned out to settle long-running disputes about credit. Both used to be argued about in meetings. Both are now attributable from the database directly, with the logic visible and the export shareable. The arguments stopped.
And the dashboard kept growing. It started as four modules. It's six now. The framework — modular JS, CSS-prefixed scopes, PostgreSQL views — means each new module ships in days, not weeks. The pattern works.
Under the hood
- Backend
- Python + Flask. REST API against the shared PostgreSQL database that the vendor MIS writes to. Heavy use of CTEs and window functions (
ROW_NUMBERin particular, for the Telecaller Visits attribution logic) — faster and cleaner than aggregating in Python. - Database
- PostgreSQL. Proper use of timestamptz for everything time-related (Kathmandu is UTC+5:45, not a time zone that tolerates naive datetimes). Read-only access from the dashboard — only the vendor MIS writes to the core tables.
- Frontend architecture
- Vanilla JavaScript, no framework. Each module is a self-contained IIFE with a unique CSS prefix (
ul-,ls-,ut-,ur-,tv-,cr-) so styles and state can't leak between modules. Adding a new module means adding a new file and a new prefix — no touching existing code. - Auth & scope
- Microsoft SSO via MSAL — the whole org was already on Microsoft 365. Admin-scoped: leadership and operations roles only. Counsellors do not have access. The dashboard is a management lens, not a counsellor tool.
- Exports
- SheetJS for Excel/CSV exports across modules. The Counsellor Referrals module produces a multi-sheet workbook (summary + per-counsellor breakdown).
- Deployment
- Windows Server 2019 with IIS reverse-proxy to Waitress WSGI, process-managed by PM2. Cloudflare Tunnel for public egress. Same stack as IR Connect and Student Analytics.
- What I'd do differently
- The first module used inline
styleattributes for layout instead of CSS classes. By module four I'd settled on the prefixed-class pattern and had to retrofit. Should have started with the convention from day one.