Spaces:
Running
Running
Upload app.py with huggingface_hub
Browse files
app.py
CHANGED
|
@@ -420,93 +420,115 @@ def analyze_job(job_title):
|
|
| 420 |
empty, "", ""
|
| 421 |
)
|
| 422 |
|
| 423 |
-
|
| 424 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 425 |
empty = go.Figure()
|
| 426 |
apply_layout(empty, height=100)
|
| 427 |
return (
|
| 428 |
-
"<p style='color:
|
| 429 |
empty, "", ""
|
| 430 |
)
|
| 431 |
|
| 432 |
-
row = row.iloc[0]
|
| 433 |
-
score = row["observed_exposure"]
|
| 434 |
-
rank = row["rank"]
|
| 435 |
-
occ_code = row["occ_code"]
|
| 436 |
-
|
| 437 |
-
# Percentile
|
| 438 |
-
percentile = ((total_jobs - rank) / total_jobs * 100)
|
| 439 |
-
|
| 440 |
-
# Find similar jobs (same 2-digit SOC code)
|
| 441 |
-
soc_prefix = occ_code[:2]
|
| 442 |
-
similar = df_jobs[
|
| 443 |
-
(df_jobs["occ_code"].str.startswith(soc_prefix))
|
| 444 |
-
& (df_jobs["title"] != job_title)
|
| 445 |
-
].head(5)
|
| 446 |
-
|
| 447 |
-
# Build cards HTML
|
| 448 |
-
cards_html = f"""
|
| 449 |
-
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin:16px 0;">
|
| 450 |
-
{styled_card("AI Exposure Score", f"{score*100:.1f}%", f"Rank #{rank} of {total_jobs}",
|
| 451 |
-
COLORS['danger'] if score >= 0.5 else COLORS['warning'] if score >= 0.3
|
| 452 |
-
else COLORS['info'] if score >= 0.15 else COLORS['success'])}
|
| 453 |
-
{styled_card("Percentile", f"{percentile:.0f}th",
|
| 454 |
-
"Higher = more exposed than other jobs",
|
| 455 |
-
COLORS['primary'])}
|
| 456 |
-
{styled_card("Risk Level", risk_badge(score), "", COLORS['text'])}
|
| 457 |
-
</div>
|
| 458 |
-
<div style="background:white;border-radius:16px;padding:24px;margin-top:16px;
|
| 459 |
-
box-shadow:0 2px 12px rgba(0,0,0,0.06);border:1px solid #f0ece8;">
|
| 460 |
-
<h3 style="margin:0 0 4px 0;color:{COLORS['secondary']};">{job_title}</h3>
|
| 461 |
-
<p style="margin:0;color:{COLORS['muted']};">O*NET Code: {occ_code}</p>
|
| 462 |
-
</div>
|
| 463 |
-
"""
|
| 464 |
-
|
| 465 |
-
# Gauge chart
|
| 466 |
-
gauge = make_gauge(score)
|
| 467 |
-
|
| 468 |
-
# Similar jobs comparison
|
| 469 |
-
if not similar.empty:
|
| 470 |
-
compare_data = pd.concat([row.to_frame().T, similar])
|
| 471 |
-
similar_html = "<div style='margin-top:16px;'><h4>Related Occupations</h4><table style='width:100%;border-collapse:collapse;'>"
|
| 472 |
-
similar_html += "<tr style='border-bottom:2px solid #f0ece8;'><th style='text-align:left;padding:8px;'>Occupation</th><th style='text-align:right;padding:8px;'>Exposure</th></tr>"
|
| 473 |
-
for _, s in similar.iterrows():
|
| 474 |
-
bar_width = s["observed_exposure"] * 100
|
| 475 |
-
similar_html += f"""
|
| 476 |
-
<tr style='border-bottom:1px solid #f5f0eb;'>
|
| 477 |
-
<td style='padding:10px 8px;'>{s['title']}</td>
|
| 478 |
-
<td style='padding:10px 8px;text-align:right;'>
|
| 479 |
-
<div style='display:flex;align-items:center;justify-content:flex-end;gap:8px;'>
|
| 480 |
-
<div style='width:80px;height:8px;background:#f0ece8;border-radius:4px;overflow:hidden;'>
|
| 481 |
-
<div style='width:{bar_width}%;height:100%;background:{COLORS["primary"]};border-radius:4px;'></div>
|
| 482 |
-
</div>
|
| 483 |
-
<span style='font-weight:600;min-width:45px;'>{s['observed_exposure']*100:.1f}%</span>
|
| 484 |
-
</div>
|
| 485 |
-
</td>
|
| 486 |
-
</tr>"""
|
| 487 |
-
similar_html += "</table></div>"
|
| 488 |
-
else:
|
| 489 |
-
similar_html = ""
|
| 490 |
-
|
| 491 |
-
# Interpretation
|
| 492 |
-
if score >= 0.5:
|
| 493 |
-
interp = "This occupation has **very high** AI exposure. A significant portion of its tasks are already being performed with AI assistance. Workers in this field should actively develop AI collaboration skills."
|
| 494 |
-
elif score >= 0.3:
|
| 495 |
-
interp = "This occupation has **high** AI exposure. Many of its tasks intersect with AI capabilities. Embracing AI tools can significantly boost productivity."
|
| 496 |
-
elif score >= 0.15:
|
| 497 |
-
interp = "This occupation has **moderate** AI exposure. Some tasks are being augmented by AI, but core functions still require substantial human expertise."
|
| 498 |
-
else:
|
| 499 |
-
interp = "This occupation has **low** AI exposure. Most of its tasks are not significantly impacted by current AI capabilities, though this may change over time."
|
| 500 |
-
|
| 501 |
-
interp_html = f"""
|
| 502 |
-
<div style="background:#fef9f5;border-left:4px solid {COLORS['primary']};padding:16px 20px;
|
| 503 |
-
border-radius:0 12px 12px 0;margin-top:16px;">
|
| 504 |
-
<strong>Interpretation:</strong> {interp}
|
| 505 |
-
</div>
|
| 506 |
-
"""
|
| 507 |
-
|
| 508 |
-
return cards_html, gauge, similar_html, interp_html
|
| 509 |
-
|
| 510 |
|
| 511 |
# ============================================================================
|
| 512 |
# KEY METRICS
|
|
|
|
| 420 |
empty, "", ""
|
| 421 |
)
|
| 422 |
|
| 423 |
+
try:
|
| 424 |
+
row = df_jobs[df_jobs["title"] == job_title]
|
| 425 |
+
if row.empty:
|
| 426 |
+
empty = go.Figure()
|
| 427 |
+
apply_layout(empty, height=100)
|
| 428 |
+
return (
|
| 429 |
+
"<p style='color:#999;'>Job not found</p>",
|
| 430 |
+
empty, "", ""
|
| 431 |
+
)
|
| 432 |
+
|
| 433 |
+
row = row.iloc[0]
|
| 434 |
+
score = float(row["observed_exposure"])
|
| 435 |
+
rank = int(row["rank"])
|
| 436 |
+
occ_code = str(row["occ_code"])
|
| 437 |
+
|
| 438 |
+
# Percentile
|
| 439 |
+
percentile = (total_jobs - rank) / total_jobs * 100
|
| 440 |
+
|
| 441 |
+
# Find similar jobs (same 2-digit SOC code)
|
| 442 |
+
soc_prefix = occ_code[:2]
|
| 443 |
+
similar = df_jobs[
|
| 444 |
+
(df_jobs["occ_code"].str.startswith(soc_prefix))
|
| 445 |
+
& (df_jobs["title"] != job_title)
|
| 446 |
+
].head(5)
|
| 447 |
+
|
| 448 |
+
# Risk color
|
| 449 |
+
if score >= 0.5:
|
| 450 |
+
score_color = COLORS["danger"]
|
| 451 |
+
elif score >= 0.3:
|
| 452 |
+
score_color = COLORS["warning"]
|
| 453 |
+
elif score >= 0.15:
|
| 454 |
+
score_color = COLORS["info"]
|
| 455 |
+
else:
|
| 456 |
+
score_color = COLORS["success"]
|
| 457 |
+
|
| 458 |
+
# Build cards HTML
|
| 459 |
+
badge = risk_badge(score)
|
| 460 |
+
card1 = styled_card("AI Exposure Score", f"{score*100:.1f}%", f"Rank #{rank} of {total_jobs}", score_color)
|
| 461 |
+
card2 = styled_card("Percentile", f"{percentile:.0f}th", "Higher = more exposed than other jobs", COLORS["primary"])
|
| 462 |
+
card3 = styled_card("Risk Level", badge, "", COLORS["text"])
|
| 463 |
+
|
| 464 |
+
cards_html = f"""
|
| 465 |
+
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin:16px 0;">
|
| 466 |
+
{card1}
|
| 467 |
+
{card2}
|
| 468 |
+
{card3}
|
| 469 |
+
</div>
|
| 470 |
+
<div style="background:white;border-radius:16px;padding:24px;margin-top:16px;
|
| 471 |
+
box-shadow:0 2px 12px rgba(0,0,0,0.06);border:1px solid #f0ece8;">
|
| 472 |
+
<h3 style="margin:0 0 4px 0;color:{COLORS['secondary']};">{job_title}</h3>
|
| 473 |
+
<p style="margin:0;color:{COLORS['muted']};">O*NET Code: {occ_code}</p>
|
| 474 |
+
</div>
|
| 475 |
+
"""
|
| 476 |
+
|
| 477 |
+
# Gauge chart
|
| 478 |
+
gauge = make_gauge(score)
|
| 479 |
+
|
| 480 |
+
# Similar jobs comparison
|
| 481 |
+
if not similar.empty:
|
| 482 |
+
similar_html = "<div style='margin-top:16px;'><h4>Related Occupations</h4><table style='width:100%;border-collapse:collapse;'>"
|
| 483 |
+
similar_html += "<tr style='border-bottom:2px solid #f0ece8;'><th style='text-align:left;padding:8px;'>Occupation</th><th style='text-align:right;padding:8px;'>Exposure</th></tr>"
|
| 484 |
+
for _, s in similar.iterrows():
|
| 485 |
+
bar_width = float(s["observed_exposure"]) * 100
|
| 486 |
+
similar_html += f"""
|
| 487 |
+
<tr style='border-bottom:1px solid #f5f0eb;'>
|
| 488 |
+
<td style='padding:10px 8px;'>{s['title']}</td>
|
| 489 |
+
<td style='padding:10px 8px;text-align:right;'>
|
| 490 |
+
<div style='display:flex;align-items:center;justify-content:flex-end;gap:8px;'>
|
| 491 |
+
<div style='width:80px;height:8px;background:#f0ece8;border-radius:4px;overflow:hidden;'>
|
| 492 |
+
<div style='width:{bar_width:.1f}%;height:100%;background:{COLORS["primary"]};border-radius:4px;'></div>
|
| 493 |
+
</div>
|
| 494 |
+
<span style='font-weight:600;min-width:45px;'>{bar_width:.1f}%</span>
|
| 495 |
+
</div>
|
| 496 |
+
</td>
|
| 497 |
+
</tr>"""
|
| 498 |
+
similar_html += "</table></div>"
|
| 499 |
+
else:
|
| 500 |
+
similar_html = ""
|
| 501 |
+
|
| 502 |
+
# Interpretation
|
| 503 |
+
if score >= 0.5:
|
| 504 |
+
interp = "This occupation has <b>very high</b> AI exposure. A significant portion of its tasks are already being performed with AI assistance. Workers in this field should actively develop AI collaboration skills."
|
| 505 |
+
elif score >= 0.3:
|
| 506 |
+
interp = "This occupation has <b>high</b> AI exposure. Many of its tasks intersect with AI capabilities. Embracing AI tools can significantly boost productivity."
|
| 507 |
+
elif score >= 0.15:
|
| 508 |
+
interp = "This occupation has <b>moderate</b> AI exposure. Some tasks are being augmented by AI, but core functions still require substantial human expertise."
|
| 509 |
+
else:
|
| 510 |
+
interp = "This occupation has <b>low</b> AI exposure. Most of its tasks are not significantly impacted by current AI capabilities, though this may change over time."
|
| 511 |
+
|
| 512 |
+
interp_html = f"""
|
| 513 |
+
<div style="background:#fef9f5;border-left:4px solid {COLORS['primary']};padding:16px 20px;
|
| 514 |
+
border-radius:0 12px 12px 0;margin-top:16px;">
|
| 515 |
+
<strong>Interpretation:</strong> {interp}
|
| 516 |
+
</div>
|
| 517 |
+
"""
|
| 518 |
+
|
| 519 |
+
return cards_html, gauge, similar_html, interp_html
|
| 520 |
+
|
| 521 |
+
except Exception as e:
|
| 522 |
+
import traceback
|
| 523 |
+
err = traceback.format_exc()
|
| 524 |
+
print(f"Error in analyze_job: {err}")
|
| 525 |
empty = go.Figure()
|
| 526 |
apply_layout(empty, height=100)
|
| 527 |
return (
|
| 528 |
+
f"<p style='color:red;'>Error analyzing job: {str(e)}</p>",
|
| 529 |
empty, "", ""
|
| 530 |
)
|
| 531 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 532 |
|
| 533 |
# ============================================================================
|
| 534 |
# KEY METRICS
|