MozI1223 commited on
Commit
bed31b2
·
1 Parent(s): 916d804

fix(Matcher): Raw text role auto-detection and domain filter pills

Browse files
Files changed (1) hide show
  1. deployment/frontend/app.js +123 -5
deployment/frontend/app.js CHANGED
@@ -70,6 +70,76 @@ function ScoreRadialGauge({ score = 0, fitTier = "Good Fit" }) {
70
  );
71
  }
72
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  function App() {
74
  const [currentPage, setCurrentPage] = useState("search"); // 'search' (Page 1) | 'matcher' (Page 2)
75
  const [sampleData, setSampleData] = useState({ personas: [], jobs: [] });
@@ -646,18 +716,32 @@ function App() {
646
  style={{ width: '100%', height: '140px', padding: '10px', border: '1px solid #cbd5e1', borderRadius: '6px', fontFamily: 'monospace', fontSize: '0.82rem', outline: 'none' }}
647
  value={resumeText}
648
  onChange={(e) => setResumeText(e.target.value)}
649
- placeholder="Paste raw CV text..."
650
  />
651
  <button
652
  className="search-submit-btn"
653
- style={{ marginTop: '8px', padding: '6px 14px', fontSize: '0.82rem' }}
654
  onClick={() => {
655
- setUploadedWordCount(resumeText.split(/\s+/).filter(Boolean).length);
 
 
 
 
 
 
 
 
 
 
 
 
656
  setShowTextPaste(false);
657
- fetchJobsAndMatch(resumeText, searchQuery, searchLocation);
 
 
658
  }}
659
  >
660
- Re-Analyze Matched Jobs
661
  </button>
662
  </div>
663
  )}
