v1.0 — Phase 1 MVP Live · GPT-4o-mini LangGraph 0.4.8 FastAPI + Next.js

TopNotchResume
Architecture Blueprint

Enterprise-grade AI Resume Intelligence Platform. Multi-agent orchestration via JARVIS (LangGraph), GPT-4o-mini powered semantic analysis, dynamic DOCX rendering, and ATS compliance scoring. Full request lifecycle from candidate profile → tailored .docx in under 60 seconds.

5
Intelligent Agents
5
OpenAI API Calls / Resume
4
REST Endpoints
0–100
ATS Score Range
<60s
End-to-End Generation
6
Resume Section Renderers

Contents

High-Level System Overview

Three-tier architecture: React frontend → FastAPI backend → multi-agent AI pipeline. All agent calls route through a single OpenAI async client with structured function-calling outputs.

Layer 1 — Client
Next.js 14
Resume Wizard
7-step form · SmartExtract · Progress UI
Tailwind CSS
Dark UI Theme
Landing · Generate · Download pages
TypeScript
lib/api.ts
Typed fetch wrappers · Error handling
POST /api/generate · POST /api/extract
↓
JSON · CORS · HTTP/1.1
Layer 2 — Backend API
FastAPI
app/main.py
CORS · Lifespan · Router mount
Pydantic v2
models/schema.py
Request validation · Type safety
Async
utils/claude_client.py
OpenAI AsyncClient · Function calling
UUID Storage
downloads/
Local .docx · 1hr TTL · Meta files
GenerateRequest → AgentState
↓
LangGraph ainvoke()
Layer 3 — AI Orchestration (JARVIS)
👤
Profile Agent
Seniority · Domains · Achievements
📋
JD Analyzer
Keywords · Signals · Culture
🎯
Relevance Engine
0–100 scores · Gap analysis
✍️
Optimizer
Bullets · Summary · Keywords
🏗️
Composer
Layout · Order · Page-fit
ResumeComposition JSON
↓
Layer 4 — Rendering & Scoring Engines
📄
DOCX Engine — engine/docx_engine.py
python-docx · Tab-stop alignment · Section borders · Typography · → .docx bytes
📊
ATS Engine — engine/ats_engine.py
Keyword matching · Power-verb check · Mandatory skills · → 0–100 score

Agent Pipeline — Full Execution Flow

LangGraph compiles a directed acyclic StateGraph. Each node is an async Python function. State flows forward; no agent can modify a predecessor's output.

🧑‍💼
User Request
Profile + JD + Options
GenerateRequest
1
👤
Profile Agent
Semantic analysis
semantic_profile
2
📋
JD Analyzer
Extracts requirements
jd_analysis
3
🎯
Relevance Engine
Scores & ranks
relevance_map + ats_score
↓
✅
Response
download_id · ats_score · keywords
GenerateResponse
📄
DOCX Engine
python-docx render
composition JSON
5
🏗️
Composer Agent
Layout + page-fit
optimized_content
4
✍️
Optimizer Agent
Rewrites bullets
Input Layer
Agent Nodes (GPT-4o-mini)
Rendering Engine
Output
Each node makes 1 async GPT-4o-mini call via OpenAI function calling

Each Agent — Responsibilities, Inputs, Outputs

Every agent is an async Python function that receives the full AgentState, calls GPT-4o-mini with a structured tool schema, and returns a partial state update dict.

👤
1

Profile Understanding Agent

profile_agent.py

Converts raw candidate data into a semantic profile graph that downstream agents use for intelligent matching. Identifies seniority level, primary domains, leadership signals, quantified achievements, and career trajectory.

Inputs from State

  • request.profile — CandidateProfile model
  • Experience entries (company, title, bullets)
  • Projects, skills, education, certs
  • Existing summary (if any)

Outputs → semantic_profile

  • candidate_level — Junior/Mid/Senior/Staff/Principal
  • years_of_experience — inferred float
  • primary_domains — top 3–5 areas
  • technical_depth — skill → score 1–10 map
  • leadership_indicators — evidence strings
  • quantified_achievements — all measurable impacts
  • top_differentiators — unique strengths
  • all_skills — comprehensive flat list
