File size: 24,542 Bytes
d632eb4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
"""
app.py β€” AI Resume ATS Analyzer
================================
 
Main Streamlit application entry point.
 
Features:
  - Upload PDF or DOCX resume
  - Automatic text extraction
  - Named entity recognition (name, orgs, locations)
  - Skills extraction (technical + soft)
  - Section detection (Skills, Experience, Projects, Education, Summary)
  - Resume base score (0–100) with breakdown
  - Job description semantic similarity via Sentence-BERT
  - ATS Score = 0.6 Γ— base_score + 0.4 Γ— job_match
  - Classification: Good / Average / Poor
  - Actionable suggestions
  - AI-powered resume rewrite via FLAN-T5
  - Manual resume editing + re-evaluation
 
Run locally:
    streamlit run app.py
 
Deploy on Hugging Face Spaces:
    1. Create a new Space (SDK: Streamlit)
    2. Upload all project files
    3. The Space will install requirements.txt and launch automatically
"""
 
import streamlit as st
import pandas as pd
import time
 
# Local utility modules
from utils.parser import parse_resume
from utils.nlp_utils import (
    extract_entities,
    detect_sections,
    extract_skills,
    get_missing_sections,
    classify_resume,
    generate_suggestions,
)
from utils.scorer import compute_base_score, compute_ats_score
from utils.similarity import compute_similarity, keyword_overlap_score
from utils.generator import generate_improved_resume, generate_professional_summary
 
# ---------------------------------------------------------------------------
# Page configuration
# ---------------------------------------------------------------------------
st.set_page_config(
    page_title="AI Resume ATS Analyzer",
    page_icon="πŸ“„",
    layout="wide",
    initial_sidebar_state="expanded",
)
 
# ---------------------------------------------------------------------------
# Custom CSS β€” clean, professional look
# ---------------------------------------------------------------------------
st.markdown(
    """
    <style>
        /* Main header */
        .main-header {
            font-size: 2.4rem;
            font-weight: 800;
            background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            margin-bottom: 0.2rem;
        }
        /* Score cards */
        .score-card {
            background: #f8f9fa;
            border-radius: 12px;
            padding: 1.2rem;
            text-align: center;
            border: 1px solid #e9ecef;
            box-shadow: 0 2px 6px rgba(0,0,0,0.06);
        }
        .score-number {
            font-size: 2.2rem;
            font-weight: 700;
            margin: 0;
        }
        .score-label {
            font-size: 0.85rem;
            color: #6c757d;
            text-transform: uppercase;
            letter-spacing: 0.05em;
        }
        /* Section headers */
        .section-header {
            font-size: 1.1rem;
            font-weight: 600;
            color: #343a40;
            border-bottom: 2px solid #667eea;
            padding-bottom: 0.3rem;
            margin-top: 1.5rem;
            margin-bottom: 0.8rem;
        }
        /* Skill badges */
        .skill-badge {
            display: inline-block;
            background: #e8f4fd;
            color: #1a6a9a;
            border-radius: 20px;
            padding: 0.2rem 0.7rem;
            margin: 0.15rem;
            font-size: 0.82rem;
            font-weight: 500;
        }
        /* Suggestion items */
        .suggestion-item {
            background: #fffbf0;
            border-left: 4px solid #ffc107;
            padding: 0.6rem 0.9rem;
            border-radius: 0 6px 6px 0;
            margin-bottom: 0.5rem;
            font-size: 0.9rem;
        }
        /* Good/Average/Poor banner */
        .classification-good {
            background: #d4edda; color: #155724;
            padding: 0.5rem 1rem; border-radius: 8px;
            font-size: 1.1rem; font-weight: 600;
            text-align: center;
        }
        .classification-average {
            background: #fff3cd; color: #856404;
            padding: 0.5rem 1rem; border-radius: 8px;
            font-size: 1.1rem; font-weight: 600;
            text-align: center;
        }
        .classification-poor {
            background: #f8d7da; color: #721c24;
            padding: 0.5rem 1rem; border-radius: 8px;
            font-size: 1.1rem; font-weight: 600;
            text-align: center;
        }
        /* Info boxes */
        .stAlert { border-radius: 8px; }
    </style>
    """,
    unsafe_allow_html=True,
)
 
