From 755cec389d015a9f61f47bd044b43927c4f89252 Mon Sep 17 00:00:00 2001 From: Alv24-hub Date: Sat, 11 Jul 2026 13:33:21 +0530 Subject: [PATCH] Complete removal of Resume Analyser project along with all of its dependency --- .../AI-Resume-Analyzer/AI-Resume-Analyzer.py | 185 --------- utilities/AI-Resume-Analyzer/README.MD | 50 --- utilities/AI-Resume-Analyzer/requirements.txt | 1 - web-app/assets/banners/reverse-hangman.webp | Bin 18182 -> 0 bytes web-app/css/styles.css | 228 ----------- web-app/generate_banners.py | 13 - web-app/index.html | 8 - web-app/js/main.js | 3 +- web-app/js/modules/modal.js | 2 +- web-app/js/projects.js | 10 - web-app/js/projects/resume-analyzer.js | 355 ------------------ web-app/projects_registry.json | 15 - web-app/utilities.html | 22 -- 13 files changed, 2 insertions(+), 890 deletions(-) delete mode 100644 utilities/AI-Resume-Analyzer/AI-Resume-Analyzer.py delete mode 100644 utilities/AI-Resume-Analyzer/README.MD delete mode 100644 utilities/AI-Resume-Analyzer/requirements.txt delete mode 100644 web-app/assets/banners/reverse-hangman.webp delete mode 100644 web-app/js/projects/resume-analyzer.js diff --git a/utilities/AI-Resume-Analyzer/AI-Resume-Analyzer.py b/utilities/AI-Resume-Analyzer/AI-Resume-Analyzer.py deleted file mode 100644 index 145a7b8b..00000000 --- a/utilities/AI-Resume-Analyzer/AI-Resume-Analyzer.py +++ /dev/null @@ -1,185 +0,0 @@ -import re -import nltk -from nltk.corpus import stopwords -from nltk.tokenize import word_tokenize -from nltk.util import ngrams - - -def _ensure_nltk_resources() -> None: - """Download NLTK resources only if not already present.""" - resources = { - 'punkt': 'tokenizers/punkt', - 'punkt_tab': 'tokenizers/punkt_tab', - 'stopwords': 'corpora/stopwords', - } - for name, path in resources.items(): - try: - nltk.data.find(path) - except LookupError: - nltk.download(name, quiet=True) - - -def analyze_resume(resume_text: str) -> dict: - _ensure_nltk_resources() - - resume = resume_text.lower() - - # Pre-process to protect C++, C#, and .NET from NLTK tokenization - PROTECTED = {"c++": "cpplang", "c#": "csharplang", ".net": "dotnetlang"} - for raw, placeholder in PROTECTED.items(): - resume = resume.replace(raw, placeholder) - - # NLP processing - words = word_tokenize(resume) - - # Restore protected tokens - words = [PROTECTED.get(w, w) for w in words] - - stop_words = set(stopwords.words('english')) - KEEP_AS_IS = {"c++", "c#", ".net"} - - clean_words = [ - w for w in words - if (w.isalnum() or w in KEEP_AS_IS) and w not in stop_words - ] - - # Phrase tokens - bigrams = [' '.join(bg) for bg in ngrams(clean_words, 2)] - - # Skill aliases - skill_aliases = { - # Languages - "py": "python", - "js": "javascript", - "ts": "typescript", - "cpp": "c++", - # Databases - "postgres": "postgresql", - "mongo": "mongodb", - # AI / ML - "ml": "machine learning", - "dl": "deep learning", - "cv": "computer vision", - # Frameworks - "reactjs": "react", - "react.js": "react", - "nodejs": "node.js", - "expressjs": "express", - # Cloud / DevOps - "aws": "amazon web services", - "gcp": "google cloud platform", - # Tools - "gitlab": "git", - "github": "git", - } - - normalized_words = [ - skill_aliases.get(word, word) - for word in clean_words - ] - - normalized_bigrams = [ - skill_aliases.get(bg, bg) - for bg in bigrams - ] - - # Skills - skills = [ - "python", "java", "c++", "c#", ".net", "django", "sql", - "machine learning", "html", "css", - "javascript", "communication", "teamwork" - ] - - found_skills = [] - for skill in skills: - if skill in normalized_words or skill in normalized_bigrams: - found_skills.append(skill) - - # Resume section parsing - education_pattern = r"(education|academic|qualification)" - experience_pattern = r"(experience|internship|work experience)" - project_pattern = r"(projects|project)" - - edu_found = bool(re.search(education_pattern, resume)) - exp_found = bool(re.search(experience_pattern, resume)) - project_found = bool(re.search(project_pattern, resume)) - - # Improved score system - score = 0 - - # Skill score - score += min(len(found_skills) * 8, 40) - - # Section score - if edu_found: - score += 20 - if exp_found: - score += 25 - if project_found: - score += 15 - - # Resume quality bonus - if len(found_skills) >= 5: - score += 10 - - if score > 100: - score = 100 - - missing_skills = [s for s in skills if s not in found_skills] - - return { - "found_skills": found_skills, - "score": score, - "edu_found": edu_found, - "exp_found": exp_found, - "project_found": project_found, - "missing_skills": missing_skills, - "all_skills": skills, - } - - -def main() -> None: - print("=== πŸ€– AI Resume Analyzer (Advanced NLP Version) ===") - - resume = input("\nPaste your resume text:\n") - if not resume.strip(): - print("❌ Error: Resume cannot be empty.") - return - - result = analyze_resume(resume) - - # OUTPUT - print("\nπŸ“Š === Analysis Result ===") - - print("\nβœ… Skills detected:") - for s in result["found_skills"]: - print("-", s) - - score = result["score"] - print(f"\n🎯 ATS Score: {score}/100") - - # Strength - if score >= 80: - print("βœ… Resume Strength: Excellent") - elif score >= 50: - print("πŸ‘ Resume Strength: Good") - else: - print("⚠️ Needs Improvement") - - # Suggestions - print("\nπŸ’‘ Recommendations:") - for m in result["missing_skills"][:5]: - print("-", m) - - # Section check - print("\nπŸ“„ Resume Sections:") - print("- Education:", "βœ… Found" if result["edu_found"] else "❌ Missing") - print("- Experience:", "βœ… Found" if result["exp_found"] else "❌ Missing") - print("- Projects:", "βœ… Found" if result["project_found"] else "❌ Missing") - - # Final - print("\nπŸš€ Analysis Completed") - - -if __name__ == "__main__": - main() diff --git a/utilities/AI-Resume-Analyzer/README.MD b/utilities/AI-Resume-Analyzer/README.MD deleted file mode 100644 index cca36ba7..00000000 --- a/utilities/AI-Resume-Analyzer/README.MD +++ /dev/null @@ -1,50 +0,0 @@ -# πŸ€– AI Resume Analyzer (NLP Powered) - -An intelligent resume analysis tool built using Python and NLTK that extracts skills, evaluates resumes, and provides ATS scoring with improvement suggestions. - ---- - -## πŸš€ Features - -- 🧠 NLP-based text processing using NLTK -- 🎯 Skill detection from resume text -- πŸ“Š ATS (Applicant Tracking System) score generation -- πŸ“„ Resume strength evaluation -- πŸ’‘ Smart improvement suggestions -- πŸ“Œ Education & experience detection - ---- - -## πŸ› οΈ Tech Stack - -- Python 🐍 -- NLTK (Natural Language Toolkit) - ---- - -## πŸ“‚ Project Structure -AI-Resume-Analyzer/ -β”œβ”€β”€ main.py -β”œβ”€β”€ requirements.txt -β”œβ”€β”€ README.md - - ---- - -## βš™οΈ Installation & Setup - -### 1️⃣ Install dependencies - -```bash -pip install -r requirements.txt - -run project- python main.py - ----NLTK Setup (FIRST TIME ONLY)--- - -Run this once in Python: - -import nltk -nltk.download('punkt_tab') -nltk.download('punkt') -nltk.download('stopwords') \ No newline at end of file diff --git a/utilities/AI-Resume-Analyzer/requirements.txt b/utilities/AI-Resume-Analyzer/requirements.txt deleted file mode 100644 index 6fa2de44..00000000 --- a/utilities/AI-Resume-Analyzer/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -nltk \ No newline at end of file diff --git a/web-app/assets/banners/reverse-hangman.webp b/web-app/assets/banners/reverse-hangman.webp deleted file mode 100644 index 841df72d480a1743128d8e967edf5bdc16348ec9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18182 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 diff --git a/web-app/css/styles.css b/web-app/css/styles.css index 86a0be1d..2d41d484 100644 --- a/web-app/css/styles.css +++ b/web-app/css/styles.css @@ -876,234 +876,6 @@ body.sidebar-active .sidebar-dock { display: none !important; } -/* ── AI Resume Analyzer ────────────────────────────────────── */ -.resume-analyzer-shell { - max-width: 960px; - margin: 0 auto; - padding: 0.25rem 0 0.75rem; -} - -.resume-analyzer-hero { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 1rem; - margin-bottom: 1rem; -} - -.resume-analyzer-copy h2 { - margin: 0.35rem 0 0.45rem; - font-size: clamp(1.8rem, 4vw, 2.6rem); - letter-spacing: -0.04em; -} - -.resume-analyzer-copy p { - margin: 0; - color: var(--text-secondary); - line-height: 1.6; -} - -.resume-analyzer-badge, -.resume-analyzer-pill { - display: inline-flex; - align-items: center; - gap: 0.5rem; - padding: 0.55rem 0.9rem; - border-radius: 999px; - border: 1px solid var(--border); - background: var(--bg-glass); - color: var(--text-secondary); - font-family: var(--font-mono); - font-size: 0.72rem; - letter-spacing: 0.08em; - text-transform: uppercase; -} - -.resume-analyzer-pill { - white-space: nowrap; -} - -.resume-analyzer-grid { - display: grid; - gap: 1rem; -} - -.resume-panel { - background: var(--bg-elevated); - border: 1px solid var(--border); - border-radius: 22px; - padding: 1.1rem; - box-shadow: var(--shadow-sm); -} - -.resume-upload-box { - display: grid; - place-items: center; - gap: 0.75rem; - padding: 1.25rem; - border-radius: 18px; - border: 1.5px dashed var(--accent); - background: linear-gradient(180deg, - rgba(34, 197, 94, 0.06), - rgba(6, 182, 212, 0.04)); - text-align: center; -} - -.resume-upload-icon { - font-size: 2.4rem; - color: var(--accent); -} - -.resume-file-btn { - display: inline-flex; - align-items: center; - gap: 0.5rem; - padding: 0.8rem 1.15rem; - border-radius: 12px; - background: var(--accent); - color: #fff; - font-weight: 700; - cursor: pointer; - transition: - transform var(--duration-fast) var(--ease-out-expo), - box-shadow var(--duration-fast) ease; -} - -.resume-file-btn:hover { - transform: translateY(-1px); - box-shadow: 0 0 0 4px var(--accent-soft); -} - -.resume-upload-box p { - margin: 0; - color: var(--text-secondary); -} - -.resume-analyze-btn { - width: 100%; - margin-top: 1rem; - padding: 0.95rem 1rem; - border: none; - border-radius: 14px; - background: linear-gradient(135deg, var(--accent), #06b6d4); - color: #fff; - font-family: var(--font-sans); - font-weight: 700; - cursor: pointer; - box-shadow: 0 14px 30px rgba(34, 197, 94, 0.18); - transition: - transform var(--duration-fast) var(--ease-out-expo), - box-shadow var(--duration-fast) ease; -} - -.resume-analyze-btn:hover { - transform: translateY(-2px); - box-shadow: 0 18px 34px rgba(34, 197, 94, 0.24); -} - -.resume-score-panel h3, -.resume-mini-card h3 { - margin: 0 0 0.9rem; - font-size: 1.05rem; -} - -.resume-score-panel { - display: grid; - place-items: center; - text-align: center; -} - -.resume-progress-circle { - width: 136px; - height: 136px; - border-radius: 50%; - border: 10px solid var(--accent); - display: grid; - place-items: center; - margin: 0.2rem auto 1rem; - background: radial-gradient(circle at 30% 30%, - rgba(255, 255, 255, 0.08), - transparent 55%); -} - -.resume-progress-circle span { - font-size: 2rem; - font-weight: 800; -} - -.resume-extra-info { - color: var(--text-secondary); - font-size: 0.92rem; -} - -.resume-bottom-panel { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 1rem; -} - -.resume-mini-card { - background: var(--bg-glass); - border: 1px solid var(--border); - border-radius: 18px; - padding: 1rem; -} - -.resume-keyword-item { - margin-bottom: 0.95rem; -} - -.resume-keyword-item span { - display: inline-block; - margin-bottom: 0.45rem; - color: var(--text); - font-weight: 600; -} - -.resume-bar { - height: 10px; - border-radius: 999px; - overflow: hidden; - background: var(--bg-elevated); -} - -.resume-bar>div { - height: 100%; - border-radius: inherit; - background: linear-gradient(90deg, var(--accent), #06b6d4); -} - -.resume-suggestion { - display: flex; - gap: 0.7rem; - align-items: flex-start; - margin-top: 0.85rem; - color: var(--text-secondary); -} - -.resume-suggestion i { - color: var(--accent); - margin-top: 0.2rem; -} - -@media (max-width: 720px) { - .resume-analyzer-hero { - flex-direction: column; - } - - .resume-analyzer-pill { - white-space: normal; - } - - .resume-bottom-panel { - grid-template-columns: 1fr; - } - - .resume-panel { - padding: 1rem; - } -} - /* ── Hero Badge Row (Glassified Kicker & Status Button) ──────── */ .hero-kicker, .hero-status { diff --git a/web-app/generate_banners.py b/web-app/generate_banners.py index bc6b0aad..b86721fa 100644 --- a/web-app/generate_banners.py +++ b/web-app/generate_banners.py @@ -468,18 +468,6 @@ def draw_o(ox, oy): x = 220 + i * 80 y = 175 v_draw.ellipse([x, y, x + 65, y + 65], fill=col, outline=(255,255,255,100), width=2) - elif "resume" in n_lower or "analyzer" in n_lower: - # Resume analyzer dashboard - v_draw.rounded_rectangle([250, 100, 550, 350], radius=24, fill=(255,255,255,12), outline=color_accent, width=3) - v_draw.rounded_rectangle([285, 135, 515, 315], radius=18, fill=(255,255,255,8), outline=(255,255,255,50), width=2) - v_draw.polygon([(332, 135), (515, 135), (515, 190)], fill=(255,255,255,20), outline=color_accent) - v_draw.ellipse([280, 145, 390, 255], outline=color_accent, width=8) - v_draw.text((335, 200), "82%", fill=color_accent, anchor="mm") - for i, w in enumerate([90, 75, 65]): - y = 275 + i * 18 - v_draw.rounded_rectangle([410, y, 410 + w, y + 10], radius=4, fill=color_accent) - v_draw.text((430, 165), "AI RESUME", fill=color_accent, anchor="lm") - v_draw.text((430, 188), "ANALYZER", fill=(255,255,255), anchor="lm") elif "caesar" in n_lower: # Cipher wheel and shifting letters cx, cy = 400, 225 @@ -720,7 +708,6 @@ def draw_o(ox, oy): ("Number Converter", "utilities", "number-converter.webp"), ("Typing Speed Tester", "utilities", "typing-speed-tester.webp"), ("Color Palette Suggestor", "utilities", "color-palette.webp"), - ("AI Resume Analyzer", "utilities", "resume-analyzer.webp"), ("Caesar Cipher", "utilities", "caesar-cipher.webp"), ("Unit Converter", "utilities", "unit-converter.webp"), ("Budget Tracker", "utilities", "budget-tracker.webp"), diff --git a/web-app/index.html b/web-app/index.html index be026e0d..ea257c14 100644 --- a/web-app/index.html +++ b/web-app/index.html @@ -508,7 +508,6 @@