SYSTEM: "You are the Profile Understanding Agent within JARVIS. Determine seniority level (Junior/Mid/Senior/Staff/Principal/Executive). Identify primary technical domains and depth. Surface leadership indicators and management experience. Extract ALL quantifiable achievements (%, $, numbers, scale, impact). Assess career trajectory — upward / specialist deepening / lateral / pivot."
📋
2

JD Analyzer Agent

jd_agent.py

Parses the job description to extract hard requirements, ATS keywords, soft skills, and hidden hiring signals. Also infers company stage, engineering culture, and expected resume tone — enabling tone-matching and ATS optimization in downstream agents.

Inputs from State

  • request.job_description — raw JD text
  • request.target_role — optional user hint

Outputs → jd_analysis

  • role_title + company_name
  • mandatory_skills — must-have list
  • preferred_skills — nice-to-have list
  • ats_keywords — exact ATS scan phrases
  • domain_focus — primary tech/business domains
  • company_stage — Startup/Growth/Enterprise
  • expected_resume_tone — Quantitative/Technical/Executive
  • key_responsibilities — top 5–7 duties
  • years_experience_required
SYSTEM: "Extract hard skills (mandatory vs preferred). Extract ATS keywords — exact phrases including acronyms AND full forms. Infer company maturity stage (startup/growth/enterprise/public). Detect engineering culture signals (move-fast / process / research). Determine expected resume tone (quantitative / technical / executive / narrative)."
🎯
3

Matching & Relevance Engine

relevance_agent.py

The intelligence brain of the system. Computes semantic alignment between the candidate profile and JD, scores every experience entry and project 0–100, identifies keyword gaps, and creates an injection opportunity map — telling the optimizer exactly which bullets to enrich.

Inputs from State

  • semantic_profile — from Agent 1
  • jd_analysis — from Agent 2
  • request.profile — raw experience + projects
  • request.max_pages — 1 or 2

Outputs → relevance_map

  • overall_match_score — 0–100
  • experience_relevance[] — per-role score, include bool, priority rank, inject keywords
  • project_relevance[] — per-project score + include bool
  • keywords_matched — already present in profile
  • keywords_missing — critical gaps
  • skills_to_highlight — prioritized for JD
  • recommended_summary_angle — tone directive
SYSTEM: "Score overall alignment 0–100. Score each experience entry 0–100 for relevance to this JD. Mark compress=true for roles to show with fewer bullets (page budget). Map injection_opportunities per company: keywords to weave in naturally. Identify critical ATS keywords MISSING from candidate profile."
Key outputs that drive ATS score: keywords_matched and keywords_missing from this agent are stored directly on AgentState and surfaced in the API response alongside the final ATS score.
✍️
4

Content Optimization Agent

optimizer_agent.py

Transforms raw bullets into high-impact, recruiter-friendly statements using the CAR (Challenge → Action → Result) framework. Injects missing ATS keywords naturally. Generates a tailored professional summary. Only processes roles marked include=true by the relevance engine.

Inputs from State

  • relevance_map — which roles to optimize
  • jd_analysis.ats_keywords — inject targets
  • keywords_missing — gap keywords
  • jd_analysis.expected_resume_tone
  • relevance_map.recommended_summary_angle
  • Original bullets per role (raw text)

Outputs → optimized_content

  • professional_summary — 2–3 sentence tailored summary
  • optimized_experience[] — company + rewritten bullets (best first)
  • optimized_projects[] — rewritten project descriptions
BULLET RULES: 1. Start with strong action verb (Architected, Led, Engineered, Scaled...) 2. Include metrics: %, $, counts, latency, throughput, user scale 3. Weave in JD keywords naturally — no keyword stuffing 4. Under 150 chars preferred; 180 max 5. CAR framework: Context → Action → Result 6. Dual audience: ATS scanners (keywords) + human recruiters (clarity) 7. Best bullet FIRST per role EXAMPLE TRANSFORM: Raw: "Worked on APIs" Output: "Architected high-throughput REST APIs serving 50M+ requests/month with 99.99% uptime"
🏗️
5

Dynamic Resume Composer Agent

composer_agent.py

The final AI layer before rendering. Decides section ordering, bullet count per role, page-fit strategy, and produces the complete ResumeComposition JSON that the DOCX engine renders verbatim. No further AI processing after this point.

