Files · Automated Resume Rubric Scorer for Boutique Recruiters
101 (1 binary, 640.2 kB total)attempt 3
README.md·4958 B·markdown
markdown
# Automated Resume Rubric Scorer for Boutique Recruiters
> Score 200 resumes per role against custom rubrics in minutes, not hours.
A solo recruiter at a 5-person agency spends 10+ hours per role manually scoring resumes against client rubrics. With 50–200 resumes per role and 40 active roles, this becomes unsustainable. This recipe provides a consistent, auditable scoring system to surface top candidates quickly without hiring more staff.
## How it works
1. **Create a rubric** — Define scoring criteria with weights and max scores for a specific role.
2. **Upload resumes** — Upload PDF or DOCX resumes via the API or dashboard.
3. **Score automatically** — Each resume is scored against the rubric using an LLM (OpenAI GPT via Vercel AI SDK).
4. **Rank candidates** — View candidates sorted by their best score across multiple scoring runs.
5. **Review and iterate** — Score results include per-criterion scores, rationales, and cost tracking.
## Architecture
```
app/page.tsx Dashboard UI (server component)
app/api/
rubrics/ CRUD for rubrics
resumes/ Resume upload and listing
resumes/[id]/score/ Trigger and list scoring results
candidates/ Candidate management and ranking
candidates/[id]/ Candidate detail with score results
score-results/[id]/ Score result detail
health/ Health check endpoint
src/
schemas/ Zod schemas for Rubric, Resume, Candidate, ScoreResult
lib/ InMemoryStore, LLM client (Vercel AI SDK), LLM cache engine
services/ Business logic (rubric, resume-parser, scorer, candidate, eval, scoring-queue)
instrumentation.ts Next.js instrumentation hook initializing observability
tests/ Vitest suite with 100+ tests
```
### Key packages
| Package | Purpose |
|---------|---------|
| `next@16` | App Router framework |
| `ai` + `@ai-sdk/openai` | Vercel AI SDK with OpenAI provider |
| `zod` | Runtime schema validation |
| `unpdf` | PDF text extraction |
| `office-text-extractor` | DOCX/XLSX/PPTX text extraction |
| `bullmq` | Redis-backed async scoring queue |
| `@reaatech/agent-eval-harness-golden` | Golden trajectory management for scoring consistency |
| `@reaatech/agent-eval-harness-tool-use` | Tool call validation for LLM scoring |
| `@reaatech/agent-eval-harness-cost` | Per-trajectory LLM cost tracking and budget enforcement |
| `@reaatech/agent-eval-harness-observability` | OTel tracing, metrics, structured logging, and dashboards |
| `@reaatech/llm-cache` | Semantic + exact-match LLM response caching |
| `@reaatech/llm-cache-adapters-redis` | Redis storage adapter for the cache |
## Setup
```bash
pnpm install
cp .env.example .env # edit with your API keys
pnpm dev # start Next.js dev server
pnpm test # run vitest with coverage
pnpm typecheck # TypeScript type checking
pnpm lint # ESLint
```
## API Reference
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/api/rubrics` | List all rubrics |
| `POST` | `/api/rubrics` | Create a new rubric |
| `GET` | `/api/rubrics/:id` | Get rubric by ID |
| `PUT` | `/api/rubrics/:id` | Update rubric |
| `DELETE` | `/api/rubrics/:id` | Delete rubric |
| `GET` | `/api/resumes` | List all resumes |
| `POST` | `/api/resumes` | Upload a resume (multipart form) |
| `POST` | `/api/resumes/:id/score` | Enqueue scoring job for a resume |
| `GET` | `/api/resumes/:id/score` | List score results for a resume |
| `GET` | `/api/candidates` | List candidates sorted by best score |
| `POST` | `/api/candidates` | Create a candidate |
| `GET` | `/api/candidates/:id` | Get candidate with score result |
| `GET` | `/api/score-results/:id` | Get score result with criterion breakdown |
| `GET` | `/api/health` | Health check |
## Usage
1. Create a rubric: `POST /api/rubrics` with `{ name, roleName, criteria: [{ name, description, weight, maxScore }] }`.
2. Upload resumes: `POST /api/resumes` with multipart form data (field `file`).
3. Trigger scoring: `POST /api/resumes/:id/score` with `{ "rubricId": "..." }`.
4. View results: `GET /api/score-results/:id` for a detailed per-criterion breakdown.
5. Rank candidates: `GET /api/candidates` returns candidates sorted by `bestPercentage` descending.
## Configuration
| Env var | Default | Description |
|---------|---------|-------------|
| `OPENAI_API_KEY` | — | OpenAI API key (required) |
| `REDIS_URL` | `redis://localhost:6379` | Redis connection URL |
| `LLM_MODEL` | `gpt-5.2` | OpenAI model for scoring |
| `CACHE_SIMILARITY_THRESHOLD` | `0.85` | Cosine similarity threshold for cache |
| `CACHE_DEFAULT_TTL` | `3600` | Cache TTL in seconds |
| `BUDGET_PRESET` | `moderate` | Budget preset (strict/moderate/lenient) |
## Testing
```bash
pnpm test # 104 tests, 90%+ coverage on all metrics
pnpm typecheck
pnpm lint
```
## License
MIT — see [LICENSE](./LICENSE).