Stay Updated

- @@ -865,13 +864,6 @@

Stay Updated

desc: "Generate color palettes", tags: "utility,design", }, - { - project: "resume-analyzer", - title: "Resume Analyzer", - category: "utilities", - desc: "Analyze and improve resumes", - tags: "utility,career", - }, { project: "calculator", title: "Scientific Graphing Calculator", diff --git a/web-app/js/main.js b/web-app/js/main.js index 80e8ba7d..15e0f7b4 100644 --- a/web-app/js/main.js +++ b/web-app/js/main.js @@ -1354,8 +1354,7 @@ document.addEventListener("DOMContentLoaded", function () { // Info button already exists, skip injection console.log('ℹ️ Info button already exists for', name); } else { - // Look for any heading element - var firstHeading = projectContent.querySelector("h2, h3, .resume-analyzer-copy h2, .pet-title"); + var firstHeading = projectContent.querySelector("h2, h3, .pet-title"); // Special case for Tic Tac Toe - look for the heading inside project-content if (!firstHeading) { diff --git a/web-app/js/modules/modal.js b/web-app/js/modules/modal.js index 54e2e0c9..323583b1 100644 --- a/web-app/js/modules/modal.js +++ b/web-app/js/modules/modal.js @@ -143,7 +143,7 @@ export function openProjectSafe(name, trigger) { const projectContent = modalBody.querySelector(".project-content"); if (projectContent) { let firstHeading = projectContent.querySelector( - "h2, h3, .resume-analyzer-copy h2, .pet-title" + "h2, h3, .pet-title" ); if (!firstHeading) { firstHeading = projectContent.querySelector( diff --git a/web-app/js/projects.js b/web-app/js/projects.js index fe6ece08..0bf144bf 100644 --- a/web-app/js/projects.js +++ b/web-app/js/projects.js @@ -25,7 +25,6 @@ function getProjectHTML(projectName) { 'matrix-calculator': () => getMatrixCalculatorHTML(), 'sudoku-game': getSudokuGameHTML(), 'unit-converter': getUnitConverterHTML(), - 'resume-analyzer': getResumeAnalyzerHTML(), 'reverse-hangman': () => getReverseHangmanHTML(), 'budget-tracker': getBudgetTrackerHTML(), 'snake-game': getSnakeGameHTML(), @@ -465,15 +464,6 @@ const projectInstructions = { "Copy the secure password" ] }, - "resume-analyzer": { - title: "πŸ“„ How to Use AI Resume Analyzer", - steps: [ - "Upload your resume (PDF, DOC, or TXT)", - "Click 'Analyze Resume'", - "View your ATS score and keyword matches", - "Check suggestions to improve your resume" - ] - }, "typing-speed-tester": { title: "⌨️ How to Use Typing Speed Tester", steps: [ diff --git a/web-app/js/projects/resume-analyzer.js b/web-app/js/projects/resume-analyzer.js deleted file mode 100644 index a70632eb..00000000 --- a/web-app/js/projects/resume-analyzer.js +++ /dev/null @@ -1,355 +0,0 @@ -// ============================================ -// AI RESUME ANALYZER -// Logic ported from utilities/AI-Resume-Analyzer/AI-Resume-Analyzer.py -// No API calls β€” pure client-side text analysis. -// ============================================ - -function getResumeAnalyzerHTML() { - return ` -
-
-
- GSSoC Utility -

AI Resume Analyzer

-

Upload a resume and get a quick ATS-style snapshot with keyword, structure, and formatting feedback.

-
-
No backend required
-
- -
-
-
- - -

Drag & drop or click to choose a resume (.pdf, .doc, .docx, .txt)

-
- - -
- - - - -
-
- `; -} - -function initResumeAnalyzer() { - const analyzeBtn = document.getElementById('analyzeBtn'); - const resumeInput = document.getElementById('resumeInput'); - const fileStatus = document.getElementById('resumeFileStatus'); - const uploadIcon = document.getElementById('resumeUploadIcon'); - const ats = document.getElementById('ats'); - const bottomSection = document.getElementById('bottomSection'); - - if (!analyzeBtn || !resumeInput) return; - - function loadPdfJs() { - return new Promise((resolve, reject) => { - if (window.pdfjsLib) { resolve(window.pdfjsLib); return; } - const script = document.createElement('script'); - script.src = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js'; - script.onload = () => { - window.pdfjsLib.GlobalWorkerOptions.workerSrc = - 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js'; - resolve(window.pdfjsLib); - }; - script.onerror = () => reject(new Error('Failed to load PDF.js')); - document.head.appendChild(script); - }); - } - - async function readPdfAsText(file) { - const pdfjsLib = await loadPdfJs(); - const arrayBuffer = await file.arrayBuffer(); - const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise; - const pageTexts = []; - for (let i = 1; i <= pdf.numPages; i++) { - const page = await pdf.getPage(i); - const content = await page.getTextContent(); - pageTexts.push(content.items.map(item => item.str).join(' ')); - } - return pageTexts.join('\n'); - } - - const SKILLS = [ - "python", "java", "c++", "c#", ".net", "django", "sql", - "machine learning", "html", "css", "javascript", - "communication", "teamwork" - ]; - - const ALIASES = { - "py": "python", - "js": "javascript", - "ts": "typescript", - "cpp": "c++", - "postgres": "postgresql", - "mongo": "mongodb", - "ml": "machine learning", - "dl": "deep learning", - "cv": "computer vision", - "reactjs": "react", - "react.js": "react", - "nodejs": "node.js", - "expressjs": "express", - "aws": "amazon web services", - "gcp": "google cloud platform", - "gitlab": "git", - "github": "git", - }; - - const EDU_RE = /education|academic|qualification/i; - const EXP_RE = /experience|internship|work experience/i; - const PROJ_RE = /projects?/i; - - resumeInput.addEventListener('change', () => { - const file = resumeInput.files[0]; - if (!file) return; - - fileStatus.textContent = `βœ… File selected: ${file.name}`; - fileStatus.style.color = 'var(--accent)'; - uploadIcon.style.color = 'var(--accent)'; - uploadIcon.className = 'fa-solid fa-circle-check resume-upload-icon'; - }); - - const dropZone = document.getElementById('resumeDropZone'); - if (dropZone) { - dropZone.addEventListener('dragover', e => { - e.preventDefault(); - dropZone.style.borderColor = 'var(--accent)'; - }); - dropZone.addEventListener('dragleave', () => { - dropZone.style.borderColor = ''; - }); - dropZone.addEventListener('drop', e => { - e.preventDefault(); - dropZone.style.borderColor = ''; - const file = e.dataTransfer.files[0]; - if (file) { - const dt = new DataTransfer(); - dt.items.add(file); - resumeInput.files = dt.files; - fileStatus.textContent = `βœ… File selected: ${file.name}`; - fileStatus.style.color = 'var(--accent)'; - uploadIcon.className = 'fa-solid fa-circle-check resume-upload-icon'; - } - }); - } - - analyzeBtn.addEventListener('click', async () => { - if (!resumeInput.files.length) { - fileStatus.textContent = '⚠️ Please upload a resume file first!'; - fileStatus.style.color = 'var(--danger-color, #ef4444)'; - return; - } - - const file = resumeInput.files[0]; - const ext = file.name.split('.').pop().toLowerCase(); - - analyzeBtn.textContent = 'Analyzing… ⏳'; - analyzeBtn.disabled = true; - - try { - let text = ''; - - if (ext === 'pdf') { - fileStatus.textContent = 'πŸ“„ Extracting text from PDF…'; - fileStatus.style.color = 'var(--accent)'; - text = await readPdfAsText(file); - //console.log(`[ResumeAnalyzer] PDF extracted (${text.length} chars):`, text); - if (!text || text.trim().length < 30) { - console.warn('[ResumeAnalyzer] Text too short β€” likely a scanned/image PDF.'); - fileStatus.textContent = - `⚠️ "${file.name}" appears to be a scanned/image-only PDF. ` + - `For best results, export your resume as a text-based PDF or .txt file.`; - fileStatus.style.color = 'var(--warning-color, #f59e0b)'; - return; - } - } else if (ext === 'doc' || ext === 'docx') { - text = await readFileAsText(file); - //console.log(`[ResumeAnalyzer] DOCX raw text (${text.length} chars):`, text); - if (!text || text.trim().length < 30) { - console.warn('[ResumeAnalyzer] DOCX text too short β€” binary file.'); - fileStatus.textContent = - `⚠️ "${file.name}" could not be read as plain text in the browser. ` + - `For best results, export your resume as a .pdf or .txt file and re-upload.`; - fileStatus.style.color = 'var(--warning-color, #f59e0b)'; - return; - } - } else { - text = await readFileAsText(file); - //console.log(`[ResumeAnalyzer] TXT extracted (${text.length} chars):`, text); - } - - //console.log('[ResumeAnalyzer] Running analysis on text:', text); - runAnalysis(text); - } catch (err) { - fileStatus.textContent = `❌ Could not read file: ${err.message}`; - fileStatus.style.color = 'var(--danger-color, #ef4444)'; - } finally { - analyzeBtn.textContent = 'Analyze Resume πŸš€'; - analyzeBtn.disabled = false; - } - }); - - function readFileAsText(file) { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = e => resolve(e.target.result); - reader.onerror = () => reject(new Error('Read error')); - reader.readAsText(file); - }); - } - - function runAnalysis(rawText) { - const text = rawText.toLowerCase(); - - const tokenRe = /c\+\+|c#|\.net|[a-z0-9]+/gi; - const tokens = (text.match(tokenRe) || []).map(t => t.toLowerCase()); - - const normalised = tokens.map(t => ALIASES[t] ?? t); - - const bigrams = []; - for (let i = 0; i < normalised.length - 1; i++) { - bigrams.push(normalised[i] + ' ' + normalised[i + 1]); - } - - const foundSkills = SKILLS.filter( - skill => normalised.includes(skill) || bigrams.includes(skill) - ); - - const eduFound = EDU_RE.test(text); - const expFound = EXP_RE.test(text); - const projFound = PROJ_RE.test(text); - - let score = 0; - score += Math.min(foundSkills.length * 8, 40); // up to 40 - if (eduFound) score += 20; - if (expFound) score += 25; - if (projFound) score += 15; - if (foundSkills.length >= 5) score += 10; // quality bonus - score = Math.min(score, 100); - - const formattingScore = Math.round( - ((eduFound ? 1 : 0) + (expFound ? 1 : 0) + (projFound ? 1 : 0)) / 3 * 100 - ); - const keywordScore = Math.min( - Math.round((foundSkills.length / SKILLS.length) * 100), 100 - ); - - let strength; - if (score >= 80) strength = 'βœ… Resume Strength: Excellent'; - else if (score >= 50) strength = 'πŸ‘ Resume Strength: Good'; - else strength = '⚠️ Resume Strength: Needs Improvement'; - - const missingSkills = SKILLS.filter(s => !foundSkills.includes(s)); - - renderResults({ - score, formattingScore, keywordScore, strength, - foundSkills, missingSkills, - eduFound, expFound, projFound - }); - } - - function renderResults({ score, formattingScore, keywordScore, strength, - foundSkills, missingSkills, - eduFound, expFound, projFound }) { - ats.classList.remove('hidden'); - bottomSection.classList.remove('hidden'); - - document.getElementById('atsScoreDisplay').textContent = score + '%'; - document.getElementById('atsFormattingScore').textContent = `βœ” Formatting Score: ${formattingScore}%`; - document.getElementById('atsKeywordScore').textContent = `βœ” Keyword Match: ${keywordScore}%`; - document.getElementById('atsStrength').textContent = strength; - - const kwList = document.getElementById('resumeKeywordsList'); - kwList.textContent = ''; - - const displaySkills = [ - ...foundSkills.map(s => ({ name: s, pct: Math.min(70 + Math.random() * 30 | 0, 100), found: true })), - ...missingSkills.slice(0, Math.max(0, 5 - foundSkills.length)) - .map(s => ({ name: s, pct: 0, found: false })) - ].slice(0, 6); - - if (displaySkills.length === 0) { - kwList.innerHTML = '

No matching keywords detected. Try a .txt version of your resume.

'; - } else { - displaySkills.forEach(({ name, pct, found }) => { - kwList.innerHTML += ` -
- ${capitalise(name)}${found ? '' : ' ❌'} -
-
-
-
`; - }); - } - - const suggestionsEl = document.getElementById('resumeSuggestions'); - suggestionsEl.innerHTML = ''; - - const suggestions = buildSuggestions({ foundSkills, missingSkills, eduFound, expFound, projFound }); - suggestions.forEach(s => { - suggestionsEl.innerHTML += ` -
- -

${s.text}

-
`; - }); - - ats.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); - } - - function buildSuggestions({ foundSkills, missingSkills, eduFound, expFound, projFound }) { - const list = []; - - if (eduFound) list.push({ ok: true, text: 'Education section detected' }); - else list.push({ ok: false, text: 'Add an Education section' }); - - if (expFound) list.push({ ok: true, text: 'Experience section detected' }); - else list.push({ ok: false, text: 'Add a Work Experience / Internship section' }); - - if (projFound) list.push({ ok: true, text: 'Projects section detected' }); - else list.push({ ok: false, text: 'Add a Projects section' }); - - if (foundSkills.length >= 5) - list.push({ ok: true, text: `Strong skill set detected (${foundSkills.length} skills)` }); - else - list.push({ ok: false, text: 'Add more technical skills' }); - - if (missingSkills.length > 0) - list.push({ ok: false, text: `Consider adding: ${missingSkills.slice(0, 3).map(capitalise).join(', ')}` }); - - list.push({ ok: false, text: 'Use strong action verbs (e.g. "Developed", "Led", "Built")' }); - - return list.slice(0, 6); - } - - function capitalise(str) { - return str.charAt(0).toUpperCase() + str.slice(1); - } -} \ No newline at end of file diff --git a/web-app/projects_registry.json b/web-app/projects_registry.json index 4c88a0c9..5535901e 100644 --- a/web-app/projects_registry.json +++ b/web-app/projects_registry.json @@ -487,21 +487,6 @@ ], "path": "math/Quadratic-Solver/Quadratic-Solver.py" }, - { - "name": "AI Resume Analyzer", - "emoji": "πŸ“„", - "category": "utilities", - "difficulty": "advanced", - "description": "Analyze resumes using NLTK to extract key skills and suggestions.", - "keywords": [ - "resume", - "analyzer", - "nltk", - "skills", - "parser" - ], - "path": "utilities/AI-Resume-Analyzer/AI-Resume-Analyzer.py" - }, { "name": "Budget Tracker", "emoji": "πŸ’°", diff --git a/web-app/utilities.html b/web-app/utilities.html index 2ed4577e..12f05017 100644 --- a/web-app/utilities.html +++ b/web-app/utilities.html @@ -370,27 +370,6 @@

Budget Tracker

Track income and expenses, view categories, and persist data locally

-
- AI Resume Analyzer -
- -
- -

AI Resume Analyzer

-

- Score resumes with ATS-style keyword and formatting insights! -

-
-
TSP Visualizer -