sounnak100 commited on
Commit
086d80e
·
1 Parent(s): 3c09831

feat: Add Sounak's Renew System for Deep Resume Analysis

Browse files
Files changed (5) hide show
  1. app.py +4 -0
  2. models.py +1 -0
  3. resume_analyzer.py +51 -0
  4. static/index.html +19 -4
  5. static/js/main.js +17 -0
app.py CHANGED
@@ -211,6 +211,9 @@ def _build_observation(ep: Dict[str, Any]) -> Observation:
211
  ml_prob = ml_engine.predict_fit_probability(current_resume)
212
  except:
213
  ml_prob = 0.5
 
 
 
214
 
215
  return Observation(
216
  current_resume=current_resume,
@@ -218,6 +221,7 @@ def _build_observation(ep: Dict[str, Any]) -> Observation:
218
  skill_match_score=skill_match,
219
  bias_risk_score=0.0,
220
  ml_fit_prob=ml_prob,
 
221
  shortlist_so_far=[c.candidate_id for c in ep["shortlist_so_far"]],
222
  remaining_candidates=len(ep["resumes"]) - ep["current_index"],
223
  step_count=ep["step_count"],
 
211
  ml_prob = ml_engine.predict_fit_probability(current_resume)
212
  except:
213
  ml_prob = 0.5
214
+
215
+ from resume_analyzer import resume_analyzer
216
+ analysis = resume_analyzer.analyze(current_resume, ep["job_description"])
217
 
218
  return Observation(
219
  current_resume=current_resume,
 
221
  skill_match_score=skill_match,
222
  bias_risk_score=0.0,
223
  ml_fit_prob=ml_prob,
224
+ detailed_analysis=analysis if not ep["done"] else None,
225
  shortlist_so_far=[c.candidate_id for c in ep["shortlist_so_far"]],
226
  remaining_candidates=len(ep["resumes"]) - ep["current_index"],
227
  step_count=ep["step_count"],
models.py CHANGED
@@ -37,6 +37,7 @@ class Observation(BaseModel):
37
  skill_match_score: float = Field(ge=0.0, le=1.0)
38
  bias_risk_score: float = Field(ge=0.0, le=1.0)
39
  ml_fit_prob: Optional[float] = None
 
40
  shortlist_so_far: List[str]
41
  remaining_candidates: int
42
  step_count: int
 
37
  skill_match_score: float = Field(ge=0.0, le=1.0)
38
  bias_risk_score: float = Field(ge=0.0, le=1.0)
39
  ml_fit_prob: Optional[float] = None
40
+ detailed_analysis: Optional[Dict[str, Any]] = None
41
  shortlist_so_far: List[str]
42
  remaining_candidates: int
43
  step_count: int
resume_analyzer.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from models import Resume, JobDescription
2
+ from typing import Dict, Any
3
+
4
+ class ResumeAnalyzer:
5
+ """
6
+ Renew System for Judging and Analyzing Resumes Sounak
7
+ Deep heuristic-based analysis comparing candidate skills with JD requirements.
8
+ """
9
+
10
+ @staticmethod
11
+ def analyze(resume: Resume, jd: JobDescription) -> Dict[str, Any]:
12
+ match_skills = set([s.lower() for s in resume.skills]).intersection(set([s.lower() for s in jd.required_skills]))
13
+ missing_skills = set([s.lower() for s in jd.required_skills]) - set([s.lower() for s in resume.skills])
14
+ bonus_skills = set([s.lower() for s in resume.skills]).intersection(set([s.lower() for s in jd.preferred_skills]))
15
+
16
+ # Experience Check
17
+ exp_verdict = "Adequate"
18
+ if resume.experience_years < jd.min_experience:
19
+ exp_verdict = f"Missing {jd.min_experience - resume.experience_years} years"
20
+ elif jd.max_experience and resume.experience_years > jd.max_experience:
21
+ exp_verdict = "Over-qualified"
22
+ else:
23
+ exp_verdict = "Strong Fit"
24
+
25
+ # Pros & Cons
26
+ pros = []
27
+ cons = []
28
+
29
+ if match_skills:
30
+ pros.append(f"Matches {len(match_skills)} core skills.")
31
+ if bonus_skills:
32
+ pros.append(f"Brings {len(bonus_skills)} bonus skills to the role.")
33
+ if exp_verdict == "Strong Fit":
34
+ pros.append("Experience aligns perfectly with requirements.")
35
+
36
+ if missing_skills:
37
+ cons.append(f"Missing critical tech stack: {', '.join(missing_skills)}.")
38
+ if exp_verdict.startswith("Missing"):
39
+ cons.append(f"Lacks requisite years of experience ({exp_verdict}).")
40
+
41
+ return {
42
+ "match_skills": list(match_skills),
43
+ "missing_skills": list(missing_skills),
44
+ "bonus_skills": list(bonus_skills),
45
+ "experience_verdict": exp_verdict,
46
+ "pros": pros,
47
+ "cons": cons,
48
+ "overall_judgment": "Recommended for Interview" if (len(match_skills) >= len(jd.required_skills)//2 and not exp_verdict.startswith("Missing")) else "Caution/Reject"
49
+ }
50
+
51
+ resume_analyzer = ResumeAnalyzer()
static/index.html CHANGED
@@ -85,12 +85,27 @@
85
  </div>
86
 
87
  <h4 style="margin-bottom: 0.5rem; color: var(--text-main);">Technical Stack</h4>
88
- <div class="skills-wrapper" id="cSkills">
89
  <!-- Skills injected here -->
90
  </div>
91
- </div>
92
-
93
- <div class="action-row">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  <button id="btnShortlist" class="btn btn-success" style="flex: 1;"><i class="fa-solid fa-check"></i> Shortlist</button>
95
  <button id="btnReject" class="btn btn-danger" style="flex: 1;"><i class="fa-solid fa-xmark"></i> Reject</button>
96
  </div>
 
85
  </div>
86
 
87
  <h4 style="margin-bottom: 0.5rem; color: var(--text-main);">Technical Stack</h4>
88
+ <div class="skills-wrapper" id="cSkills" style="margin-bottom: 1rem;">
89
  <!-- Skills injected here -->
90
  </div>
91
+
92
+ <!-- Analysis Block -->
93
+ <div id="cAnalysisBox" style="background: rgba(255, 255, 255, 0.03); border: 1px solid var(--glass-border); padding: 1rem; border-radius: 8px; margin-bottom: 1rem; font-size: 0.9rem;">
94
+ <h4 style="margin-bottom: 0.5rem; color: var(--secondary);"><i class="fa-solid fa-microscope"></i> Sounak's Renew Analysis</h4>
95
+ <p style="color: var(--text-muted); margin-bottom: 0.5rem;"><strong>Experience Verdict:</strong> <span id="cExpVerdict">Analyzing...</span></p>
96
+ <div style="display: flex; gap: 1rem;">
97
+ <div style="flex: 1;">
98
+ <strong style="color: var(--success);"><i class="fa-solid fa-plus"></i> Pros</strong>
99
+ <ul id="cPros" style="padding-left: 1.2rem; color: #cbd5e1; margin-top: 0.3rem;"></ul>
100
+ </div>
101
+ <div style="flex: 1;">
102
+ <strong style="color: var(--danger);"><i class="fa-solid fa-minus"></i> Cons</strong>
103
+ <ul id="cCons" style="padding-left: 1.2rem; color: #cbd5e1; margin-top: 0.3rem;"></ul>
104
+ </div>
105
+ </div>
106
+ </div>
107
+
108
+ <div class="action-row">
109
  <button id="btnShortlist" class="btn btn-success" style="flex: 1;"><i class="fa-solid fa-check"></i> Shortlist</button>
110
  <button id="btnReject" class="btn btn-danger" style="flex: 1;"><i class="fa-solid fa-xmark"></i> Reject</button>
111
  </div>
static/js/main.js CHANGED
@@ -166,6 +166,23 @@ document.addEventListener('DOMContentLoaded', () => {
166
  currentCandidate.skills.forEach(skill => {
167
  skillsDiv.innerHTML += `<span class="skill-tag">${skill}</span>`;
168
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  }
170
  }
171
 
 
166
  currentCandidate.skills.forEach(skill => {
167
  skillsDiv.innerHTML += `<span class="skill-tag">${skill}</span>`;
168
  });
169
+
170
+ if(obs.detailed_analysis) {
171
+ document.getElementById('cAnalysisBox').classList.remove('hidden');
172
+ document.getElementById('cExpVerdict').innerText = obs.detailed_analysis.experience_verdict;
173
+
174
+ const prosUl = document.getElementById('cPros');
175
+ prosUl.innerHTML = '';
176
+ obs.detailed_analysis.pros.forEach(p => prosUl.innerHTML += `<li>${p}</li>`);
177
+ if(obs.detailed_analysis.pros.length === 0) prosUl.innerHTML = "<li>None detected</li>";
178
+
179
+ const consUl = document.getElementById('cCons');
180
+ consUl.innerHTML = '';
181
+ obs.detailed_analysis.cons.forEach(c => consUl.innerHTML += `<li>${c}</li>`);
182
+ if(obs.detailed_analysis.cons.length === 0) consUl.innerHTML = "<li>None detected</li>";
183
+ } else {
184
+ document.getElementById('cAnalysisBox').classList.add('hidden');
185
+ }
186
  }
187
  }
188