@@ -692,6 +776,40 @@ function App() {
692
  </div>
693
  </div>
694
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
695
  {loading ? (
696
  <div className="jobs-list-container">
697
  {[1, 2, 3].map(i => (
 
70
  );
71
  }
72
 
73
+ // 🎯 Client-Side Candidate Role & Name Extraction
74
+ const KNOWN_ROLES_LIST = [
75
+ 'Full Stack Developer', 'Full Stack Engineer',
76
+ 'Frontend Developer', 'Frontend Engineer', 'UI Developer',
77
+ 'Backend Developer', 'Backend Engineer',
78
+ 'Machine Learning Engineer', 'AI Engineer', 'NLP Engineer', 'Deep Learning Engineer',
79
+ 'Data Scientist', 'Data Analyst', 'Data Engineer',
80
+ 'DevOps Engineer', 'Cloud Engineer', 'Site Reliability Engineer',
81
+ 'Software Engineer', 'Software Developer',
82
+ 'QA Engineer', 'Mobile Developer', 'Android Developer', 'iOS Developer',
83
+ 'HR Specialist', 'HR Manager', 'Talent Acquisition Specialist',
84
+ 'Product Manager', 'Project Manager', 'UI/UX Designer'
85
+ ];
86
+
87
+ function detectRoleFromClientText(cleanText) {
88
+ if (!cleanText) return "Software Engineer";
89
+ const lines = cleanText.split('\n').map(l => l.trim()).filter(Boolean);
90
+
91
+ // 1. Check top 8 lines
92
+ for (let i = 0; i < Math.min(lines.length, 8); i++) {
93
+ for (let role of KNOWN_ROLES_LIST) {
94
+ const re = new RegExp('\\b' + role.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b', 'i');
95
+ if (re.test(lines[i])) {
96
+ return role;
97
+ }
98
+ }
99
+ }
100
+
101
+ // 2. Keyword heuristic fallback
102
+ const t = cleanText.toLowerCase();
103
+ if (t.includes('devops') || t.includes('kubernetes') || t.includes('docker') || t.includes('terraform') || t.includes('ci/cd') || t.includes('jenkins') || t.includes('ansible')) {
104
+ return 'DevOps Engineer';
105
+ }
106
+ if (t.includes('full stack') || (t.includes('react') && (t.includes('node') || t.includes('express') || t.includes('django')))) {
107
+ return 'Full Stack Developer';
108
+ }
109
+ if (t.includes('frontend') || t.includes('react') || t.includes('vue') || t.includes('angular') || t.includes('css3') || t.includes('html5')) {
110
+ return 'Frontend Developer';
111
+ }
112
+ if (t.includes('backend') || t.includes('fastapi') || t.includes('django') || t.includes('spring boot') || t.includes('express.js')) {
113
+ return 'Backend Developer';
114
+ }
115
+ if (t.includes('machine learning') || t.includes('pytorch') || t.includes('deep learning') || t.includes('tensorflow') || t.includes('computer vision') || t.includes('nlp')) {
116
+ return 'Machine Learning Engineer';
117
+ }
118
+ if (t.includes('data scientist')) {
119
+ return 'Data Scientist';
120
+ }
121
+ if (t.includes('data analyst') || t.includes('tableau') || t.includes('power bi')) {
122
+ return 'Data Analyst';
123
+ }
124
+ if (t.includes('hr') || t.includes('human resource') || t.includes('recruitment') || t.includes('talent acquisition')) {
125
+ return 'HR Specialist';
126
+ }
127
+ return 'Software Engineer';
128
+ }
129
+
130
+ function detectNameFromClientText(cleanText) {
131
+ if (!cleanText) return "Candidate";
132
+ const lines = cleanText.split('\n').map(l => l.trim()).filter(Boolean);
133
+ for (let line of lines.slice(0, 3)) {
134
+ if (!/resume|curriculum|vitae|summary|experience|education|contact|phone|email|profile/i.test(line)) {
135
+ if (line.split(/\s+/).length <= 4 && line.length <= 40) {
136
+ return line.replace(/\|/g, '').trim();
137
+ }
138
+ }
139
+ }
140
+ return "Candidate";
141
+ }
142
+
143
  function App() {
144
  const [currentPage, setCurrentPage] = useState("search"); // 'search' (Page 1) | 'matcher' (Page 2)
145
  const [sampleData, setSampleData] = useState({ personas: [], jobs: [] });
 
716
  style={{ width: '100%', height: '140px', padding: '10px', border: '1px solid #cbd5e1', borderRadius: '6px', fontFamily: 'monospace', fontSize: '0.82rem', outline: 'none' }}
717
  value={resumeText}
718
  onChange={(e) => setResumeText(e.target.value)}
719
+ placeholder="Paste raw CV text (e.g. DevOps Engineer with Kubernetes, Docker, CI/CD, Terraform, AWS)..."
720
  />
721
  <button
722
  className="search-submit-btn"
723
+ style={{ marginTop: '8px', padding: '6px 16px', fontSize: '0.82rem' }}
724
  onClick={() => {
725
+ const wordCount = resumeText.split(/\s+/).filter(Boolean).length;
726
+ setUploadedWordCount(wordCount);
727
+
728
+ // Detect candidate role and name from raw pasted text
729
+ const detectedRole = detectRoleFromClientText(resumeText);
730
+ const detectedName = detectNameFromClientText(resumeText);
731
+
732
+ if (detectedName && detectedName !== "Candidate") {
733
+ setUserName(detectedName);
734
+ }
735
+ setUserRole(detectedRole);
736
+ setSearchQuery(detectedRole);
737
+ setUploadedFileName("Pasted Resume Document");
738
  setShowTextPaste(false);
739
+
740
+ // Automatically search and match jobs for detected role
741
+ fetchJobsAndMatch(resumeText, detectedRole, searchLocation);
742
  }}
743
  >
744
+ 🔍 Auto-Detect Role & Re-Analyze Matches
745
  </button>
746
  </div>
747
  )}
 
776
  </div>
777
  </div>
778
 
779
+ {/* Domain Filter Pills on Matcher Page */}
780
+ <div style={{ display: 'flex', gap: '5px', flexWrap: 'wrap', marginBottom: '1rem' }}>
781
+ {[
782
+ { label: `🎯 ${userRole.split(' ')[0]} ${userRole.split(' ')[1] || ''}`, query: userRole },
783
+ { label: "☁️ DevOps", query: "DevOps Engineer" },
784
+ { label: "⚡ Full Stack", query: "Full Stack Developer" },
785
+ { label: "⚛️ Frontend", query: "Frontend Developer" },
786
+ { label: "🐍 Backend", query: "Backend Developer" },
787
+ { label: "🤖 AI & ML", query: "AI Engineer" },
788
+ { label: "📊 Data Analyst", query: "Data Analyst" }
789
+ ].map((tab) => (
790
+ <button
791
+ key={tab.label}
792
+ style={{
793
+ padding: '4px 10px',
794
+ fontSize: '0.75rem',
795
+ fontWeight: '700',
796
+ background: searchQuery.toLowerCase().includes(tab.query.toLowerCase().split(' ')[0]) ? '#0284c7' : '#ffffff',
797
+ color: searchQuery.toLowerCase().includes(tab.query.toLowerCase().split(' ')[0]) ? '#ffffff' : '#475569',
798
+ border: '1px solid #cbd5e1',
799
+ borderRadius: '999px',
800
+ cursor: 'pointer',
801
+ transition: 'all 0.15s ease'
802
+ }}
803
+ onClick={() => {
804
+ setSearchQuery(tab.query);
805
+ fetchJobsAndMatch(resumeText, tab.query, searchLocation);
806
+ }}
807
+ >
808
+ {tab.label}
809
+ </button>
810
+ ))}
811
+ </div>
812
+
813
  {loading ? (
814
  <div className="jobs-list-container">
815
  {[1, 2, 3].map(i => (