Skip to content

AM10101010/rationale

Repository files navigation

Rationale

Explainable AI candidate screening

Rank candidates against a job — and prove the ranking. Every score comes with the evidence behind it.

Proof of concept · .NET 10 · Blazor · Supabase · Azure OpenAI


Contents


Overview

Hiring teams increasingly use AI to rank candidates. The catch: most tools hand back a score with no explanation. That's hard to trust, hard to defend to a hiring manager or a candidate, and impossible to reproduce or audit later.

Rationale takes a different approach — it shows its work. For every candidate, instead of a single mystery number, you get:

  • 📊 A score per criterion — must-haves, nice-to-haves, experience, domain fit, and seniority — rather than one opaque total.
  • 📌 The evidence behind each score — the exact lines from the CV that justify it.
  • ⚠️ Missing must-haves flagged up front.
  • 🔁 Repeatable results — the same candidate and job produce the same outcome, so a decision can be revisited and explained.
  • ⚖️ Fair by design — only the CV's content is scored. Names and contact details are stored, but never seen by the AI.

The guiding idea: the AI explains, the code decides the number. The AI reads a CV and produces the building blocks — a score per criterion and the evidence for each. The application then calculates the overall score itself. The AI never sets the final number, so every result traces back to evidence.


What's included

A deliberately small, focused product — a screening engine wrapped in a lightweight mini-ATS, not a full applicant tracking system.

✅ Included 🚫 Out of scope (by design)
Candidate management (manual entry or import from a CV) Candidate sourcing
Job definitions to screen against Messaging / candidate communications
AI screening with cited evidence Dashboards & reporting
A three-stage triage Board (New → In Review → Decided) Configurable pipelines / SLAs
Admin-only user management with roles

How it works

Three things happen behind the scenes — signing in, screening, and saving results:

  1. Sign in. You log in, and Supabase confirms who you are and what you're allowed to do.
  2. Screen. You pick a job and run screening. Each CV goes to the AI, which scores it against the job's criteria and cites its evidence. The app combines those into an overall score and ranks the candidates.
  3. Save. The whole run is stored, so it can be reopened, shared, and audited later — no need to re-run the AI.
See the detailed flow (sign-in, screening, and storage)
sequenceDiagram
    actor R as Recruiter
    participant W as Web (Blazor Server)
    participant AUTH as Supabase Auth
    participant API as API
    participant AOAI as Azure OpenAI
    participant DB as Supabase Postgres

    rect rgb(238, 246, 255)
    Note over R,AUTH: 1 · Authentication
    R->>W: Sign in (email + password)
    W->>AUTH: Authenticate
    AUTH-->>W: Secure token (role included)
    end

    rect rgb(238, 255, 244)
    Note over R,DB: 2 · Candidate screening
    R->>W: "Screen candidates" for a job
    W->>API: Request screening (with token)
    API->>AUTH: Verify token
    API->>DB: Load job + candidates
    loop Each candidate
        API->>AOAI: Score CV content only
        AOAI-->>API: Per-criterion scores + cited evidence
    end
    API->>API: Calculate overall score (in code)
    end

    rect rgb(255, 248, 236)
    Note over API,DB: 3 · Data storage
    API->>DB: Save the run (results + evidence)
    API-->>W: Ranked candidates
    W-->>R: Explainable ranked list
    end
Loading

Architecture

Built with Clean Architecture, scaled to a POC. Dependencies point inward — Core knows nothing about the database, the AI provider, or the web framework — and the boundary is enforced at compile time (Api → Infrastructure → Core, Web → Core).

System at a glance

flowchart LR
    R["Recruiter<br/>(browser)"]

    subgraph host["Azure App Service"]
        W["MiniAts.Web<br/>Blazor Server"]
        API["MiniAts.Api<br/>Minimal API"]
    end

    subgraph svc["Managed services"]
        AUTH["Supabase Auth<br/>ES256 / JWKS"]
        DB[("Supabase Postgres")]
        AOAI["Azure OpenAI<br/>gpt-5.4-mini"]
    end

    R --> W
    W -->|"sign in"| AUTH
    W -->|"HTTPS + token"| API
    API -->|"verify token"| AUTH
    API -->|"EF Core / Npgsql"| DB
    API -->|"screening + CV extraction"| AOAI
Loading

In plain terms: the browser talks to the web app; the web app talks to the API; the API is the only part that touches the database, the login service, and the AI.

Inside the screening engine

flowchart TD
    A["POST /jobs/{id}/screen"] --> SJ["ScreenJob use-case · Core"]
    SJ --> L["Load job + candidates<br/>from Postgres"]
    L --> LOOP["For each candidate<br/>(capped concurrency)"]
    LOOP --> P["AI scoring provider<br/>one structured call per candidate"]
    P --> SCH["Strict JSON schema from Criterion enum<br/>fixed seed · versioned prompt<br/>CV text only — fairness"]
    SCH --> OUT["Per-criterion scores<br/>+ cited evidence + missing must-haves<br/>(no overall-score field)"]
    OUT --> SC["Scorer computes weighted overall<br/>(code owns the number)"]
    SC --> PER["Persist results<br/>one RunId · jsonb breakdown"]
    PER --> RANK["Ranked list + evidence"]
