Skip to content

Commit 540fc75

Browse files
committed
feat: frontend redesign, dashboard API, auto-create applications
- Complete frontend visual overhaul with indigo/purple gradients, animations (fadeIn, slideUp, shimmer), glass morphism, hover effects - Dashboard now fetches live stats and recent analyses from new /dashboard/stats and /dashboard/recent-analyses API endpoints - Analysis service auto-creates Application record (status: wishlist) after each successful agent run, linking analysis + cover letter - Updated CLAUDE.md with current project state, completed phases, and key changes log
1 parent 2cd3131 commit 540fc75

21 files changed

Lines changed: 1871 additions & 471 deletions

File tree

CLAUDE.md

Lines changed: 68 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ NextHire is an **agentic AI system** that helps job seekers analyze job postings
66

77
This is NOT a simple "prompt-in, text-out" wrapper. It is a **multi-step agent** that plans, executes tools, evaluates its own output, and iterates. The agent uses **multi-model routing** (cheap model for simple tasks, powerful model for reasoning) and streams its execution steps to the frontend in real-time.
88

9-
**Live Architecture:** Next.js frontend → FastAPI backend → LangGraph Agent → AWS Bedrock (Claude Haiku 4.5 + Claude Sonnet)
9+
**Live Architecture:** Next.js frontend → FastAPI backend → LangGraph Agent → AWS Bedrock (Claude Haiku 4.5 + Claude Sonnet 4)
10+
11+
**Current Status (March 2026):** Core pipeline fully operational. Analysis flow works end-to-end: CV upload + job description → 7-node agent pipeline → ATS score, gap analysis, cover letter → auto-creates kanban application. Dashboard shows live stats. CI/CD passes.
1012

1113
---
1214

@@ -200,6 +202,7 @@ NextHire/
200202
│ │ │ ├── cover_letter.py # POST /api/v1/cover-letter
201203
│ │ │ ├── cv_review.py # POST /api/v1/cv-review
202204
│ │ │ ├── agent.py # GET /api/v1/agent/stream/{run_id} (SSE)
205+
│ │ │ ├── dashboard.py # GET /api/v1/dashboard/stats, /recent-analyses
203206
│ │ │ └── health.py # GET /api/v1/health
204207
│ │ │
205208
│ │ ├── models/ # SQLAlchemy ORM models
@@ -272,7 +275,7 @@ NextHire/
272275
├── frontend/
273276
│ ├── Dockerfile
274277
│ ├── package.json
275-
│ ├── next.config.ts
278+
│ ├── next.config.mjs
276279
│ ├── tailwind.config.ts
277280
│ ├── tsconfig.json
278281
│ ├── postcss.config.mjs
@@ -568,36 +571,36 @@ docker compose exec backend pytest -v # Tests in container
568571

569572
## Implementation Order (Phases)
570573

571-
### Phase 1: Foundation (Day 1-2)
572-
- [ ] Project scaffolding (monorepo structure)
573-
- [ ] Docker Compose (FastAPI + Next.js + PostgreSQL)
574-
- [ ] Database models + Alembic migrations
575-
- [ ] Basic CRUD API for applications (kanban data)
576-
- [ ] LLM abstraction layer + Bedrock provider
577-
- [ ] Health check endpoints
578-
- [ ] Frontend: Layout, sidebar, routing
579-
580-
### Phase 2: Agent Core (Day 3-5)
581-
- [ ] LangGraph agent graph definition
582-
- [ ] Agent state schema
583-
- [ ] Tools: cv_parser, job_scraper, keyword_extractor, semantic_scorer
584-
- [ ] Nodes: parse_job, parse_cv, analyze_match
585-
- [ ] SSE streaming endpoint
586-
- [ ] Frontend: Agent stream viewer component
587-
588-
### Phase 3: Full Agent Pipeline (Day 6-8)
589-
- [ ] Nodes: identify_gaps, generate_cover_letter, reflect, compile_report
590-
- [ ] Reflection loop with quality gate
591-
- [ ] Multi-model routing (Haiku for parse, Sonnet for analysis)
592-
- [ ] Analysis results stored in DB
593-
- [ ] Frontend: Analysis report page (score card, skill match, gaps)
594-
595-
### Phase 4: Application Tracking + Polish (Day 9-10)
596-
- [ ] Kanban board frontend (drag-and-drop)
597-
- [ ] Cover letter editor
598-
- [ ] Link analysis → application → cover letter
599-
- [ ] Dashboard with stats
600-
- [ ] CI/CD pipeline
574+
### Phase 1: Foundation
575+
- [x] Project scaffolding (monorepo structure)
576+
- [x] Docker Compose (FastAPI + Next.js + PostgreSQL)
577+
- [x] Database models + Alembic migrations
578+
- [x] Basic CRUD API for applications (kanban data)
579+
- [x] LLM abstraction layer + Bedrock provider
580+
- [x] Health check endpoints
581+
- [x] Frontend: Layout, sidebar, routing
582+
583+
### Phase 2: Agent Core
584+
- [x] LangGraph agent graph definition
585+
- [x] Agent state schema
586+
- [x] Tools: cv_parser, job_scraper, keyword_extractor, semantic_scorer
587+
- [x] Nodes: parse_job, parse_cv, analyze_match
588+
- [x] SSE streaming endpoint
589+
- [x] Frontend: Agent stream viewer component
590+
591+
### Phase 3: Full Agent Pipeline
592+
- [x] Nodes: identify_gaps, generate_cover_letter, reflect, compile_report
593+
- [x] Reflection loop with quality gate
594+
- [x] Multi-model routing (Haiku for parse, Sonnet for analysis)
595+
- [x] Analysis results stored in DB
596+
- [x] Frontend: Analysis report page (score card, skill match, gaps)
597+
598+
### Phase 4: Application Tracking + Polish
599+
- [x] Kanban board frontend (drag-and-drop)
600+
- [x] Cover letter editor
601+
- [x] Link analysis → application → cover letter (auto-creates Application on analysis completion)
602+
- [x] Dashboard with live stats (dynamic API-driven)
603+
- [x] CI/CD pipeline (GitHub Actions: lint, test, build, docker)
601604
- [ ] README + demo screenshots
602605

