Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions HEpiR-HREvolution/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
HRFLOW_API_KEY=your_hrflow_api_key
HRFLOW_USER_EMAIL=your_hrflow_user_email
HRFLOW_SOURCE_KEY=your_hrflow_source_key
HRFLOW_BOARD_KEY=your_hrflow_board_key

# OpenRouter (https://openrouter.ai) — or any OpenAI-compatible LLM API
LLM_API_KEY=your_openrouter_api_key
LLM_BASE_URL=https://openrouter.ai/api/v1
LLM_MODEL=nvidia/nemotron-3-super-120b-a12b:free
125 changes: 125 additions & 0 deletions HEpiR-HREvolution/PROJECT_DOCS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# HRFlow Hackathon — Project Documentation

## Overview

**Project Name:** Candidate Application Synthesis Generator
**Goal:** AI-powered recruitment pipeline dashboard. Integrates the HRFlow API to track, rank, and synthesise candidate applications per job. An LLM layer grades candidates, analyses their profiles, and auto-generates structured recruitment summaries.

**UI Design:** Slack-dark sidebar + Jira-light content. Minimalist, information-dense, modern.

---

## Detailed Documentation

| Document | Contents |
|----------|----------|
| [docs/architecture.md](docs/architecture.md) | System design, file structure, data persistence strategy, indexing delay mitigations |
| [docs/api-reference.md](docs/api-reference.md) | All backend API endpoints with request/response schemas |
| [docs/hrflow-integration.md](docs/hrflow-integration.md) | HRFlow endpoints used, data models, tag schemas, known quirks & fixes |
| [docs/ai-pipeline.md](docs/ai-pipeline.md) | Grading pipeline, synthesis flow, caching, prompt rules, robustness notes |
| [docs/frontend-components.md](docs/frontend-components.md) | Component breakdown, layout, props, UX flows, api.js reference |
| [docs/infrastructure.md](docs/infrastructure.md) | Docker Compose, env vars, Dockerfiles, FastAPI config, running the stack |
| [docs/candidate-extra-documents.md](docs/candidate-extra-documents.md) | Extra document data model, file upload/extraction, audio transcription, scoring integration, UI |
| [docs/manual-status-management.md](docs/manual-status-management.md) | Candidate stage pipeline, bonus scoring, real-time UI updates |

---

## Tech Stack

| Layer | Technology |
|----------|------------|
| Frontend | React 18, Vite 5 |
| Backend | Python 3.12, FastAPI |
| AI (grading/synthesis/questions) | OpenRouter — configurable via `LLM_MODEL` env var |
| AI (audio transcription) | OpenRouter — `google/gemini-2.0-flash-001` (fixed) |
| PDF extraction | `pypdf` |
| DOCX extraction | `python-docx` |
| HR Data | HRFlow API v1 |
| Infra | Docker Compose |

---

## Quick Start

```bash
# 1. Copy and fill in credentials
cp .env.example .env

# 2. Start the full stack
sudo docker compose up --build

# Frontend → http://localhost:3000
# Swagger → http://localhost:8080/docs
```

**.env required keys:**
```env
HRFLOW_API_KEY=...
HRFLOW_USER_EMAIL=...
HRFLOW_SOURCE_KEY=...
HRFLOW_BOARD_KEY=...
LLM_API_KEY=...
LLM_BASE_URL=https://openrouter.ai/api/v1
LLM_MODEL=nvidia/nemotron-3-super-120b-a12b:free
```

---

## Data Flow

```
PDF Resume Upload (job-scoped)
→ HRFlow profile/parsing/file → profile created in Source
→ HRFlow tracking/indexing → links profile to job (stage: applied)
→ localStorage pending key → shown immediately before indexing completes
→ POST /api/ai/grade → auto-triggered
→ HRFlow base score + upskilling
→ LLM → final_score + rationale
→ PUT profile tag: job_data_{job_key}
→ POST /api/ai/synthesize → auto-triggered after grade
→ LLM → synthesis (summary, strengths, weaknesses, verdict)
→ PUT profile tag: synthesis_{job_key}
→ in-memory cache: _synthesis_cache[job_key:profile_key]

Extra Document Submission (text)
→ POST /api/candidates/{key}/documents
→ POST /api/ai/grade → auto-triggered; only unscored docs re-evaluated
→ POST /api/ai/synthesize → auto-triggered; synthesis refreshed

Extra Document Upload (PDF / DOCX / audio)
→ POST /api/candidates/{key}/documents/file
PDF → pypdf text extraction
DOCX → python-docx paragraph extraction
Audio → LLM transcription (google/gemini-2.0-flash-001)
→ extracted text stored as document content
→ POST /api/ai/grade + synthesize → same auto chain as text submission

Job Creation
→ HRFlow job/indexing → job created in Board
→ localStorage pending key → shown immediately before indexing completes

Candidate Panel Open
→ GET /api/candidates/{key} → full profile (parallel)
→ GET /api/ai/synthesis → cache hit → instant display
cache miss → HRFlow tag → display
tag miss → auto-generate → store → display
```