# ---------------------------------------------------------------------------
# Sidebar β€” About & Instructions
# ---------------------------------------------------------------------------
with st.sidebar:
    st.image(
        "https://img.icons8.com/color/96/resume.png",
        width=70,
    )
    st.markdown("## πŸ“„ AI Resume Analyzer")
    st.markdown(
        """
        **How to use:**
        1. Upload your resume (PDF or DOCX)
        2. Paste the job description
        3. Click **Analyze Resume**
        4. Review your scores & suggestions
        5. Use **Generate Improved Resume** for AI rewrite
        6. Edit & **Re-evaluate** anytime
 
        ---
        **Scoring Formula:**
        - Resume Score: up to 100 pts
          - Skills (20) + Experience (30)
          - Projects (20) + Education (10)
          - Length (10) + Diversity (10)
        - ATS Score = 0.6 Γ— Resume Score
                    + 0.4 Γ— Job Match %
 
        ---
        **Powered by:**
        - πŸ€— FLAN-T5 (text generation)
        - πŸ”€ Sentence-BERT (similarity)
        - 🧠 spaCy (NER)
        - πŸ“„ PyMuPDF + python-docx
        """
    )
    st.markdown("---")
    st.caption("Built with ❀️ using open-source AI")
 
 
# ---------------------------------------------------------------------------
# Main UI β€” Header
# ---------------------------------------------------------------------------
st.markdown('<p class="main-header">🎯 AI Resume ATS Analyzer</p>', unsafe_allow_html=True)
st.markdown(
    "Upload your resume and a job description to get an **AI-powered ATS score**, "
    "skill analysis, and personalized improvement suggestions.",
    unsafe_allow_html=True,
)
st.markdown("---")
 
# ---------------------------------------------------------------------------
# Input Section β€” two columns
# ---------------------------------------------------------------------------
col_upload, col_jd = st.columns([1, 1], gap="large")
 
with col_upload:
    st.markdown("### πŸ“€ Upload Resume")
    uploaded_file = st.file_uploader(
        "Supported formats: PDF, DOCX",
        type=["pdf", "docx"],
        help="Upload your resume in PDF or Word format",
    )
 
with col_jd:
    st.markdown("### πŸ“‹ Job Description")
    job_description = st.text_area(
        "Paste the job description here",
        height=200,
        placeholder=(
            "e.g., We are looking for a Python Developer with experience in "
            "FastAPI, PostgreSQL, and Docker. You will work on backend systems, "
            "REST APIs, and cloud deployments on AWS..."
        ),
        help="The job description is used for semantic matching and tailored suggestions.",
    )
 
# ---------------------------------------------------------------------------
# Session state β€” persist results across reruns
# ---------------------------------------------------------------------------
if "results" not in st.session_state:
    st.session_state.results = None
if "resume_text_editable" not in st.session_state:
    st.session_state.resume_text_editable = ""
if "improved_resume" not in st.session_state:
    st.session_state.improved_resume = ""
 
 
# ---------------------------------------------------------------------------
# Helper: run the full analysis pipeline
# ---------------------------------------------------------------------------
def run_analysis(resume_text: str, job_desc: str) -> dict:
    """
    Execute the complete resume analysis pipeline.
 
    Args:
        resume_text: extracted or edited resume text
        job_desc   : job description text
 
    Returns:
        Dictionary with all analysis results.
    """
    # 1. NLP Analysis
    with st.spinner("πŸ” Running NLP analysis..."):
        entities = extract_entities(resume_text)
        sections = detect_sections(resume_text)
        skills = extract_skills(resume_text)
        missing_sections = get_missing_sections(sections)
 
    # 2. Scoring
    with st.spinner("πŸ“Š Computing resume score..."):
        score_result = compute_base_score(resume_text, sections, skills)
 
    # 3. Job Description Similarity
    with st.spinner("🎯 Matching against job description..."):
        if job_desc.strip():
            job_match = compute_similarity(resume_text, job_desc)
            kw_overlap = keyword_overlap_score(resume_text, job_desc)
        else:
            job_match = 0.0
            kw_overlap = {"overlap_pct": 0.0, "matched": [], "missing": []}
 
    # 4. ATS Score + Classification
    ats_score = compute_ats_score(score_result["total"], job_match)
    classification = classify_resume(ats_score)
 
    # 5. Suggestions
    suggestions = generate_suggestions(
        sections, skills, score_result["total"], job_match
    )
 
    return {
        "entities": entities,
        "sections": sections,
        "skills": skills,
        "missing_sections": missing_sections,
        "base_score": score_result["total"],
        "score_breakdown": score_result["breakdown"],
        "job_match": job_match,
        "kw_overlap": kw_overlap,
        "ats_score": ats_score,
        "classification": classification,
        "suggestions": suggestions,
    }
 
 