603606
### Phase 5: Future Enhancements (Backlog)
@@ -607,6 +610,7 @@ docker compose exec backend pytest -v # Tests in container
607610
- [ ] CV version management
608611
- [ ] Export reports as PDF
609612
- [ ] Email notifications for application deadlines
613+
- [ ] Real-time SSE streaming to frontend during agent execution (currently polls result)
610614

611615
---
612616

@@ -643,3 +647,35 @@ docker compose exec backend pytest -v # Tests in container
643647
6. **Docker Compose** is the source of truth for local development. Both frontend and backend should work with `docker compose up`.
644648

645649
7. **PostgreSQL JSONB** — use for analysis results, parsed job/CV data. Define Pydantic schemas for validation before storing.
650+
651+
8. **AWS Bedrock inference profiles** — New Claude models on Bedrock require inference profile IDs (prefixed with region, e.g. `eu.anthropic.claude-haiku-4-5-20251001-v1:0`). On-demand model IDs will return `ValidationException`.
652+
653+
9. **Analysis → Application auto-creation**`analysis_service.py` automatically creates an `Application` record (status `wishlist`) after each successful analysis, linking it to the analysis and cover letter. No manual application creation needed.
654+
655+
---
656+
657+
## Key Changes Log
658+
659+
### March 2026 — Full Pipeline Wiring + Frontend Redesign
660+
661+
**Backend:**
662+
- Wired `analysis_service.py` to actually invoke `agent_graph.ainvoke()` (was previously a TODO stub)
663+
- Fixed `bedrock.py` system prompt handling — `_get_system_prompt()` existed but was never called in `invoke()`/`stream()`
664+
- Added `dashboard.py` route with `/dashboard/stats` and `/dashboard/recent-analyses` endpoints
665+
- Auto-creation of `Application` records after each analysis (links analysis + cover letter to kanban)
666+
- Generated and applied initial Alembic migration for all 4 tables
667+
- Fixed 18+ ruff lint issues across the codebase
668+
- Corrected model IDs to use Bedrock inference profile format
669+
670+
**Frontend:**
671+
- Replaced `next.config.ts` with `next.config.mjs` (TS config not supported in Node 20 Alpine + Next.js 14.2)
672+
- Complete visual redesign: indigo/purple gradient palette, glass morphism, hover-lift cards
673+
- Added CSS animations: fadeIn, slideUp, slideIn, pulse-gentle, shimmer loading skeletons
674+
- Dashboard now fetches real stats via API (analyses count, applications count, avg score, recent analyses)
675+
- ScoreCard: SVG circular gauge with animated gradient stroke
676+
- Analysis report: vertical card stack with gradient accent stripes, strengths/weaknesses grid
677+
- Agent stream: vertical timeline with gradient connecting line, rich step descriptions
678+
- Sidebar: gradient logo, active nav indicator, "Powered by AI" footer
679+
680+
**CI/CD:**
681+
- Fixed all GitHub Actions jobs (backend-lint, frontend-lint, frontend-build)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from fastapi import APIRouter, Depends
2+
from sqlalchemy import func, select
3+
from sqlalchemy.ext.asyncio import AsyncSession
4+
5+
from app.api.deps import get_session
6+
from app.models.agent_run import AgentRun
7+
from app.models.application import Application
8+
from app.models.cover_letter import CoverLetter
9+
from app.models.job_analysis import JobAnalysis
10+
from app.schemas.job_analysis import JobAnalysisResponse
11+
12+
router = APIRouter()
13+
14+
15+
@router.get("/dashboard/stats")
16+
async def get_dashboard_stats(session: AsyncSession = Depends(get_session)):
17+
"""Get summary statistics for the dashboard."""
18+
analyses_count = await session.scalar(select(func.count(JobAnalysis.id)))
19+
applications_count = await session.scalar(select(func.count(Application.id)))
20+
cover_letters_count = await session.scalar(select(func.count(CoverLetter.id)))
21+
cvs_count = await session.scalar(
22+
select(func.count(func.distinct(JobAnalysis.cv_filename))).where(
23+
JobAnalysis.cv_filename.is_not(None)
24+
)
25+
)
26+
avg_score = await session.scalar(
27+
select(func.avg(JobAnalysis.overall_score)).where(
28+
JobAnalysis.overall_score.is_not(None)
29+
)
30+
)
31+
32+
return {
33+
"analyses_count": analyses_count or 0,
34+
"applications_count": applications_count or 0,
35+
"cover_letters_count": cover_letters_count or 0,
36+
"cvs_parsed_count": cvs_count or 0,
37+
"avg_score": round(avg_score, 1) if avg_score else 0,
38+
}
39+
40+
41+
@router.get("/dashboard/recent-analyses", response_model=list[JobAnalysisResponse])
42+
async def get_recent_analyses(
43+
limit: int = 10,
44+
session: AsyncSession = Depends(get_session),
45+
):
46+
"""Get recent analyses for the dashboard."""
47+
result = await session.execute(
48+
select(JobAnalysis)
49+
.where(JobAnalysis.overall_score.is_not(None))
50+
.order_by(JobAnalysis.created_at.desc())
51+
.limit(limit)
52+
)
53+
return result.scalars().all()

