Skip to content

Commit 93badd1

Browse files
ppradyothclaude
andcommitted
Add dark mode, logo, proper website layout, and Firebase deploy
- Add SVG document logo mark (black/white, used as favicon + inline component) - Add dark/light mode toggle with localStorage persistence and system preference detection - Add engine field to AnalyzeResponse (gemini | heuristic) surfaced in result UI - Redesign header: sticky nav with logo, GitHub link, theme toggle, health badge - Add hero section with CTA, tech stack pills, and staggered load animations - Add "How it works" 3-step section - Add proper footer with brand, links, and copyright - Rebuild AnalysisForm with tab-based text/file input switcher - Add loading skeleton and empty state preview cards to ResultPanel - Dynamic score ring colour (green/amber/red) based on score band - Per-section icons (missing skills, strengths, recommendations) - Full dark mode coverage across all components and CSS utilities - Add CLAUDE.md codebase guide - Wire Firebase Hosting to ml-ops-42f07 project Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 4e24ad3 commit 93badd1

15 files changed

Lines changed: 708 additions & 120 deletions

CLAUDE.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
AI Resume Analyzer — a fullstack app that accepts a resume (PDF/TXT/text) and target role, calls the Gemini API for structured ATS scoring, and returns `{ ats_score, missing_skills, strengths, recommendations }`. The AI layer has a heuristic fallback so the app works without a Gemini key (used in CI and local demos).
8+
9+
## Commands
10+
11+
### Backend
12+
13+
```bash
14+
cd backend
15+
python3.11 -m venv .venv && source .venv/bin/activate
16+
pip install -r requirements.txt
17+
uvicorn app.main:app --reload --host 127.0.0.1 --port 8000
18+
```
19+
20+
Run tests (from `backend/` with venv active):
21+
```bash
22+
pytest # all tests
23+
pytest tests/test_analyze.py # single file
24+
```
25+
26+
Or from the repo root (no venv needed if installed globally):
27+
```bash
28+
npm run backend:test
29+
```
30+
31+
### Frontend
32+
33+
```bash
34+
cd frontend
35+
npm install
36+
npm run dev # http://localhost:5173
37+
npm run build
38+
```
39+
40+
### Docker
41+
42+
```bash
43+
docker compose up --build backend # backend only at :8000
44+
```
45+
46+
### Environment setup
47+
48+
```bash
49+
cp .env.example .env
50+
cp frontend/.env.example frontend/.env
51+
```
52+
53+
Set `GEMINI_API_KEY` in `.env` for live AI; leave `ENABLE_AI_FALLBACK=true` to use heuristics instead.
54+
55+
## Architecture
56+
57+
```
58+
Request → FastAPI (routers/analyze.py)
59+
→ ResumeAnalyzerService (services/resume_service.py)
60+
→ input extraction (utils/pdf.py)
61+
→ Pydantic validation (schemas/resume.py: AnalyzePayload)
62+
→ GeminiResumeService (services/gemini_service.py)
63+
→ retry_async (utils/retry.py, exponential backoff + jitter)
64+
→ Gemini API with response_json_schema enforcement
65+
[on failure or no key]
66+
→ HeuristicResumeService (services/heuristic_service.py)
67+
→ AnalyzeResponse (Pydantic, validated on the way out)
68+
monitoring_middleware → metrics singleton (services/monitoring_service.py)
69+
```
70+
71+
### Key architectural rules
72+
73+
- **Strict schema enforcement everywhere.** `AnalyzePayload` validates inputs before any AI call. `AnalyzeResponse` validates Gemini output via `model_validate_json`. `ANALYSIS_JSON_SCHEMA` is passed to Gemini's `response_json_schema` to constrain generation at the API level.
74+
- **Fallback is controlled by `ENABLE_AI_FALLBACK`.** In production (`render.yaml`) it is `false` so Gemini failures surface as errors. In CI and local dev it defaults to `true`.
75+
- **All config lives in `app/config.py` (`Settings`).** Access via `get_settings()` (lru_cache). Never read env vars directly in route or service code.
76+
- **New endpoints** require a router in `app/routers/`, a service in `app/services/`, and Pydantic schemas in `app/schemas/`. Wire the router into `app/main.py`.
77+
- **Error types** (`AppError`, `BadRequestError`, `AIServiceError`, etc.) are in `app/utils/exceptions.py`. The global handlers in `main.py` convert them to consistent JSON with `request_id`.
78+
- **Monitoring** is in-memory only — `metrics` singleton in `monitoring_service.py`, recorded by `monitoring_middleware` in `main.py`. Not persistent across restarts.
79+
80+
### Frontend
81+
82+
Single-page React app (`frontend/src/App.jsx`). Components: `AnalysisForm`, `ResultPanel`, `HealthBadge`, `ErrorBanner`, `ErrorBoundary`. API calls are in `frontend/src/lib/api.js`. Backend URL is set via `VITE_API_BASE_URL`.
83+
84+
## Deployment
85+
86+
- **Backend**: Dockerized, deployed to Render via `render.yaml`. Health check on `/health`.
87+
- **Frontend**: Static Vite build, deployed to Firebase Hosting (`frontend/firebase.json`). Set `VITE_API_BASE_URL` to the Render URL before `npm run build`.
88+
- **CI**: GitHub Actions (`.github/workflows/ci.yml`) runs `pytest` (backend) and `npm run build` (frontend) on every push/PR to `main`. Tests run with `ENABLE_AI_FALLBACK=true`.
89+
90+
## Environment Variables
91+
92+
| Variable | Default | Purpose |
93+
|---|---|---|
94+
| `GEMINI_API_KEY` || Required for live AI; omit to use heuristic fallback |
95+
| `ENABLE_AI_FALLBACK` | `true` | Fall back to heuristics when Gemini fails or is unconfigured |
96+
| `GEMINI_MODEL` | `gemini-2.5-flash` | Gemini model name |
97+
| `CORS_ORIGINS` | `http://localhost:5173` | Comma-separated allowed origins |
98+
| `ENVIRONMENT` | `development` | Surfaced in `/health` response |
99+
| `MAX_RESUME_CHARS` | `20000` | Resume text is truncated to this before AI call |
100+
| `MAX_UPLOAD_MB` | `5` | Max file upload size |