# ---------------------------------------------------------------------------
# Helper: render analysis results
# ---------------------------------------------------------------------------
def render_results(results: dict, resume_text: str):
    """Render the full analysis results in the Streamlit UI."""
 
    st.markdown("---")
    st.markdown("## πŸ“Š Analysis Results")
 
    # ── Score Dashboard ────────────────────────────────────────────────────
    c1, c2, c3, c4 = st.columns(4)
 
    def _color_score(score, thresholds=(70, 45)):
        high, mid = thresholds
        if score >= high:
            return "#28a745"
        elif score >= mid:
            return "#ffc107"
        return "#dc3545"
 
    with c1:
        color = _color_score(results["base_score"])
        st.markdown(
            f"""<div class="score-card">
                <p class="score-number" style="color:{color}">{results["base_score"]}</p>
                <p class="score-label">Resume Score</p>
            </div>""",
            unsafe_allow_html=True,
        )
    with c2:
        color = _color_score(results["job_match"])
        st.markdown(
            f"""<div class="score-card">
                <p class="score-number" style="color:{color}">{results["job_match"]}%</p>
                <p class="score-label">Job Match</p>
            </div>""",
            unsafe_allow_html=True,
        )
    with c3:
        color = _color_score(results["ats_score"])
        st.markdown(
            f"""<div class="score-card">
                <p class="score-number" style="color:{color}">{results["ats_score"]}%</p>
                <p class="score-label">ATS Score</p>
            </div>""",
            unsafe_allow_html=True,
        )
    with c4:
        label = results["classification"]["label"]
        cls_color = results["classification"]["color"]
        css_class = f"classification-{cls_color}"
        st.markdown(
            f"""<div class="score-card">
                <div class="{css_class}" style="margin-top:0.5rem">{label}</div>
                <p class="score-label" style="margin-top:0.5rem">Classification</p>
            </div>""",
            unsafe_allow_html=True,
        )
 
    st.markdown("<br>", unsafe_allow_html=True)
 
    # ── Score Breakdown Bar Chart ──────────────────────────────────────────
    with st.expander("πŸ“ˆ Score Breakdown", expanded=True):
        breakdown = results["score_breakdown"]
        max_vals = {
            "Skills": 20,
            "Experience": 30,
            "Projects": 20,
            "Education": 10,
            "Length": 10,
            "Diversity": 10,
        }
        df = pd.DataFrame(
            {
                "Category": list(breakdown.keys()),
                "Your Score": list(breakdown.values()),
                "Max Score": [max_vals.get(k, 10) for k in breakdown.keys()],
            }
        )
        st.bar_chart(df.set_index("Category")[["Your Score", "Max Score"]])
 
    # ── Two column layout for details ─────────────────────────────────────
    left_col, right_col = st.columns([1, 1], gap="large")
 
    with left_col:
        # ── Detected Information ──────────────────────────────────────────
        st.markdown('<p class="section-header">πŸ” Detected Information</p>', unsafe_allow_html=True)
 
        entities = results["entities"]
        if entities.get("name"):
            st.markdown(f"**πŸ‘€ Candidate Name:** {entities['name']}")
        if entities.get("organizations"):
            st.markdown(f"**🏒 Organizations:** {', '.join(entities['organizations'][:5])}")
        if entities.get("locations"):
            st.markdown(f"**πŸ“ Locations:** {', '.join(entities['locations'][:5])}")
 
        # ── Sections Detected ─────────────────────────────────────────────
        st.markdown('<p class="section-header">πŸ“‘ Sections Detected</p>', unsafe_allow_html=True)
 
        section_display = {
            "skills": "πŸ› οΈ Skills",
            "education": "πŸŽ“ Education",
            "experience": "πŸ’Ό Experience",
            "projects": "πŸš€ Projects",
            "summary": "πŸ“ Summary",
            "certifications": "πŸ† Certifications",
        }
        cols = st.columns(2)
        for i, (key, label) in enumerate(section_display.items()):
            present = results["sections"].get(key, False)
            icon = "βœ…" if present else "❌"
            cols[i % 2].markdown(f"{icon} {label}")
 
        # ── Missing Sections ──────────────────────────────────────────────
        if results["missing_sections"]:
            st.markdown('<p class="section-header">⚠️ Missing Sections</p>', unsafe_allow_html=True)
            for ms in results["missing_sections"]:
                st.warning(f"Missing: **{ms}**")
 
    with right_col:
        # ── Technical Skills ──────────────────────────────────────────────
        st.markdown('<p class="section-header">βš™οΈ Technical Skills Found</p>', unsafe_allow_html=True)
        tech_skills = results["skills"].get("technical", [])
        if tech_skills:
            badges = "".join(
                f'<span class="skill-badge">{s.title()}</span>'
                for s in tech_skills
            )
            st.markdown(badges, unsafe_allow_html=True)
        else:
            st.info("No technical skills detected. Add a Skills section.")
 
        # ── Soft Skills ───────────────────────────────────────────────────
        st.markdown('<p class="section-header">🀝 Soft Skills Found</p>', unsafe_allow_html=True)
        soft_skills = results["skills"].get("soft", [])
        if soft_skills:
            badges = "".join(
                f'<span class="skill-badge" style="background:#f0fff0;color:#276749">{s.title()}</span>'
                for s in soft_skills
            )
            st.markdown(badges, unsafe_allow_html=True)
        else:
            st.info("No soft skills detected.")
 
        # ── Keyword Overlap (if JD provided) ─────────────────────────────
        kw = results["kw_overlap"]
        if kw["matched"] or kw["missing"]:
            st.markdown('<p class="section-header">πŸ”‘ Keyword Overlap with JD</p>', unsafe_allow_html=True)
            k1, k2 = st.columns(2)
            with k1:
                st.metric("Overlap", f"{kw['overlap_pct']}%")
                if kw["matched"]:
                    st.markdown("**βœ… Matched:**")
                    st.markdown(", ".join(kw["matched"][:10]))
            with k2:
                if kw["missing"]:
                    st.markdown("**❌ Missing from JD:**")
                    st.markdown(", ".join(kw["missing"][:10]))
 
    # ── Suggestions ────────────────────────────────────────────────────────
    st.markdown("---")
    st.markdown("## πŸ’‘ Improvement Suggestions")
    for suggestion in results["suggestions"]:
        st.markdown(
            f'<div class="suggestion-item">{suggestion}</div>',
            unsafe_allow_html=True,
        )
 
    # ── Resume Text Preview ───────────────────────────────────────────────
    st.markdown("---")
    st.markdown("## πŸ“„ Resume Preview")
    word_count = len(resume_text.split())
    st.caption(f"Word count: {word_count}")
    with st.expander("View extracted resume text", expanded=False):
        st.text_area(
            "Resume Text (read-only preview)",
            value=resume_text,
            height=300,
            disabled=True,
            key="preview_text",
        )
 
 