backend/app/main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from fastapi import FastAPI
44
from fastapi.middleware.cors import CORSMiddleware
55

6-
from app.api.routes import agent, analyze, applications, cover_letter, cv_review, health
6+
from app.api.routes import agent, analyze, applications, cover_letter, cv_review, dashboard, health
77
from app.config import settings
88

99

@@ -38,3 +38,4 @@ async def lifespan(app: FastAPI):
3838
app.include_router(cover_letter.router, prefix="/api/v1", tags=["cover-letter"])
3939
app.include_router(cv_review.router, prefix="/api/v1", tags=["cv-review"])
4040
app.include_router(agent.router, prefix="/api/v1", tags=["agent"])
41+
app.include_router(dashboard.router, prefix="/api/v1", tags=["dashboard"])

backend/app/services/analysis_service.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from app.agent.graph import agent_graph
77
from app.agent.tools.cv_parser import parse_cv as parse_cv_file
88
from app.models.agent_run import AgentRun
9+
from app.models.application import Application
910
from app.models.cover_letter import CoverLetter
1011
from app.models.job_analysis import JobAnalysis
1112

@@ -69,13 +70,28 @@ async def run_analysis(
6970

7071
# Create cover letter record if generated
7172
cover_letter_text = result.get("cover_letter")
73+
cover_letter_record = None
7274
if cover_letter_text:
73-
cover_letter = CoverLetter(
75+
cover_letter_record = CoverLetter(
7476
analysis_id=analysis.id,
7577
content=cover_letter_text,
7678
tone=result.get("cover_letter_tone", "professional"),
7779
)
78-
self.session.add(cover_letter)
80+
self.session.add(cover_letter_record)
81+
await self.session.flush()
82+
83+
# Auto-create application for kanban tracking
84+
application = Application(
85+
company_name=analysis.company_name or "Unknown Company",
86+
position_title=analysis.job_title or "Untitled Position",
87+
job_url=job_url,
88+
status="wishlist",
89+
priority="medium",
90+
match_score=analysis.overall_score,
91+
analysis_id=analysis.id,
92+
cover_letter_id=cover_letter_record.id if cover_letter_record else None,
93+
)
94+
self.session.add(application)
7995

8096
# Update agent run as completed
8197
agent_run.status = "completed"

0 commit comments

Comments
 (0)