A full-stack web application for scoring candidate speech using the ELSA API. Supports two flows: a Recruiter Flow for admin-managed candidates and an Applicant Flow for self-submitted recordings.
- Secure login with ELSA credentials (Staging / Production environments)
- Create and manage candidate profiles
- Upload meeting transcripts and recordings
- Extract candidate speech segments using FFmpeg
- Score audio via the ELSA
score_audio_plus_elsaAPI - View detailed score breakdowns (EPS, fluency, pronunciation, integrity)
- Public
/applypage — no login required - In-browser audio recording (30 seconds minimum, 1 minute maximum)
- Live waveform visualiser and countdown timer
- Cycling prompts to guide the applicant
- Submissions automatically linked to a single profile by email
- Submission timestamps recorded for each entry
- Sortable, filterable table view of all candidates
- Filter by name, email, source (recruiter/applicant), EPS score range, and date
- Colour-coded EPS scores
- Click any row to view full candidate and meeting details
| Layer | Technology |
|---|---|
| Frontend | React 18 + TypeScript + Vite |
| Backend | Node.js + Express |
| Audio extraction | FFmpeg (via nix) |
| Storage | JSON file (data/candidates.json) + local uploads/ |
| Speech scoring | ELSA score_audio_plus_elsa API |
├── client/ # React frontend (Vite)
│ └── src/
│ ├── pages/
│ │ ├── ApplyPage.tsx # Public applicant recording flow
│ │ ├── CandidatesPage.tsx # Recruiter candidate table + filters
│ │ ├── MeetingDetailPage.tsx
│ │ └── LoginPage.tsx
│ └── components/
├── server/ # Express backend
│ ├── index.js
│ └── routes/
│ ├── apply.js # Public POST /api/apply
│ ├── candidates.js # CRUD for candidates
│ └── scoring.js # FFmpeg extraction + ELSA scoring
├── data/
│ └── candidates.json # Persistent candidate data
└── uploads/ # Uploaded audio/transcript files
- Node.js 18+
- FFmpeg (required for recruiter flow scoring)
# Install all dependencies
npm run build
# Start the server
npm startThe backend runs on port 3000, the frontend dev server on port 5173.
The app runs in two modes selectable at login:
- Staging — connects to ELSA staging API
- Production — connects to ELSA production API
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/auth/login |
Public | Login with ELSA credentials |
| GET | /api/candidates |
Required | List all candidates |
| POST | /api/candidates |
Required | Create a candidate |
| GET | /api/candidates/:id |
Required | Get candidate + meetings |
| DELETE | /api/candidates/:id |
Required | Delete a candidate |
| POST | /api/candidates/:id/meetings |
Required | Add a meeting |
| POST | /api/candidates/:id/meetings/:mId/upload |
Required | Upload transcript/recording |
| POST | /api/candidates/:id/meetings/:mId/score |
Required | Score a meeting |
| POST | /api/apply |
Public | Submit an applicant recording |
| Elapsed time | Available actions |
|---|---|
| 0 – 29 seconds | Stop & Re-record only |
| 30 – 59 seconds | Re-record or Stop & Submit |
| 60 seconds | Auto-stops → goes to review |
The app is configured for deployment on a VM (not serverless) because:
- FFmpeg must be available at runtime
data/candidates.jsonanduploads/require persistent disk storage
npm run build # installs deps + builds frontend
npm start # runs server/index.js