Inputs from State

  • optimized_content — rewritten bullets + summary
  • relevance_map — priority ordering
  • jd_analysis — company stage, tone
  • semantic_profile — candidate level
  • request.max_pages — 1 or 2
  • request.format — ats/swe/executive/startup…
  • Original dates, locations, education, certs

Outputs → composition

  • personal — all contact fields
  • summary — final summary string
  • experience[] — FINAL bullets with dates/locations
  • projects[] — selected + final bullets
  • skill_sections[] — ordered categories
  • education[] + certifications[]
  • section_order[] — render order
  • layout — font size, margin, density
PAGE-FIT RULES (1-page): - Current role: 4–6 bullets. Roles 2–3: 3–4 bullets. Older: 2 bullets. - Pre-2015 roles: omit entirely unless highly relevant. - Projects: max 2 bullets each, max 3 projects. - Certifications: top 3 most relevant only. - Section order: Experience first for SWE/ATS; Education first for new grad. SECTION ORDER HEURISTICS: SWE/ATS: experience → skills → projects → education Executive: experience → education → certifications → skills Research/PhD: education → publications → experience → skills New Grad: education → projects → experience → skills

AgentState — Shared Pipeline Context

A single TypedDict flows through all 5 nodes. Each node receives the full state and returns a partial update dict. Later agents can read all earlier outputs.

Field Type Set By Used By Description
requestGenerateRequestAPI routeAll agentsThe original user request — profile, JD, options
semantic_profiledict | NoneAgent 1Agents 3,4,5Seniority, domains, achievements, trajectory
jd_analysisdict | NoneAgent 2Agents 3,4,5Keywords, culture signals, tone expectations
relevance_mapdict | NoneAgent 3Agents 4,5Per-role scores, keyword gaps, injection map
optimized_contentdict | NoneAgent 4Agent 5Rewritten bullets, professional summary
compositiondict | NoneAgent 5DOCX + ATS enginesFinal render-ready composition JSON
ats_scorefloatAgent 3API responseOverall match score 0–100
keywords_matchedlist[str]Agent 3API responseJD keywords present in profile
keywords_missinglist[str]Agent 3Agent 4 + responseCritical keywords to inject
errorslist[str]Any agentLoggingNon-fatal errors; pipeline continues with fallback
current_stepstrEach agentObservabilityTracking label for current pipeline stage
Fault tolerance: Every agent wraps its GPT call in try/except. On failure, it returns a fallback state and appends to errors[]. The pipeline continues — a failed agent doesn't crash the whole generation. The final resume may be lower quality but is still produced.

LangGraph Graph Definition

workflow = StateGraph(AgentState) workflow.add_node("parse_profile", run_profile_agent) workflow.add_node("analyze_jd", run_jd_agent) workflow.add_node("compute_relevance", run_relevance_agent) workflow.add_node("optimize_content", run_optimizer_agent) workflow.add_node("compose_resume", run_composer_agent) workflow.set_entry_point("parse_profile") workflow.add_edge("parse_profile", "analyze_jd") workflow.add_edge("analyze_jd", "compute_relevance") workflow.add_edge("compute_relevance", "optimize_content") workflow.add_edge("optimize_content", "compose_resume") workflow.add_edge("compose_resume", END) jarvis = workflow.compile() # Execution (async) final_state = await jarvis.ainvoke(initial_state)

Dynamic DOCX Rendering Engine

Data-driven python-docx renderer. The composition JSON from Agent 5 controls every rendering decision — section order, content, typography, and spacing. No static templates.

Rendering Pipeline

1. Document Setup
Letter paper 8.5×11", 0.5" margins, Calibri font, default paragraph spacing cleared
2. Header Render
Name: 16pt bold centered. Contact: 9pt centered, pipe-separated fields
3. Section Loop
Iterates section_order[]. Dispatches each key to its dedicated renderer function
4. Section Header
11pt bold uppercase + XML bottom border (w:pBdr) for visual separator
5. Experience Render
Title (bold 11pt) + right-tab date using XML w:tabs at 7.5" position. Company italic. Bullets with • prefix + 0.2" indent
6. Serialize to Bytes
io.BytesIO buffer → doc.save() → raw bytes returned to API handler