---

## Key Design Decisions

| Decision | Reason |
|----------|--------|
| No local database | HRFlow is the single source of truth; avoids sync complexity |
| `PUT` (not `PATCH`) for profile tags | HRFlow returns 405 on PATCH; PUT requires full profile payload |
| In-memory synthesis cache | HRFlow tag indexing delay means tags aren't readable immediately after write |
| localStorage pending keys (jobs + candidates) | HRFlow search index lags after creation; individual GET fallback bridges the gap |
| `redirect_slashes=False` on FastAPI | Prevents 307 redirects that break the Vite `/api` proxy |
| Auto-grade + auto-synthesise on upload | Removes manual steps; synthesis is ready when the panel is first opened |
| Extra candidate skills = neutral/positive | LLM prompt explicitly forbids penalising over-qualification |
| Tracking created on upload | Without a tracking, candidates never appear in job's candidate list |
| File extraction server-side | PDF/DOCX parsing and audio transcription happen in the backend; frontend receives only text |
| Race condition guard on job switching | `currentJobKeyRef` pattern discards stale candidate-list responses when the user navigates quickly |
| Optimistic stage/bonus update via `candidateOverride` | Stage and bonus changes are reflected immediately in the candidate list without waiting for HRFlow re-fetch |
| Synthesis banner gated on profile load | Banner is suppressed until profile data has arrived, preventing false "Generating synthesis…" on initial open |
89 changes: 89 additions & 0 deletions HEpiR-HREvolution/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# HEpiR — HR Evolution

> AI-powered recruitment dashboard that ranks, analyses, and synthesises candidate applications in real time.

## What it does

HEpiR connects to the HrFlow.ai API to give HR teams a unified view of every job opening and its applicants. Drop a PDF resume into a job, and the system instantly scores the candidate against the role, generates a structured AI synthesis (strengths, weaknesses, upskilling recommendations), and lets HR attach supplementary documents — interview notes, technical test transcripts, audio recordings — that feed directly back into the scoring model.

Key capabilities:
- **Ranked candidate list** per job, scored by HrFlow's native matching engine combined with an LLM adjustment layer
- **AI synthesis** — structured summary, strengths, weaknesses, upskilling recommendations, and a hire verdict, auto-generated on upload and refreshable on demand
- **Extra documents** — attach plain text, PDF, DOCX, or audio files to any candidate; each document is individually scored by the LLM and contributes a delta to the total score
- **Interview question generator** — tailored questions based on the candidate's profile and attached documents
- **Recruitment pipeline** — customisable stages per job (Screening, Interview, Technical Test, …) with real-time stage tracking
- **HR bonus** — manual score adjustment (±) on top of the AI score
- **Job management** — create jobs, set operational status (Open / On Hold / Closed), manage custom pipeline stages

## HrFlow.ai APIs used

| Endpoint | Usage |
|----------|-------|
| `POST /v1/profile/parsing/file` | Parse a PDF resume and create a candidate profile |
| `GET /v1/profile/indexing` | Fetch a full candidate profile (skills, experiences, tags, metadata) |
| `PUT /v1/profile/indexing` | Store scores, synthesis, stage, and extra documents in profile tags/metadata |
| `POST /v1/tracking/indexing` | Link a candidate profile to a job (creates the application) |
| `GET /v1/tracking/searching` | List all candidates who applied to a given job |
| `GET /v1/job/indexing` | Fetch a single job's full data |
| `POST /v1/job/indexing` | Create a new job in the board |
| `GET /v1/job/searching` | List all jobs in the board |
| `POST /v1/score/searching` | Compute HrFlow's native matching score between a profile and a job |