# ---------------------------------------------------------------------------
# BUTTON 1 β€” Analyze Resume
# ---------------------------------------------------------------------------
st.markdown("<br>", unsafe_allow_html=True)
analyze_col, _, _ = st.columns([1, 1, 1])
with analyze_col:
    analyze_clicked = st.button(
        "πŸ” Analyze Resume",
        type="primary",
        use_container_width=True,
        disabled=(uploaded_file is None),
    )
 
if analyze_clicked:
    if uploaded_file is None:
        st.error("Please upload a resume file first.")
    else:
        # Parse the uploaded file
        with st.spinner("πŸ“‚ Extracting text from resume..."):
            parse_result = parse_resume(uploaded_file)
 
        if parse_result["error"]:
            st.error(f"❌ {parse_result['error']}")
            st.stop()
 
        resume_text = parse_result["text"]
 
        if len(resume_text.split()) < 20:
            st.warning(
                "⚠️ Very little text was extracted. "
                "The file might be image-based. Try a text-based PDF or DOCX."
            )
 
        # Store editable text in session state
        st.session_state.resume_text_editable = resume_text
 
        # Run full pipeline
        results = run_analysis(resume_text, job_description)
        st.session_state.results = results
 
        st.success("βœ… Analysis complete!")
 
# Render results if available
if st.session_state.results:
    render_results(st.session_state.results, st.session_state.resume_text_editable)
 
 