backend/app/schemas/resume.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Literal
2+
13
from pydantic import BaseModel, Field, field_validator
24

35

@@ -19,6 +21,7 @@ class AnalyzeResponse(BaseModel):
1921
missing_skills: list[str] = Field(default_factory=list)
2022
strengths: list[str] = Field(default_factory=list)
2123
recommendations: list[str] = Field(default_factory=list)
24+
engine: Literal["gemini", "heuristic"] = "heuristic"
2225

2326
@field_validator("missing_skills", "strengths", "recommendations")
2427
@classmethod

backend/app/services/gemini_service.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ def _parse_response(self, response) -> AnalyzeResponse:
7474
raise AIOutputError("Gemini returned an empty response.")
7575

7676
try:
77-
return AnalyzeResponse.model_validate_json(raw_text)
77+
result = AnalyzeResponse.model_validate_json(raw_text)
78+
return result.model_copy(update={"engine": "gemini"})
7879
except json.JSONDecodeError as exc:
7980
logger.warning("malformed_ai_json", extra={"raw_output": raw_text[:1000]})
8081
raise AIOutputError("Gemini returned malformed JSON.") from exc

backend/app/services/heuristic_service.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ async def analyze(self, payload: AnalyzePayload) -> AnalyzeResponse:
5656
missing_skills=missing_skills,
5757
strengths=strengths,
5858
recommendations=recommendations,
59+
engine="heuristic",
5960
)
6061

6162
def _strengths(self, text: str, resume_skills: set[str]) -> list[str]:

frontend/.firebaserc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"projects": {
3+
"default": "ml-ops-42f07"
4+
}
5+
}
6+

frontend/index.html

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
66
<meta
77
name="description"
8-
content="AI Resume Analyzer and Career Assistant for ATS scoring and recommendations."
8+
content="Get an ATS score, skill gap breakdown, and targeted recommendations for your resume — powered by Gemini."
9+
/>
10+
<title>Resume Analyzer — ATS Scoring by Gemini</title>
11+
<link
12+
rel="icon"
13+
type="image/svg+xml"
14+
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 44 44' fill='none'%3E%3Cpath d='M8 5C8 3.34315 9.34315 2 11 2H27L37 12V40C37 41.6569 35.6569 43 34 43H11C9.34315 43 8 41.6569 8 40V5Z' stroke='black' stroke-width='2.5'/%3E%3Cpath d='M27 2.5V11C27 11.5523 27.4477 12 28 12H36.5' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cline x1='14' y1='20' x2='31' y2='20' stroke='black' stroke-width='2.5' stroke-linecap='round'/%3E%3Cline x1='14' y1='26' x2='26' y2='26' stroke='black' stroke-width='2.5' stroke-linecap='round'/%3E%3Cline x1='14' y1='32' x2='29' y2='32' stroke='black' stroke-width='2.5' stroke-linecap='round'/%3E%3C/svg%3E"
915
/>
10-
<title>AI Resume Analyzer</title>
1116
</head>
1217
<body>
1318
<div id="root"></div>

0 commit comments

Comments
 (0)