## How to run

### Prerequisites

- Docker & Docker Compose
- An [HrFlow.ai](https://hrflow.ai) account with an API key, source key, and board key
- An [OpenRouter](https://openrouter.ai) API key (or any OpenAI-compatible LLM endpoint)

### Setup

```bash
# Clone the repo
git clone <repo-url>
cd HEpiR-HREvolution

# Copy and fill in credentials
cp .env.example .env
# Edit .env with your actual keys

# Build and start the full stack
docker compose up --build
```

- **Frontend** → http://localhost:3000
- **API / Swagger UI** → http://localhost:8080/docs

### Environment variables

| Variable | Required | Description |
|----------|----------|-------------|
| `HRFLOW_API_KEY` | Yes | HrFlow.ai API secret key |
| `HRFLOW_USER_EMAIL` | Yes | HrFlow.ai account email |
| `HRFLOW_SOURCE_KEY` | Yes | HrFlow.ai source key (profile storage) |
| `HRFLOW_BOARD_KEY` | Yes | HrFlow.ai board key (job storage) |
| `LLM_API_KEY` | Yes | OpenRouter (or compatible) API key |
| `LLM_BASE_URL` | Yes | LLM base URL (default: `https://openrouter.ai/api/v1`) |
| `LLM_MODEL` | Yes | Model for grading/synthesis (e.g. `nvidia/nemotron-super-49b-v1:free`) |

## Architecture

```
frontend/ React 18 + Vite — dashboard UI
backend/ Python 3.12 + FastAPI — orchestration layer
├── routers/jobs.py job CRUD + stage pipeline
├── routers/candidates.py profile, score, documents, file upload
├── routers/ai.py grading, synthesis, interview questions
└── services/
├── hrflow.py HrFlow API client
└── llm.py OpenRouter LLM calls
```

No local database — HrFlow is the single source of truth. Scores, synthesis, and extra documents are stored directly in profile tags and metadata.

## Team

- **Adrien CAPITAINE** — Developer
- **Nathan CHAMPAGNE** — Developer
- **Joris BELY** — Developer
16 changes: 16 additions & 0 deletions HEpiR-HREvolution/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "../../schemas/app.schema.json",
"name": "HEpiR — HR Evolution",
"description": "AI-powered recruitment dashboard. Ranks candidates per job using HrFlow scoring combined with an LLM adjustment layer, auto-generates structured synthesis reports, and lets HR attach documents (PDF, DOCX, audio) that feed back into the score.",
"credentials": {
"source_keys": [],
"board_keys": [],
"algorithm_key": ""
},
"settings": {
"team_name": "HEpiR",
"theme_color": "#1264a3",
"custom_filters": [],
"filters": []
}
}
Binary file added HEpiR-HREvolution/assets/preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions HEpiR-HREvolution/backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8080
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080", "--reload"]
19 changes: 19 additions & 0 deletions HEpiR-HREvolution/backend/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from pydantic_settings import BaseSettings


class Settings(BaseSettings):
hrflow_api_key: str
hrflow_user_email: str
hrflow_source_key: str
hrflow_board_key: str

llm_api_key: str
llm_base_url: str = "https://openrouter.ai/api/v1"
llm_model: str = "nvidia/nemotron-3-super-120b-a12b:free"

class Config:
env_file = ".env"
env_file_encoding = "utf-8"


settings = Settings()
23 changes: 23 additions & 0 deletions HEpiR-HREvolution/backend/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from routers import jobs, candidates, ai

app = FastAPI(title="HRFlow Hackathon API", version="1.0.0", redirect_slashes=False)

app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

app.include_router(jobs.router, prefix="/api/jobs", tags=["jobs"])
app.include_router(candidates.router, prefix="/api/candidates", tags=["candidates"])
app.include_router(ai.router, prefix="/api/ai", tags=["ai"])


@app.get("/health")
def health():
return {"status": "ok"}
10 changes: 10 additions & 0 deletions HEpiR-HREvolution/backend/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
fastapi==0.115.0
uvicorn[standard]==0.30.6
httpx==0.27.2
python-dotenv==1.0.1
pydantic==2.9.2
pydantic-settings==2.5.2
openai==1.51.0
python-multipart==0.0.12
pypdf==5.1.0
python-docx==1.1.2
Empty file.
Loading
Loading