omgy commited on
Commit
828d9c3
·
verified ·
1 Parent(s): 9b4c864

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +254 -1
main.py CHANGED
@@ -39,7 +39,14 @@ async def root():
39
  "endpoints": {
40
  "upload": "POST /upload - Upload Excel file with candidate data",
41
  "predict": "GET /predict - Get ranked candidates",
42
- "docs": "GET /docs - API documentation"
 
 
 
 
 
 
 
43
  }
44
  }
45
 
@@ -63,6 +70,252 @@ def rank_candidate(name, surname, resume, cover_letter):
63
  return round(float(scores[2]) * 100, 2) # Use positive sentiment score
64
 
65
  # Add simple web interface
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  @app.get("/upload-form", response_class=HTMLResponse)
67
  async def upload_form():
68
  return """
 
39
  "endpoints": {
40
  "upload": "POST /upload - Upload Excel file with candidate data",
41
  "predict": "GET /predict - Get ranked candidates",
42
+ "docs": "GET /docs - API documentation",
43
+ "upload_form": "GET /upload-form - Simple upload form",
44
+ "test_interface": "GET /test-interface - Complete testing interface"
45
+ },
46
+ "links": {
47
+ "api_docs": "/docs",
48
+ "upload_form": "/upload-form",
49
+ "test_interface": "/test-interface"
50
  }
51
  }
52
 
 
70
  return round(float(scores[2]) * 100, 2) # Use positive sentiment score
71
 
72
  # Add simple web interface
73
+ @app.get("/test-interface", response_class=HTMLResponse)
74
+ async def test_interface():
75
+ return """
76
+ <!DOCTYPE html>
77
+ <html lang="en">
78
+ <head>
79
+ <meta charset="UTF-8">
80
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
81
+ <title>API Testing Interface</title>
82
+ <style>
83
+ body { font-family: Arial, sans-serif; max-width: 1000px; margin: 20px auto; padding: 20px; background: #f5f5f5; }
84
+ .container { background: white; padding: 30px; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); }
85
+ h1 { color: #333; text-align: center; margin-bottom: 30px; }
86
+ .section { background: #f8f9ff; padding: 20px; margin: 20px 0; border-radius: 8px; border-left: 4px solid #007bff; }
87
+ .section h2 { color: #007bff; margin-bottom: 15px; }
88
+ .form-group { margin: 15px 0; }
89
+ label { display: block; margin-bottom: 5px; font-weight: bold; }
90
+ input[type="file"] { width: 100%; padding: 10px; border: 2px solid #ddd; border-radius: 5px; }
91
+ button { background: #007bff; color: white; padding: 12px 20px; border: none; border-radius: 5px; cursor: pointer; margin: 5px; }
92
+ button:hover { background: #0056b3; }
93
+ button:disabled { background: #ccc; cursor: not-allowed; }
94
+ .btn-secondary { background: #28a745; }
95
+ .btn-danger { background: #dc3545; }
96
+ .response { background: #1e1e1e; color: #00ff00; padding: 15px; border-radius: 5px; font-family: monospace; white-space: pre-wrap; max-height: 300px; overflow-y: auto; margin: 15px 0; }
97
+ .loading { display: none; text-align: center; padding: 20px; }
98
+ .spinner { border: 4px solid #f3f3f3; border-top: 4px solid #007bff; border-radius: 50%; width: 30px; height: 30px; animation: spin 1s linear infinite; margin: 0 auto; }
99
+ @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
100
+ .candidates-table { width: 100%; border-collapse: collapse; margin: 20px 0; background: white; border-radius: 5px; overflow: hidden; }
101
+ .candidates-table th, .candidates-table td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
102
+ .candidates-table th { background: #007bff; color: white; }
103
+ .candidates-table tr:hover { background: #f8f9ff; }
104
+ .priority-high { background: #28a745; color: white; padding: 4px 8px; border-radius: 15px; }
105
+ .priority-medium { background: #ffc107; color: black; padding: 4px 8px; border-radius: 15px; }
106
+ .priority-low { background: #dc3545; color: white; padding: 4px 8px; border-radius: 15px; }
107
+ </style>
108
+ </head>
109
+ <body>
110
+ <div class="container">
111
+ <h1>🧪 Candidate Ranking API Tester</h1>
112
+
113
+ <div class="section">
114
+ <h2>📋 API Endpoints</h2>
115
+ <button onclick="testRoot()">Test Root (/)</button>
116
+ <button onclick="getCandidates()">Get Candidates (/predict)</button>
117
+ <button onclick="window.open('/docs', '_blank')" class="btn-secondary">API Docs</button>
118
+ </div>
119
+
120
+ <div class="section">
121
+ <h2>📁 File Upload Test</h2>
122
+ <form id="uploadForm" enctype="multipart/form-data">
123
+ <div class="form-group">
124
+ <label for="file">Select Excel File:</label>
125
+ <input type="file" id="file" name="file" accept=".xlsx,.xls" required>
126
+ <small>Required columns: Name, Surname, Resume Link, Cover Letter Link</small>
127
+ </div>
128
+ <button type="submit">Upload & Process File</button>
129
+ <button type="button" onclick="generateSample()" class="btn-secondary">Download Sample CSV</button>
130
+ <button type="button" onclick="clearLog()" class="btn-danger">Clear Log</button>
131
+ </form>
132
+ </div>
133
+
134
+ <div class="loading" id="loading">
135
+ <div class="spinner"></div>
136
+ <p>Processing...</p>
137
+ </div>
138
+
139
+ <div class="section">
140
+ <h2>📊 Response Log</h2>
141
+ <div class="response" id="responseLog">Ready to test API...\n</div>
142
+ </div>
143
+
144
+ <div class="section" id="resultsSection" style="display: none;">
145
+ <h2>🏆 Ranked Candidates</h2>
146
+ <div id="candidatesTable"></div>
147
+ </div>
148
+ </div>
149
+
150
+ <script>
151
+ function log(message, type = 'info') {
152
+ const logArea = document.getElementById('responseLog');
153
+ const timestamp = new Date().toLocaleTimeString();
154
+ const prefix = type === 'error' ? '❌' : type === 'success' ? '✅' : 'ℹ️';
155
+ logArea.textContent += `[${timestamp}] ${prefix} ${message}\n`;
156
+ logArea.scrollTop = logArea.scrollHeight;
157
+ }
158
+
159
+ function showLoading() {
160
+ document.getElementById('loading').style.display = 'block';
161
+ }
162
+
163
+ function hideLoading() {
164
+ document.getElementById('loading').style.display = 'none';
165
+ }
166
+
167
+ function clearLog() {
168
+ document.getElementById('responseLog').textContent = 'Log cleared...\n';
169
+ document.getElementById('resultsSection').style.display = 'none';
170
+ }
171
+
172
+ async function testRoot() {
173
+ showLoading();
174
+ log('Testing root endpoint...');
175
+ try {
176
+ const response = await fetch('/');
177
+ const data = await response.json();
178
+ log(`Status: ${response.status}`, response.ok ? 'success' : 'error');
179
+ log(`Response: ${JSON.stringify(data, null, 2)}`);
180
+ } catch (error) {
181
+ log(`Error: ${error.message}`, 'error');
182
+ }
183
+ hideLoading();
184
+ }
185
+
186
+ async function getCandidates() {
187
+ showLoading();
188
+ log('Getting ranked candidates...');
189
+ try {
190
+ const response = await fetch('/predict');
191
+ const data = await response.json();
192
+ log(`Status: ${response.status}`, response.ok ? 'success' : 'error');
193
+
194
+ if (response.ok && Array.isArray(data) && data.length > 0) {
195
+ log(`Found ${data.length} candidates`, 'success');
196
+ displayCandidates(data);
197
+ } else if (Array.isArray(data) && data.length === 0) {
198
+ log('No candidates found. Upload a file first.');
199
+ } else {
200
+ log(`Response: ${JSON.stringify(data, null, 2)}`);
201
+ }
202
+ } catch (error) {
203
+ log(`Error: ${error.message}`, 'error');
204
+ }
205
+ hideLoading();
206
+ }
207
+
208
+ function displayCandidates(candidates) {
209
+ const sorted = candidates.sort((a, b) => (b.priority || 0) - (a.priority || 0));
210
+
211
+ let tableHTML = `
212
+ <table class="candidates-table">
213
+ <thead>
214
+ <tr>
215
+ <th>Rank</th>
216
+ <th>Name</th>
217
+ <th>Surname</th>
218
+ <th>Priority Score</th>
219
+ <th>Resume</th>
220
+ <th>Cover Letter</th>
221
+ </tr>
222
+ </thead>
223
+ <tbody>
224
+ `;
225
+
226
+ sorted.forEach((candidate, index) => {
227
+ const priority = candidate.priority || 0;
228
+ let scoreClass = 'priority-low';
229
+ if (priority >= 70) scoreClass = 'priority-high';
230
+ else if (priority >= 40) scoreClass = 'priority-medium';
231
+
232
+ tableHTML += `
233
+ <tr>
234
+ <td><strong>#${index + 1}</strong></td>
235
+ <td>${candidate.name || 'N/A'}</td>
236
+ <td>${candidate.surname || 'N/A'}</td>
237
+ <td><span class="${scoreClass}">${priority.toFixed(1)}%</span></td>
238
+ <td><a href="${candidate.resume_link || '#'}" target="_blank">Link</a></td>
239
+ <td><a href="${candidate.cover_letter_link || '#'}" target="_blank">Link</a></td>
240
+ </tr>
241
+ `;
242
+ });
243
+
244
+ tableHTML += '</tbody></table>';
245
+ document.getElementById('candidatesTable').innerHTML = tableHTML;
246
+ document.getElementById('resultsSection').style.display = 'block';
247
+ }
248
+
249
+ function generateSample() {
250
+ const sampleData = [
251
+ ['Name', 'Surname', 'Resume Link', 'Cover Letter Link'],
252
+ ['John', 'Smith', 'https://example.com/john-resume.pdf', 'https://example.com/john-cover.pdf'],
253
+ ['Sarah', 'Johnson', 'https://example.com/sarah-resume.pdf', 'https://example.com/sarah-cover.pdf'],
254
+ ['Michael', 'Brown', 'https://example.com/michael-resume.pdf', 'https://example.com/michael-cover.pdf'],
255
+ ['Emily', 'Davis', 'https://example.com/emily-resume.pdf', 'https://example.com/emily-cover.pdf'],
256
+ ['David', 'Wilson', 'https://example.com/david-resume.pdf', 'https://example.com/david-cover.pdf']
257
+ ];
258
+
259
+ const csvContent = sampleData.map(row => row.join(',')).join('\\n');
260
+ const blob = new Blob([csvContent], { type: 'text/csv' });
261
+ const url = window.URL.createObjectURL(blob);
262
+ const a = document.createElement('a');
263
+ a.href = url;
264
+ a.download = 'sample_candidates.csv';
265
+ a.click();
266
+ window.URL.revokeObjectURL(url);
267
+ log('Sample CSV downloaded! Convert to Excel and upload.', 'success');
268
+ }
269
+
270
+ // Handle form submission
271
+ document.getElementById('uploadForm').addEventListener('submit', async function(e) {
272
+ e.preventDefault();
273
+
274
+ const fileInput = document.getElementById('file');
275
+ const file = fileInput.files[0];
276
+
277
+ if (!file) {
278
+ log('Please select a file', 'error');
279
+ return;
280
+ }
281
+
282
+ showLoading();
283
+ log(`Uploading: ${file.name} (${(file.size / 1024).toFixed(1)} KB)`);
284
+
285
+ const formData = new FormData();
286
+ formData.append('file', file);
287
+
288
+ try {
289
+ const response = await fetch('/upload', {
290
+ method: 'POST',
291
+ body: formData
292
+ });
293
+
294
+ const data = await response.json();
295
+ log(`Upload status: ${response.status}`, response.ok ? 'success' : 'error');
296
+ log(`Response: ${JSON.stringify(data, null, 2)}`);
297
+
298
+ if (response.ok) {
299
+ log('File uploaded successfully!', 'success');
300
+ setTimeout(() => getCandidates(), 1500);
301
+ }
302
+ } catch (error) {
303
+ log(`Upload error: ${error.message}`, 'error');
304
+ }
305
+
306
+ hideLoading();
307
+ });
308
+
309
+ // Initialize
310
+ log('API Testing Interface ready!', 'success');
311
+ log('1. Test endpoints first');
312
+ log('2. Upload Excel file or download sample');
313
+ log('3. View ranked results');
314
+ </script>
315
+ </body>
316
+ </html>
317
+ """
318
+
319
  @app.get("/upload-form", response_class=HTMLResponse)
320
  async def upload_form():
321
  return """