Resume analysis and job matching built as a RAG system: parse a resume, embed chunks in PostgreSQL (pgvector), retrieve the evidence that matters for a specific job description, then generate grounded match feedback with OpenAI.
Live demo: https://smarthire-web-1w1z.onrender.com
API health: https://smarthire-api-rhhp.onrender.com/api/health
Free Render tier: the API may sleep after idle and take ~1 minute to wake.
| Layer | Choice |
|---|---|
| UI | React + TypeScript (Vite) |
| API | Spring Boot 3 · Java 17 |
| AI | OpenAI embeddings (text-embedding-3-small) + chat (gpt-4o-mini) |
| Data | PostgreSQL 16 + pgvector |
| Run / ship | Docker Compose locally · Render in production |
- Create a profile and upload a master resume (PDF/DOCX)
- Paste a job description on Job Match
- Backend indexes the resume (chunk → embed → pgvector), retrieves top matching passages, and returns scores, gaps, ATS keywords, rewrite ideas, and evidence snippets
- Adjust preferences in Settings (stored as JSON on the user record)
There is no real login yet — the browser keeps a currentUserId in localStorage. Fine for a portfolio demo; not for a wide public launch (anyone with the URL can burn your OpenAI quota).
React UI ──REST──► Spring Boot
│
├─ parse PDF/DOCX → chunks
├─ OpenAI embeddings → pgvector
├─ similarity search (top-k)
└─ chat model → structured JSON analysis
PostgreSQL
This did not start as a finished architecture. It grew one working slice at a time. Full write-up: docs/BUILD_JOURNEY.md. Decision log: docs/DECISIONS.md.
Stood up Spring Boot, Flyway, and Postgres with pgvector in Docker. Tables for resumes, chunks (vector(1536)), jobs, and analyses. Health endpoint before any AI.
Friction: machine had JDK 11; Spring Boot 3 needs 17. Installed Microsoft OpenJDK 17 and pinned JAVA_HOME for local runs.
Upload → PDFBox/POI text extraction → character chunking (~800 / 100 overlap) → persist chunks. Jobs as their own API. Status on resumes tracks PARSED → CHUNKED → later INDEXED.
Why this order: deterministic prep is easier to debug than jumping straight to LLM calls.
OpenAI client with retries, JDBC VectorStore (kept vectors out of JPA), indexing + analysis endpoints. Analysis retrieves top-k chunks for this resume + JD, then asks the model for JSON (score, gaps, recommendations, evidence).
Friction: empty/placeholder OPENAI_API_KEY → 401. Added repo-root .env loading and clearer OpenAI error payloads. Full loop worked after a real key: upload → index → analyze.
Vite React app: upload, paste JD, show results including retrieved chunks (so grounding is visible). Vite /api proxy for local CORS; Spring CORS kept for non-proxy clients.
Grew from a single match screen into three sections. Users table + profile_data / account_settings JSONB. Master resume owned by a user. Still deferred real auth.
Multi-stage Dockerfiles for API and nginx UI; Compose runs db + api + web. Local port conflicts (e.g. Maven Java vs container API) fixed by stopping the local process and bringing Compose services up cleanly.
Blueprint: static site + Docker web service + free Postgres. Several real failures along the way:
| Problem | What I changed |
|---|---|
Static site Blueprint rejected region |
Removed region from the static service |
| API OOM risk on 512MB free instances | Smaller heap (-Xmx256m), SerialGC |
| DB connection timeouts | Discrete DB_* env vars; then external Render hostname + sslmode=require when private host was unreachable |
| Wrong frontend API URL after Render renamed services | Set VITE_API_BASE_URL to the live API host and rebuilt the static site |
| Free-tier sleep / suspension quirks | Documented cold starts; keep demo traffic light |
Live app: https://smarthire-web-1w1z.onrender.com
backend/ Spring Boot API, Flyway, RAG services
frontend/ React UI (Profile, Match, Settings)
docs/ Journey, decisions, deploy notes, case study
samples/ Example resume / job payloads
deploy/ VPS helper script
docker-compose.yml / docker-compose.prod.yml
render.yaml Render Blueprint
cd C:\Users\sairo\OneDrive\Desktop\Project1
copy .env.example .env # set OPENAI_API_KEY
docker compose up -d --buildAPI-only local (DB in Docker):
docker compose up -d db
$env:JAVA_HOME = "C:\Program Files\Microsoft\jdk-17.0.20.8-hotspot"
$env:PATH = "$env:JAVA_HOME\bin;" + $env:PATH
cd backend; mvn spring-boot:run
cd frontend; npm install; npm run dev # http://localhost:5173| Doc | What you’ll find |
|---|---|
docs/BUILD_JOURNEY.md |
Step-by-step build history, problems, fixes |
docs/DECISIONS.md |
Why each major technical choice was made |
docs/CASE_STUDY.md |
Product context and what the MVP includes |
docs/RENDER_DEPLOY.md |
How the Render deploy is wired |
docs/PUBLIC_DEPLOY.md |
VPS alternative |
docs/AWS_DEPLOY.md |
ECS/RDS-shaped path |
- Real authentication before sharing the demo widely
- Wire richer profile sections into the RAG prompt
- CI + optional HNSW once embedding volume grows
- Harden free-tier DB networking (prefer private URL when it works)