Loading

In plain terms: each candidate is scored in its own AI call using only their CV; the app — not the AI — calculates the overall score and saves the whole run together.

Resilience. Transient errors are retried with backoff; a single failed candidate is skipped rather than failing the run; if the AI isn't configured, screening returns a clean error instead of a misleading empty result.

Project layout

Project Role
MiniAts.Core Domain + application logic. Pure C#, zero infrastructure dependencies.
MiniAts.Infrastructure The database (EF Core / Postgres), Azure OpenAI, and CV text extraction.
MiniAts.Api Minimal API + the app's composition root. Validates Supabase tokens.
MiniAts.Web Blazor Server UI — CRUD, CV import, the triage Board, and user management.
tests/MiniAts.UnitTests Fast, offline tests (dotnet test).
tests/MiniAts.Evals AI evaluation suite that runs against the real model.

For the full architecture handoff and design invariants, see AGENTS.md and CLAUDE.md.


Running it locally

# 1. Local Postgres (throwaway creds) — or point the connection string at Supabase
docker compose up -d

# 2. Apply migrations  (install once: dotnet tool install --global dotnet-ef)
dotnet ef database update -p src/MiniAts.Infrastructure -s src/MiniAts.Api

# 3. Load the synthetic seed (1 job, 18 proof candidates)
dotnet run --project src/MiniAts.Api -- seed

# 4. Run the API and the web app (separate terminals)
dotnet run --project src/MiniAts.Api      # http://localhost:5103
dotnet run --project src/MiniAts.Web      # http://localhost:5104

# Tests
dotnet test                               # fast, offline, free
dotnet run --project tests/MiniAts.Evals  # AI eval — hits the real model, costs money

Configuration & secrets

Secrets live in user-secrets or environment variables — never in tracked appsettings*.json. Endpoint and deployment names are not secret; keys, passwords, and connection strings are. Each integration degrades gracefully when unconfigured: the app still runs, and the affected feature fails clearly only when used.

dotnet user-secrets set "ConnectionStrings:Postgres" "<supabase-session-pooler-conn>" --project src/MiniAts.Api
dotnet user-secrets set "AzureOpenAI:Endpoint"   "<https://...>"   --project src/MiniAts.Api
dotnet user-secrets set "AzureOpenAI:Deployment" "gpt-5.4-mini"    --project src/MiniAts.Api
dotnet user-secrets set "AzureOpenAI:ApiKey"     "<key>"          --project src/MiniAts.Api

Roles & access

Sign-in is handled by Supabase. Access is role-based:

Role Can do
admin Everything — create, update, delete, and manage users.
member Read-only — view candidates, jobs, screenings, and the Board.

Extending Rationale

Rationale's core is the screening engine — explainable, ranked candidate assessment with a human deciding. The ideas below are illustrative extensions that build on that core to automate more of the hiring workflow. They are roadmap suggestions, not built features, and each keeps a human in the loop.

AI interview-prep assistant

Rationale can act as a service for external automation. As one example, an LLM-based assistant can register a candidate, read the structured screening result — per-criterion scores, gaps, and cited evidence, not just a pass/fail — and compose a tailored interview brief that probes exactly the criteria a candidate scored low or uncertain on for the role they matched. A recruiter reviews and approves the brief before any interview runs.

The assistant prepares and explains; the recruiter decides. Rationale passes the reasons, not just a verdict.

flowchart TD
    A[External AI assistant] -->|registers candidate| B[Rationale screening]
    B -->|structured result: scores, gaps, cited evidence| A
    A -->|composes tailored interview brief from the gaps| C[Draft interview brief]
    C --> D{Recruiter approves?}
    D -- Approve / edit --> E[Tailored interview proceeds]
    D -- Reject --> F[Recruiter decides next step]
Loading

Drop-folder CV intake

A lightweight intake channel that keeps email out of the platform entirely. A recruiter reviews an incoming application in their own mailbox (where corporate spam/malware filtering has already scanned the attachment) and saves the CV to a watched location. A trigger then registers it as a pending candidate in Rationale, ready to register and screen through the existing pipeline.

The recruiter is the relevance gate; the mailbox scan is the malware gate. A human still approves registration and screening.

flowchart TD
    A[Candidate emails CV] --> B[Recruiter reviews in mailbox<br/>spam + malware already filtered]
    B --> C[Saves CV to watched storage]
    C --> D[Trigger: blob-created event / poll]
    D --> E[File scanned, then surfaced as a pending candidate]
    E --> F[Recruiter registers + picks job to screen against]
    F --> G[Existing pipeline: Extract -> Register -> Screen]
Loading

About

An explainable AI candidate-screening engine. Proves every ranking with cited résumé evidence, reproducible scoring, and a built-in fairness check.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors