Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -619,29 +619,66 @@ def _extract_json(raw: str) -> str:
|
|
| 619 |
|
| 620 |
# ================= Format Skills Visualisation =================
|
| 621 |
def format_skill_cards(skills_data):
|
| 622 |
-
if not skills_data:
|
| 623 |
-
return "No skills data available"
|
| 624 |
|
| 625 |
cards = []
|
| 626 |
for skill in skills_data:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 627 |
card = f"""
|
| 628 |
<div class='skill-card'>
|
| 629 |
<div class='skill-header'>
|
| 630 |
-
<
|
| 631 |
-
|
| 632 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 633 |
</div>
|
|
|
|
| 634 |
<div class='skill-body'>
|
| 635 |
-
<
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 645 |
</div>
|
| 646 |
</div>
|
| 647 |
</div>
|
|
@@ -741,6 +778,7 @@ def process_pdf(file):
|
|
| 741 |
# Format skills before returning
|
| 742 |
formatted_skills = format_skill_cards(joined_skills)
|
| 743 |
|
|
|
|
| 744 |
joined_skills_esco = []
|
| 745 |
if has_esco and skill_esco_extract:
|
| 746 |
assessment_esco_lookup = {item['skill_name']: item for item in skill_esco_map}
|
|
@@ -753,7 +791,7 @@ def process_pdf(file):
|
|
| 753 |
}
|
| 754 |
for skill in skill_esco_extract
|
| 755 |
]
|
| 756 |
-
|
| 757 |
interview = build_interview(responsibilities, skills)
|
| 758 |
|
| 759 |
# Prepare the results for each output component
|
|
@@ -773,6 +811,8 @@ def process_pdf(file):
|
|
| 773 |
for i in range(1, 6) for field in ["code", "name", "desc"]}
|
| 774 |
esco_skills = None
|
| 775 |
|
|
|
|
|
|
|
| 776 |
debug_message = "Processing completed successfully."
|
| 777 |
return (
|
| 778 |
os.path.basename(file.name),
|
|
@@ -784,7 +824,8 @@ def process_pdf(file):
|
|
| 784 |
#joined_skills,
|
| 785 |
formatted_skills,
|
| 786 |
esco_levels,
|
| 787 |
-
esco_skills,
|
|
|
|
| 788 |
debug_message if DEBUG else None
|
| 789 |
)
|
| 790 |
|
|
@@ -1209,69 +1250,147 @@ label {
|
|
| 1209 |
/* Skills Card */
|
| 1210 |
.skills-container {
|
| 1211 |
display: grid;
|
| 1212 |
-
grid-template-columns: repeat(auto-fill, minmax(
|
| 1213 |
-
gap:
|
| 1214 |
padding: 1rem;
|
| 1215 |
}
|
| 1216 |
|
|
|
|
| 1217 |
.skill-card {
|
| 1218 |
background: white;
|
| 1219 |
border-radius: 8px;
|
| 1220 |
-
box-shadow: 0 2px
|
| 1221 |
overflow: hidden;
|
| 1222 |
-
transition:
|
|
|
|
| 1223 |
}
|
| 1224 |
|
| 1225 |
.skill-card:hover {
|
| 1226 |
-
transform: translateY(-
|
| 1227 |
-
box-shadow: 0
|
| 1228 |
}
|
| 1229 |
|
|
|
|
| 1230 |
.skill-header {
|
| 1231 |
background: #0033A0;
|
| 1232 |
color: white;
|
| 1233 |
-
padding:
|
| 1234 |
display: flex;
|
| 1235 |
-
flex-
|
| 1236 |
-
gap: 0.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1237 |
align-items: center;
|
| 1238 |
}
|
| 1239 |
|
| 1240 |
-
.skill-
|
| 1241 |
margin: 0;
|
| 1242 |
-
|
| 1243 |
-
font-
|
| 1244 |
}
|
| 1245 |
|
| 1246 |
-
.skill-
|
|
|
|
|
|
|
|
|
|
| 1247 |
padding: 0.25rem 0.5rem;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1248 |
border-radius: 999px;
|
| 1249 |
font-size: 0.8rem;
|
| 1250 |
-
font-weight:
|
| 1251 |
}
|
| 1252 |
|
| 1253 |
-
|
| 1254 |
-
.skill-pill.
|
| 1255 |
-
.skill-pill.
|
| 1256 |
-
.skill-pill.
|
|
|
|
| 1257 |
|
|
|
|
| 1258 |
.skill-body {
|
| 1259 |
-
padding:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1260 |
}
|
| 1261 |
|
| 1262 |
-
.skill-
|
| 1263 |
-
margin
|
| 1264 |
-
|
| 1265 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1266 |
display: flex;
|
| 1267 |
flex-direction: column;
|
| 1268 |
-
gap:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1269 |
}
|
| 1270 |
|
| 1271 |
progress {
|
| 1272 |
-
|
| 1273 |
-
height:
|
| 1274 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1275 |
}
|
| 1276 |
|
| 1277 |
/* Output Markdown */
|
|
@@ -1299,6 +1418,20 @@ progress {
|
|
| 1299 |
margin-right: 0 !important;
|
| 1300 |
margin-bottom: 1rem !important;
|
| 1301 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1302 |
}
|
| 1303 |
""",
|
| 1304 |
head='''
|
|
@@ -1364,7 +1497,10 @@ progress {
|
|
| 1364 |
with gr.Row():
|
| 1365 |
with gr.Column():
|
| 1366 |
gr.Markdown("### Mapped Skills")
|
| 1367 |
-
skills_output = gr.HTML(
|
|
|
|
|
|
|
|
|
|
| 1368 |
|
| 1369 |
with gr.Row():
|
| 1370 |
with gr.Column():
|
|
@@ -1391,7 +1527,10 @@ progress {
|
|
| 1391 |
|
| 1392 |
with gr.Row():
|
| 1393 |
with gr.Column():
|
| 1394 |
-
esco_skills_output =
|
|
|
|
|
|
|
|
|
|
| 1395 |
|
| 1396 |
|
| 1397 |
|
|
|
|
| 619 |
|
| 620 |
# ================= Format Skills Visualisation =================
|
| 621 |
def format_skill_cards(skills_data):
|
| 622 |
+
if not skills_data or not isinstance(skills_data, list):
|
| 623 |
+
return "<div class='skills-container'><p>No skills data available</p></div>"
|
| 624 |
|
| 625 |
cards = []
|
| 626 |
for skill in skills_data:
|
| 627 |
+
if not isinstance(skill, dict):
|
| 628 |
+
continue
|
| 629 |
+
|
| 630 |
+
# Safely get all fields with fallbacks
|
| 631 |
+
skill_name = skill.get('skill_name', 'Unnamed Skill')
|
| 632 |
+
skill_code = skill.get('skill_code', 'N/A')
|
| 633 |
+
description = skill.get('skill_description', 'No description available')
|
| 634 |
+
skill_type = skill.get('type', '').capitalize()
|
| 635 |
+
importance = skill.get('importance', '').capitalize()
|
| 636 |
+
proficiency = skill.get('proficiency_level', '').capitalize()
|
| 637 |
+
distinctive = skill.get('distinctive_elements', 'Not specified')
|
| 638 |
+
resume_signals = skill.get('resume_signals', 'Not specified')
|
| 639 |
+
assessment = skill.get('assessment_method', 'Not specified')
|
| 640 |
+
|
| 641 |
card = f"""
|
| 642 |
<div class='skill-card'>
|
| 643 |
<div class='skill-header'>
|
| 644 |
+
<div class='skill-title'>
|
| 645 |
+
<h3>{skill_name}</h3>
|
| 646 |
+
<span class='skill-code'>Code: {skill_code}</span>
|
| 647 |
+
</div>
|
| 648 |
+
<div class='skill-pills'>
|
| 649 |
+
<span class='skill-pill type-{skill.get("type", "").lower()}'>{skill_type}</span>
|
| 650 |
+
<span class='skill-pill importance-{skill.get("importance", "").lower()}'>{importance}</span>
|
| 651 |
+
</div>
|
| 652 |
</div>
|
| 653 |
+
|
| 654 |
<div class='skill-body'>
|
| 655 |
+
<div class='skill-description'>
|
| 656 |
+
<p>{description}</p>
|
| 657 |
+
</div>
|
| 658 |
+
|
| 659 |
+
<div class='skill-details'>
|
| 660 |
+
<div class='detail-group'>
|
| 661 |
+
<label>Proficiency Level:</label>
|
| 662 |
+
<div class='proficiency-bar'>
|
| 663 |
+
<progress value={get_progress_value(skill.get("proficiency_level"))} max="3"></progress>
|
| 664 |
+
<span>{proficiency}</span>
|
| 665 |
+
</div>
|
| 666 |
+
</div>
|
| 667 |
+
|
| 668 |
+
<div class='detail-group'>
|
| 669 |
+
<label>Distinctive Elements:</label>
|
| 670 |
+
<p class='detail-content'>{distinctive}</p>
|
| 671 |
+
</div>
|
| 672 |
+
|
| 673 |
+
<div class='detail-group'>
|
| 674 |
+
<label>Resume Signals:</label>
|
| 675 |
+
<p class='detail-content'>{resume_signals}</p>
|
| 676 |
+
</div>
|
| 677 |
+
|
| 678 |
+
<div class='detail-group'>
|
| 679 |
+
<label>Assessment Method:</label>
|
| 680 |
+
<p class='detail-content'>{assessment}</p>
|
| 681 |
+
</div>
|
| 682 |
</div>
|
| 683 |
</div>
|
| 684 |
</div>
|
|
|
|
| 778 |
# Format skills before returning
|
| 779 |
formatted_skills = format_skill_cards(joined_skills)
|
| 780 |
|
| 781 |
+
|
| 782 |
joined_skills_esco = []
|
| 783 |
if has_esco and skill_esco_extract:
|
| 784 |
assessment_esco_lookup = {item['skill_name']: item for item in skill_esco_map}
|
|
|
|
| 791 |
}
|
| 792 |
for skill in skill_esco_extract
|
| 793 |
]
|
| 794 |
+
|
| 795 |
interview = build_interview(responsibilities, skills)
|
| 796 |
|
| 797 |
# Prepare the results for each output component
|
|
|
|
| 811 |
for i in range(1, 6) for field in ["code", "name", "desc"]}
|
| 812 |
esco_skills = None
|
| 813 |
|
| 814 |
+
formatted_esco_skills = format_skill_cards(esco_skills)
|
| 815 |
+
|
| 816 |
debug_message = "Processing completed successfully."
|
| 817 |
return (
|
| 818 |
os.path.basename(file.name),
|
|
|
|
| 824 |
#joined_skills,
|
| 825 |
formatted_skills,
|
| 826 |
esco_levels,
|
| 827 |
+
#esco_skills,
|
| 828 |
+
formatted_esco_skills
|
| 829 |
debug_message if DEBUG else None
|
| 830 |
)
|
| 831 |
|
|
|
|
| 1250 |
/* Skills Card */
|
| 1251 |
.skills-container {
|
| 1252 |
display: grid;
|
| 1253 |
+
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
|
| 1254 |
+
gap: 1.5rem;
|
| 1255 |
padding: 1rem;
|
| 1256 |
}
|
| 1257 |
|
| 1258 |
+
/* Card styling */
|
| 1259 |
.skill-card {
|
| 1260 |
background: white;
|
| 1261 |
border-radius: 8px;
|
| 1262 |
+
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
|
| 1263 |
overflow: hidden;
|
| 1264 |
+
transition: all 0.3s ease;
|
| 1265 |
+
border: 1px solid #e0e0e0;
|
| 1266 |
}
|
| 1267 |
|
| 1268 |
.skill-card:hover {
|
| 1269 |
+
transform: translateY(-5px);
|
| 1270 |
+
box-shadow: 0 6px 16px rgba(0,0,0,0.12);
|
| 1271 |
}
|
| 1272 |
|
| 1273 |
+
/* Header section */
|
| 1274 |
.skill-header {
|
| 1275 |
background: #0033A0;
|
| 1276 |
color: white;
|
| 1277 |
+
padding: 1.2rem;
|
| 1278 |
display: flex;
|
| 1279 |
+
flex-direction: column;
|
| 1280 |
+
gap: 0.8rem;
|
| 1281 |
+
}
|
| 1282 |
+
|
| 1283 |
+
.skill-title {
|
| 1284 |
+
display: flex;
|
| 1285 |
+
justify-content: space-between;
|
| 1286 |
align-items: center;
|
| 1287 |
}
|
| 1288 |
|
| 1289 |
+
.skill-title h3 {
|
| 1290 |
margin: 0;
|
| 1291 |
+
font-size: 1.2rem;
|
| 1292 |
+
font-weight: 600;
|
| 1293 |
}
|
| 1294 |
|
| 1295 |
+
.skill-code {
|
| 1296 |
+
font-size: 0.85rem;
|
| 1297 |
+
opacity: 0.8;
|
| 1298 |
+
background: rgba(255,255,255,0.15);
|
| 1299 |
padding: 0.25rem 0.5rem;
|
| 1300 |
+
border-radius: 4px;
|
| 1301 |
+
}
|
| 1302 |
+
|
| 1303 |
+
.skill-pills {
|
| 1304 |
+
display: flex;
|
| 1305 |
+
gap: 0.5rem;
|
| 1306 |
+
flex-wrap: wrap;
|
| 1307 |
+
}
|
| 1308 |
+
|
| 1309 |
+
.skill-pill {
|
| 1310 |
+
padding: 0.35rem 0.7rem;
|
| 1311 |
border-radius: 999px;
|
| 1312 |
font-size: 0.8rem;
|
| 1313 |
+
font-weight: 500;
|
| 1314 |
}
|
| 1315 |
|
| 1316 |
+
/* Type and Importance pills */
|
| 1317 |
+
.skill-pill.type-skill { background: #4CAF50; color: white; }
|
| 1318 |
+
.skill-pill.type-knowledge { background: #2196F3; color: white; }
|
| 1319 |
+
.skill-pill.importance-essential { background: #F44336; color: white; }
|
| 1320 |
+
.skill-pill.importance-optional { background: #FF9800; color: white; }
|
| 1321 |
|
| 1322 |
+
/* Body section */
|
| 1323 |
.skill-body {
|
| 1324 |
+
padding: 1.2rem;
|
| 1325 |
+
}
|
| 1326 |
+
|
| 1327 |
+
.skill-description {
|
| 1328 |
+
margin-bottom: 1.2rem;
|
| 1329 |
+
padding-bottom: 1rem;
|
| 1330 |
+
border-bottom: 1px dashed #eee;
|
| 1331 |
}
|
| 1332 |
|
| 1333 |
+
.skill-description p {
|
| 1334 |
+
margin: 0;
|
| 1335 |
+
color: #555;
|
| 1336 |
+
line-height: 1.5;
|
| 1337 |
+
}
|
| 1338 |
+
|
| 1339 |
+
/* Details sections */
|
| 1340 |
+
.skill-details {
|
| 1341 |
display: flex;
|
| 1342 |
flex-direction: column;
|
| 1343 |
+
gap: 1rem;
|
| 1344 |
+
}
|
| 1345 |
+
|
| 1346 |
+
.detail-group {
|
| 1347 |
+
display: flex;
|
| 1348 |
+
flex-direction: column;
|
| 1349 |
+
gap: 0.3rem;
|
| 1350 |
+
}
|
| 1351 |
+
|
| 1352 |
+
.detail-group label {
|
| 1353 |
+
font-weight: 600;
|
| 1354 |
+
font-size: 0.9rem;
|
| 1355 |
+
color: #0033A0;
|
| 1356 |
+
}
|
| 1357 |
+
|
| 1358 |
+
.detail-content {
|
| 1359 |
+
margin: 0;
|
| 1360 |
+
font-size: 0.95rem;
|
| 1361 |
+
color: #444;
|
| 1362 |
+
line-height: 1.5;
|
| 1363 |
+
}
|
| 1364 |
+
|
| 1365 |
+
/* Proficiency bar */
|
| 1366 |
+
.proficiency-bar {
|
| 1367 |
+
display: flex;
|
| 1368 |
+
align-items: center;
|
| 1369 |
+
gap: 0.8rem;
|
| 1370 |
+
margin-top: 0.3rem;
|
| 1371 |
}
|
| 1372 |
|
| 1373 |
progress {
|
| 1374 |
+
flex-grow: 1;
|
| 1375 |
+
height: 8px;
|
| 1376 |
+
border-radius: 4px;
|
| 1377 |
+
}
|
| 1378 |
+
|
| 1379 |
+
progress::-webkit-progress-bar {
|
| 1380 |
+
background-color: #f0f0f0;
|
| 1381 |
+
border-radius: 4px;
|
| 1382 |
+
}
|
| 1383 |
+
|
| 1384 |
+
progress::-webkit-progress-value {
|
| 1385 |
+
background-color: #0033A0;
|
| 1386 |
+
border-radius: 4px;
|
| 1387 |
+
}
|
| 1388 |
+
|
| 1389 |
+
.proficiency-bar span {
|
| 1390 |
+
font-size: 0.9rem;
|
| 1391 |
+
font-weight: 500;
|
| 1392 |
+
min-width: 80px;
|
| 1393 |
+
text-align: right;
|
| 1394 |
}
|
| 1395 |
|
| 1396 |
/* Output Markdown */
|
|
|
|
| 1418 |
margin-right: 0 !important;
|
| 1419 |
margin-bottom: 1rem !important;
|
| 1420 |
}
|
| 1421 |
+
|
| 1422 |
+
.skills-container {
|
| 1423 |
+
grid-template-columns: 1fr;
|
| 1424 |
+
}
|
| 1425 |
+
|
| 1426 |
+
.skill-header {
|
| 1427 |
+
flex-direction: column;
|
| 1428 |
+
}
|
| 1429 |
+
|
| 1430 |
+
.skill-title {
|
| 1431 |
+
flex-direction: column;
|
| 1432 |
+
align-items: flex-start;
|
| 1433 |
+
gap: 0.5rem;
|
| 1434 |
+
}
|
| 1435 |
}
|
| 1436 |
""",
|
| 1437 |
head='''
|
|
|
|
| 1497 |
with gr.Row():
|
| 1498 |
with gr.Column():
|
| 1499 |
gr.Markdown("### Mapped Skills")
|
| 1500 |
+
skills_output = gr.HTML(
|
| 1501 |
+
elem_classes="skills-container",
|
| 1502 |
+
label=""
|
| 1503 |
+
)
|
| 1504 |
|
| 1505 |
with gr.Row():
|
| 1506 |
with gr.Column():
|
|
|
|
| 1527 |
|
| 1528 |
with gr.Row():
|
| 1529 |
with gr.Column():
|
| 1530 |
+
esco_skills_output = (
|
| 1531 |
+
elem_classes="skills-container",
|
| 1532 |
+
label=""
|
| 1533 |
+
)
|
| 1534 |
|
| 1535 |
|
| 1536 |
|