Inspiration
Recruiters reviewing high-volume applicant pools (some postings crack 10,000+ submissions) are stuck asking one question at a time: "does this individual résumé look fake?" Meanwhile résumé fraud is getting harder to catch by eye — AI-generated résumés, prompt-injection attacks hidden in PDF metadata, and coordinated groups submitting near-identical applications. We wanted to flip the framing: instead of judging applicants one by one, analyze the whole applicant population as a network, and only escalate to a human when there's real evidence something is off — not a black-box "fraud score."
What it does
Applicant Trust Intelligence (ATI) ingests a corpus of résumés and produces two separate signals for every candidate instead of one opaque number:
$$ \text{Qualification} \in [0, 100], \qquad \text{Trust Evidence} \in [0, 100] $$
- Population Intelligence — embeds every résumé, reduces dimensionality with UMAP, and clusters with HDBSCAN to surface unusually similar or coordinated groups of applications.
- Claim Provenance — extracts discrete claims from each résumé (employer, degree, GitHub, project work) and checks them against external evidence like GitHub history and account age.
- Anomaly Detection — feeds engineered features (similarity scores, timeline gaps, submission velocity, etc.) into an Isolation Forest to flag statistically unusual applicants.
- Adaptive Verification — instead of auto-rejecting, the system asks for the minimum verification needed to resolve a specific uncertainty (e.g. GitHub OAuth to confirm repo ownership).
The output is a recruiter dashboard that turns 10,000 applications into a prioritized shortlist, with full explainability for every flag.
How we built it
- Backend: Java 21 + Spring Boot 3 (Maven) serving the core API, orchestrating the trust pipeline, and persisting to PostgreSQL.
- Frontend: React + Vite + TypeScript for the recruiter dashboard (cluster visualizations, applicant landscape, claim provenance views).
- ML service: a separate Python FastAPI microservice handling embeddings, UMAP, HDBSCAN, and Isolation Forest, called by the Spring Boot backend.
- We built the pipeline in phases — application integrity checks first, then population-level clustering, then claim provenance — validating each stage before layering on the next.
Challenges we ran into
- We started on a MERN/Mongoose/Prisma stack and hit friction with schema rigidity and typing across the fraud/trust data model, so we made the call mid-hackathon to restart on Spring Boot + PostgreSQL — a real cost in hours, but it paid off in reliability.
- Splitting responsibilities between the Java backend and the Python ML service meant getting the service boundary and API contract right early, so embeddings/clustering results could flow back into the trust graph without duplicating logic.
- Keeping "qualification" and "trust" as genuinely separate scores (rather than collapsing them into one fraud probability) took discipline — it's tempting to just output a single number, but that's exactly the black-box behavior we were trying to avoid.
What we learned
- Fraud detection in hiring is as much a product and policy problem as a technical one — a hard reject on weak evidence is a legal and ethical liability (NYC's AEDT law was front of mind), so "flag for human review" is almost always the right default over automation.
- Population-level signals (clustering, embedding similarity) catch categories of fraud that per-candidate checks structurally can't — coordinated résumé rings only become visible in aggregate.
- Splitting a monolith into a dedicated ML microservice earlier rather than later made iteration on the clustering/anomaly pipeline much faster, since we could redeploy it independently of the Spring Boot API.
- "Unverified" and "false" are not the same thing, and building that distinction into the UI (rather than just the backend logic) was as important as the modeling itself.
Built With
- fastapi
- github
- joblib
- plotly
- python
- restapi
- scikit-learn
- streamlit
- umap
Log in or sign up for Devpost to join the conversation.