# ---------------------------------------------------------------------------
# BUTTON 2 β€” Generate Improved Resume (AI Rewrite)
# ---------------------------------------------------------------------------
if st.session_state.results:
    st.markdown("---")
    st.markdown("## πŸ€– AI Resume Improvement")
    st.markdown(
        "Click below to generate an **ATS-optimized rewrite** of your resume "
        "using FLAN-T5. The AI will use professional tone, bullet points, "
        "and strong action verbs."
    )
 
    gen_col, _ = st.columns([1, 2])
    with gen_col:
        gen_clicked = st.button(
            "✨ Generate Improved Resume",
            use_container_width=True,
        )
 
    if gen_clicked:
        with st.spinner("πŸ€– AI is rewriting your resume... (this may take 30–60 seconds on CPU)"):
            improved = generate_improved_resume(
                st.session_state.resume_text_editable,
                job_description,
                st.session_state.results.get("missing_sections", []),
            )
            st.session_state.improved_resume = improved
 
    if st.session_state.improved_resume:
        st.markdown("### βœ… AI-Generated Improved Resume")
        st.text_area(
            "You can copy this improved resume text:",
            value=st.session_state.improved_resume,
            height=400,
            key="improved_output",
        )
        st.info(
            "πŸ’‘ **Tip:** Copy this improved text, paste it into the editor below, "
            "and click **Re-evaluate** to see your new ATS score!"
        )
 
    # ── Professional Summary Generator ─────────────────────────────────────
    with st.expander("πŸ–ŠοΈ Generate Professional Summary"):
        summ_col, _ = st.columns([1, 2])
        with summ_col:
            target_role = st.text_input(
                "Target job title (optional)",
                placeholder="e.g., Backend Engineer",
            )
            summ_btn = st.button("Generate Summary", key="summ_btn")
 
        if summ_btn:
            with st.spinner("Generating summary..."):
                summary = generate_professional_summary(
                    name=st.session_state.results["entities"].get("name", ""),
                    skills=st.session_state.results["skills"].get("technical", []),
                    experience_present=st.session_state.results["sections"].get(
                        "experience", False
                    ),
                    job_title=target_role,
                )
            st.markdown("**Generated Summary:**")
            st.markdown(f"> {summary}")
 
 
# ---------------------------------------------------------------------------
# BUTTON 3 β€” Resume Editor + Re-evaluation
# ---------------------------------------------------------------------------
if st.session_state.results:
    st.markdown("---")
    st.markdown("## ✏️ Edit & Re-evaluate Resume")
    st.markdown(
        "Edit your resume text below (paste the AI-improved version or make "
        "manual changes) and click **Re-evaluate** to get a fresh ATS score."
    )
 
    edited_text = st.text_area(
        "Edit your resume here:",
        value=st.session_state.resume_text_editable,
        height=400,
        key="editor_area",
        help="Make any changes to improve your resume, then click Re-evaluate.",
    )
 
    re_eval_col, _ = st.columns([1, 2])
    with re_eval_col:
        re_eval_clicked = st.button(
            "πŸ”„ Re-evaluate Edited Resume",
            type="secondary",
            use_container_width=True,
        )
 
    if re_eval_clicked:
        if not edited_text.strip():
            st.error("The editor is empty. Please paste your resume text.")
        else:
            # Update session state
            st.session_state.resume_text_editable = edited_text
 
            with st.spinner("πŸ“Š Re-analyzing your edited resume..."):
                new_results = run_analysis(edited_text, job_description)
                st.session_state.results = new_results
 
            st.success("βœ… Re-evaluation complete!")
            st.markdown("### πŸ“Š Updated Results")
            render_results(new_results, edited_text)
 
 
# ---------------------------------------------------------------------------
# Footer
# ---------------------------------------------------------------------------
st.markdown("---")
st.markdown(
    """
    <div style="text-align:center; color:#6c757d; font-size:0.85rem; padding: 1rem 0;">
        🎯 AI Resume ATS Analyzer β€” Built with open-source AI tools<br>
        Powered by FLAN-T5 Β· Sentence-BERT Β· spaCy Β· PyMuPDF
    </div>
    """,
    unsafe_allow_html=True,
)