Section Renderers

_render_header() name + contact
_render_summary() professional summary
_render_experience() tab-stop + bullets
_render_skills() category: items
_render_projects() name + techs + bullets
_render_education() degree + tab date
_render_certifications() bullet list

ATS Safety Rules

✅ No text boxes or shapes
✅ No content in headers/footers
✅ Standard section names (EXPERIENCE, SKILLS…)
✅ No columns for main body content
✅ Simple bullet character (•) not symbols
✅ Standard embedded fonts (Calibri)
✅ No background colors or images
✅ Minimal table usage (skills only)

Typography Specification

ElementFontSizeStyleAlignmentSpacing
Candidate NameCalibri16ptBold, ALL CAPSCenterafter: 2pt
Contact LineCalibri9ptNormalCenterafter: 6pt
Section HeaderCalibri11ptBold + bottom borderLeftbefore: 7pt, after: 2pt
Job TitleCalibri11ptBoldLeft (tab-right for date)before: 4pt
Company / LocationCalibri10ptItalicLeftafter: 2pt
Bullet PointsCalibri10ptNormalLeft, 0.2" indentafter: 1.5pt
Skills: CategoryCalibri10ptBoldLeftline spacing: 1.0
Skills: ItemsCalibri10ptNormalLeftinline after category
DegreeCalibri11ptBoldLeft (tab-right for year)before: 4pt

ATS Compliance Score — How It's Computed

Computed post-composition against the original JD analysis. Returns a 0–100 score and matched/missing keyword lists surfaced in the API response and frontend UI.

Scoring Formula

keyword_score = matched_ats / total_ats_keywords × 100 mandatory_score = matched_mandatory / total_mandatory × 100 structure_score = 100 – penalties (missing sections, weak verbs) final_score = ( keyword_score × 0.50 + mandatory_score × 0.30 + structure_score × 0.20 ) → clamped to [0, 100]

Score Interpretation

80–100 — Excellent
90%

Passes most ATS systems. Strong keyword coverage.

60–79 — Good
65%

Will pass most ATS. Add more JD keywords manually.

0–59 — Needs Work
40%

Profile significantly mismatched to JD. Review missing keywords.

Scoring Components Detail

ComponentWeightHow ComputedPenalty
ATS Keyword Coverage50%matched_ats_keywords / total_ats_keywords. Includes fuzzy matching (ml↔machine learning, k8s↔kubernetes)—
Mandatory Skills Coverage30%matched_mandatory_skills / total_mandatory_skills from JD analysis—
Structure Compliance20%Starts at 100, deductions appliedNo EXPERIENCE section: -20. No EDUCATION: -10. Each weak bullet (no action verb): -2, max -10

Power Verb Library (sampled)

ArchitectedBuiltDeployedDesignedEngineeredLedOptimizedScaledDeliveredDroveAutomatedIntegratedReducedIncreasedMentoredLaunchedTransformedRefactoredShipped +20 more in ats_engine.py

SmartExtract — Freeform Text → Structured Profile

Separate from the resume generation pipeline. Accepts any raw text and populates the form. Uses GPT-4o-mini with section-specific JSON schemas. Available on every form step.

Supported Sections

all — Full resume parse Most powerful
personal — Contact extraction name, email, phone, linkedin…
experience — Work history Array of roles + bullets
projects — Project descriptions name, techs, URLs, bullets
skills — Skill categorization Auto-grouped by category
education — Degrees + certs edu[] + certifications[]
certifications — Certs only name, issuer, date

Extraction Flow

User pastes raw text
Any format — resume copy-paste, LinkedIn export, freeform notes, bullet points
POST /api/extract
{ section, raw_text } → FastAPI validation → route handler
GPT-4o-mini function call
Section-specific JSON schema as function parameters. Model returns structured JSON matching the schema.
Return { section, data }
Frontend SmartExtract component receives and merges into form state
Smart merge strategy
Deduplicates by company+title (experience), project name, skill category. Never overwrites existing filled data — only appends new entries.
section="all" power move: Paste an entire resume on Step 1 → all 5 subsequent form steps are pre-populated in one API call. The user only needs to review and adjust, not type anything manually.

