Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -617,6 +617,44 @@ def _extract_json(raw: str) -> str:
|
|
| 617 |
json_text = json_text.strip()
|
| 618 |
return json_text
|
| 619 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 620 |
# ================= Process Analysis =================
|
| 621 |
from concurrent.futures import ThreadPoolExecutor
|
| 622 |
|
|
@@ -700,6 +738,9 @@ def process_pdf(file):
|
|
| 700 |
for skill in skills
|
| 701 |
]
|
| 702 |
|
|
|
|
|
|
|
|
|
|
| 703 |
joined_skills_esco = []
|
| 704 |
if has_esco and skill_esco_extract:
|
| 705 |
assessment_esco_lookup = {item['skill_name']: item for item in skill_esco_map}
|
|
@@ -835,6 +876,8 @@ def generate_word_document(
|
|
| 835 |
log_debug(f"Error building result dictionary: {str(e)}")
|
| 836 |
result = default_values
|
| 837 |
|
|
|
|
|
|
|
| 838 |
# ================= DOCUMENT CONTENT GENERATION =================
|
| 839 |
try:
|
| 840 |
# Document header
|
|
@@ -1131,6 +1174,105 @@ label {
|
|
| 1131 |
.btn-primary:active {
|
| 1132 |
transform: translateY(0) !important;
|
| 1133 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1134 |
/* Output Markdown */
|
| 1135 |
.gr-markdown {
|
| 1136 |
background: #f9f9f9;
|
|
@@ -1181,6 +1323,36 @@ label {
|
|
| 1181 |
<p>Use AI to standardise an initial draft position description and identify related Job Family, Occupation, Qualification, match Skills and suggest interview questions.</p>
|
| 1182 |
</div>
|
| 1183 |
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1184 |
|
| 1185 |
with gr.Row():
|
| 1186 |
with gr.Column():
|
|
|
|
| 617 |
json_text = json_text.strip()
|
| 618 |
return json_text
|
| 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 |
+
<h3>{skill.get('skill_name', 'Unnamed Skill')}</h3>
|
| 631 |
+
<div class='skill-pill {skill.get("type", "").lower()}'>{skill.get("type", "").capitalize()}</div>
|
| 632 |
+
<div class='skill-pill {skill.get("importance", "").lower()}'>{skill.get("importance", "").capitalize()}</div>
|
| 633 |
+
</div>
|
| 634 |
+
<div class='skill-body'>
|
| 635 |
+
<p><strong>Description:</strong> {skill.get('skill_description', '')}</p>
|
| 636 |
+
<div class='skill-meta'>
|
| 637 |
+
<span class='proficiency'>
|
| 638 |
+
<strong>Level:</strong>
|
| 639 |
+
<progress value={get_progress_value(skill.get("proficiency_level"))} max="3"></progress>
|
| 640 |
+
{skill.get("proficiency_level", "").capitalize()}
|
| 641 |
+
</span>
|
| 642 |
+
<span class='assessment'>
|
| 643 |
+
<strong>Assessment:</strong> {skill.get("assessment_method", "")}
|
| 644 |
+
</span>
|
| 645 |
+
</div>
|
| 646 |
+
</div>
|
| 647 |
+
</div>
|
| 648 |
+
"""
|
| 649 |
+
cards.append(card)
|
| 650 |
+
|
| 651 |
+
return f"<div class='skills-container'>{''.join(cards)}</div>"
|
| 652 |
+
|
| 653 |
+
def get_progress_value(level):
|
| 654 |
+
level_map = {"basic": 1, "intermediate": 2, "advanced": 3}
|
| 655 |
+
return str(level_map.get(level.lower(), 1))
|
| 656 |
+
|
| 657 |
+
|
| 658 |
# ================= Process Analysis =================
|
| 659 |
from concurrent.futures import ThreadPoolExecutor
|
| 660 |
|
|
|
|
| 738 |
for skill in skills
|
| 739 |
]
|
| 740 |
|
| 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}
|
|
|
|
| 876 |
log_debug(f"Error building result dictionary: {str(e)}")
|
| 877 |
result = default_values
|
| 878 |
|
| 879 |
+
|
| 880 |
+
|
| 881 |
# ================= DOCUMENT CONTENT GENERATION =================
|
| 882 |
try:
|
| 883 |
# Document header
|
|
|
|
| 1174 |
.btn-primary:active {
|
| 1175 |
transform: translateY(0) !important;
|
| 1176 |
}
|
| 1177 |
+
|
| 1178 |
+
/* Intro */
|
| 1179 |
+
.intro-box {
|
| 1180 |
+
background: #f0f7ff;
|
| 1181 |
+
border-left: 4px solid #0033A0;
|
| 1182 |
+
border-radius: 4px;
|
| 1183 |
+
padding: 16px;
|
| 1184 |
+
margin-bottom: 20px;
|
| 1185 |
+
}
|
| 1186 |
+
.intro-title {
|
| 1187 |
+
color: #0033A0;
|
| 1188 |
+
font-weight: 600;
|
| 1189 |
+
margin-top: 0 !important;
|
| 1190 |
+
}
|
| 1191 |
+
.intro-icon {
|
| 1192 |
+
color: #0033A0;
|
| 1193 |
+
margin-right: 8px;
|
| 1194 |
+
}
|
| 1195 |
+
.benefits-grid {
|
| 1196 |
+
display: grid;
|
| 1197 |
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
| 1198 |
+
gap: 12px;
|
| 1199 |
+
margin: 16px 0;
|
| 1200 |
+
}
|
| 1201 |
+
.benefit-card {
|
| 1202 |
+
background: white;
|
| 1203 |
+
padding: 12px;
|
| 1204 |
+
border-radius: 8px;
|
| 1205 |
+
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
| 1206 |
+
}
|
| 1207 |
+
|
| 1208 |
+
/* Skills Card */
|
| 1209 |
+
.skills-container {
|
| 1210 |
+
display: grid;
|
| 1211 |
+
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
| 1212 |
+
gap: 1rem;
|
| 1213 |
+
padding: 1rem;
|
| 1214 |
+
}
|
| 1215 |
+
|
| 1216 |
+
.skill-card {
|
| 1217 |
+
background: white;
|
| 1218 |
+
border-radius: 8px;
|
| 1219 |
+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
| 1220 |
+
overflow: hidden;
|
| 1221 |
+
transition: transform 0.2s;
|
| 1222 |
+
}
|
| 1223 |
+
|
| 1224 |
+
.skill-card:hover {
|
| 1225 |
+
transform: translateY(-3px);
|
| 1226 |
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
| 1227 |
+
}
|
| 1228 |
+
|
| 1229 |
+
.skill-header {
|
| 1230 |
+
background: #0033A0;
|
| 1231 |
+
color: white;
|
| 1232 |
+
padding: 1rem;
|
| 1233 |
+
display: flex;
|
| 1234 |
+
flex-wrap: wrap;
|
| 1235 |
+
gap: 0.5rem;
|
| 1236 |
+
align-items: center;
|
| 1237 |
+
}
|
| 1238 |
+
|
| 1239 |
+
.skill-header h3 {
|
| 1240 |
+
margin: 0;
|
| 1241 |
+
flex-grow: 1;
|
| 1242 |
+
font-size: 1.1rem;
|
| 1243 |
+
}
|
| 1244 |
+
|
| 1245 |
+
.skill-pill {
|
| 1246 |
+
padding: 0.25rem 0.5rem;
|
| 1247 |
+
border-radius: 999px;
|
| 1248 |
+
font-size: 0.8rem;
|
| 1249 |
+
font-weight: bold;
|
| 1250 |
+
}
|
| 1251 |
+
|
| 1252 |
+
.skill-pill.skill { background: #4CAF50; }
|
| 1253 |
+
.skill-pill.knowledge { background: #2196F3; }
|
| 1254 |
+
.skill-pill.essential { background: #F44336; }
|
| 1255 |
+
.skill-pill.optional { background: #FF9800; }
|
| 1256 |
+
|
| 1257 |
+
.skill-body {
|
| 1258 |
+
padding: 1rem;
|
| 1259 |
+
}
|
| 1260 |
+
|
| 1261 |
+
.skill-meta {
|
| 1262 |
+
margin-top: 1rem;
|
| 1263 |
+
padding-top: 1rem;
|
| 1264 |
+
border-top: 1px solid #eee;
|
| 1265 |
+
display: flex;
|
| 1266 |
+
flex-direction: column;
|
| 1267 |
+
gap: 0.5rem;
|
| 1268 |
+
}
|
| 1269 |
+
|
| 1270 |
+
progress {
|
| 1271 |
+
width: 100%;
|
| 1272 |
+
height: 6px;
|
| 1273 |
+
margin-top: 0.25rem;
|
| 1274 |
+
}
|
| 1275 |
+
|
| 1276 |
/* Output Markdown */
|
| 1277 |
.gr-markdown {
|
| 1278 |
background: #f9f9f9;
|
|
|
|
| 1323 |
<p>Use AI to standardise an initial draft position description and identify related Job Family, Occupation, Qualification, match Skills and suggest interview questions.</p>
|
| 1324 |
</div>
|
| 1325 |
""")
|
| 1326 |
+
# Introduction Section
|
| 1327 |
+
with gr.Column(elem_classes="intro-box"):
|
| 1328 |
+
gr.Markdown("""
|
| 1329 |
+
<h2 class='intro-title'><span class='intro-icon'>📊</span>Standardizing Talent Management with AI</h2>
|
| 1330 |
+
<p>This tool transforms position descriptions into standardized job profiles using international classification frameworks.</p>
|
| 1331 |
+
|
| 1332 |
+
<div class='benefits-grid'>
|
| 1333 |
+
<div class='benefit-card'>
|
| 1334 |
+
<strong>🔍 Consistent Roles</strong>
|
| 1335 |
+
<p>Aligns with ESCO/CCOG standards automatically</p>
|
| 1336 |
+
</div>
|
| 1337 |
+
<div class='benefit-card'>
|
| 1338 |
+
<strong>⏱️ Time Saver</strong>
|
| 1339 |
+
<p>Reduces hours of manual research to minutes</p>
|
| 1340 |
+
</div>
|
| 1341 |
+
<div class='benefit-card'>
|
| 1342 |
+
<strong>⚖️ Reduced Bias</strong>
|
| 1343 |
+
<p>Data-driven skills recommendations</p>
|
| 1344 |
+
</div>
|
| 1345 |
+
<div class='benefit-card'>
|
| 1346 |
+
<strong>🎯 Better Hiring</strong>
|
| 1347 |
+
<p>Generates tailored interview questions</p>
|
| 1348 |
+
</div>
|
| 1349 |
+
</div>
|
| 1350 |
+
|
| 1351 |
+
<div style="background: #e6f2ff; padding: 12px; border-radius: 4px;">
|
| 1352 |
+
<strong>💡 How it works:</strong> Upload a PD PDF to get automated classification,
|
| 1353 |
+
skills mapping, and interview questions.
|
| 1354 |
+
</div>
|
| 1355 |
+
""")
|
| 1356 |
|
| 1357 |
with gr.Row():
|
| 1358 |
with gr.Column():
|