REST Endpoint Reference

All endpoints under http://localhost:8000. CORS enabled for localhost:3000.

POST
/api/generate
Full pipeline: Profile → JARVIS (5 agents) → DOCX render → ATS score. Returns download_id valid for 1 hour. Typical latency: 30–60s.
Body: GenerateRequest Returns: GenerateResponse Timeout: 180s 5 OpenAI calls
GET
/api/download/{download_id}
Stream the generated .docx file. ID from /api/generate response. Files expire after 1 hour (configurable via DOWNLOAD_TTL_SECONDS).
Returns: application/vnd.openxmlformats-officedocument.wordprocessingml.document
POST
/api/extract
Parse any freeform text and extract structured data for a given section. Use section="all" to parse an entire resume at once. Typical latency: 3–8s.
Body: { section, raw_text } Returns: { section, data } 1 OpenAI call Max: 30,000 chars
POST
/api/upload/resume
Upload an existing resume file (PDF, DOCX, TXT) and extract raw text. The text can then be passed to /api/extract for structured parsing.
Body: multipart/form-data Returns: { filename, text, char_count } Supported: PDF · DOCX · TXT
GET
/health
Health check. Returns app name, active model, and whether the API key is configured.
Returns: { status, app, model, api_key_set }

Core Pydantic Models

All models defined in backend/app/models/schema.py. All nested string fields default to "" for resilience against null values from GPT.

GenerateRequest

FieldTypeDefault
profileCandidateProfilerequired
job_descriptionstrrequired
formatResumeFormat"ats"
max_pagesint (1–2)1
target_rolestr | NoneNone

GenerateResponse

FieldTypeNotes
download_idstr (UUID)Use with /download/{id}
ats_scorefloat0–100
keywords_matchedlist[str]From relevance agent
keywords_missinglist[str]From relevance agent
sections_includedlist[str]From composer agent
generation_time_msintWall clock time

CandidateProfile

FieldType
name, emailstr (required)
phone, location, linkedin, github, websitestr | None
summarystr | None
experiencelist[ExperienceEntry]
projectslist[ProjectEntry]
skill_categorieslist[SkillCategory]
flat_skillslist[str]
educationlist[EducationEntry]
certificationslist[CertificationEntry]

ResumeFormat Enum

"ats" Maximum keyword density, ATS-safe sections
"swe" Technical depth, systems language, scale metrics
"executive" Business impact, P&L, team scale, board language
"startup" Ownership, velocity, cross-functional scope
"minimal" Clean, spare layout — achievements speak
"research" Academic tone, publications, methodology

Technology Stack — Phase 1 MVP

Chosen for developer velocity, production scalability, and AI ecosystem depth.

⚡
FastAPI 0.136
Async Python API · Auto OpenAPI docs
🔗
LangGraph 0.4.8
StateGraph orchestration · DAG pipeline
🤖
OpenAI GPT-4o-mini
All 5 agents + extraction · Function calling
📄
python-docx 1.1
DOCX rendering · XML manipulation
🛡️
Pydantic v2
Request validation · Schema enforcement
⚛️
Next.js 14
App Router · React 18 · SSR
🎨
Tailwind CSS 3
Utility-first · Dark theme · Responsive
📦
TypeScript 5
Full type safety · Strict mode

Phase Roadmap

✅ Phase 1 — Current
FastAPI + LangGraph + GPT-4o-mini
5-agent sequential pipeline
python-docx DOCX rendering
ATS keyword scoring
SmartExtract (7 section types)
Next.js 7-step wizard UI
🔜 Phase 2 — Intelligence
Vector embeddings (Qdrant/Pinecone)
Semantic similarity scoring
Parallel agent execution
Resume scoring simulation
LinkedIn/GitHub ingestion
Multi-format output (PDF)
🚀 Phase 3–4 — Enterprise
AI-generated layout code (sandboxed)
Celery + Redis job queue
Auth (Clerk/Auth0) + billing
Multi-tenancy + analytics
Recruiter integrations
Hiring probability prediction
TopNotchResume · Enterprise AI Resume Intelligence Platform · Phase 1 MVP
Backend: localhost:8000 Frontend: localhost:3000 Model: GPT-4o-mini Generated: 2026-05-27