Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -58,8 +58,8 @@ RED_FLAGS = [
|
|
| 58 |
("benefits clearly mentioned", -4, ["health insurance", "paid leave", "meal vouchers", "transport", "benefits include", "profit-sharing"]),
|
| 59 |
]
|
| 60 |
|
| 61 |
-
CHART_PALETTE = ["#
|
| 62 |
-
"#
|
| 63 |
|
| 64 |
# =========================================================
|
| 65 |
# DATA LOADING
|
|
@@ -135,7 +135,7 @@ def analyze_job(text: str) -> Tuple[str, int, str, go.Figure]:
|
|
| 135 |
cdf = pd.DataFrame(detected, columns=["Signal", "Weight"])
|
| 136 |
cdf["Type"] = cdf["Weight"].apply(lambda w: "Red flag" if w > 0 else "Positive")
|
| 137 |
fig = px.bar(cdf, x="Weight", y="Signal", color="Type", orientation="h",
|
| 138 |
-
color_discrete_map={"Red flag": "#
|
| 139 |
title="Signal breakdown")
|
| 140 |
fig.update_layout(**_styled_layout(height=420, showlegend=True))
|
| 141 |
else:
|
|
@@ -150,15 +150,22 @@ def analyze_job(text: str) -> Tuple[str, int, str, go.Figure]:
|
|
| 150 |
|
| 151 |
def _styled_layout(**kwargs) -> dict:
|
| 152 |
defaults = dict(
|
| 153 |
-
template="
|
| 154 |
-
paper_bgcolor="#
|
| 155 |
-
plot_bgcolor="#
|
| 156 |
-
font=dict(family="
|
| 157 |
margin=dict(l=60, r=20, t=70, b=70),
|
| 158 |
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1,
|
| 159 |
-
bgcolor="rgba(
|
| 160 |
-
bordercolor="#
|
| 161 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
)
|
| 163 |
defaults.update(kwargs)
|
| 164 |
return defaults
|
|
@@ -167,10 +174,12 @@ def _styled_layout(**kwargs) -> dict:
|
|
| 167 |
def _empty_chart(title: str) -> go.Figure:
|
| 168 |
fig = go.Figure()
|
| 169 |
fig.update_layout(
|
| 170 |
-
title=title, height=420, template="
|
| 171 |
-
paper_bgcolor="#
|
| 172 |
-
|
| 173 |
-
|
|
|
|
|
|
|
| 174 |
)
|
| 175 |
return fig
|
| 176 |
|
|
@@ -189,7 +198,7 @@ def build_flag_frequency_chart() -> go.Figure:
|
|
| 189 |
fig = go.Figure(go.Bar(
|
| 190 |
y=counts.index[::-1], x=counts.values[::-1], orientation="h",
|
| 191 |
marker=dict(color=counts.values[::-1],
|
| 192 |
-
colorscale=[[0, "#
|
| 193 |
hovertemplate="<b>%{y}</b><br>Detected in %{x} jobs<extra></extra>",
|
| 194 |
))
|
| 195 |
fig.update_layout(**_styled_layout(
|
|
@@ -203,7 +212,7 @@ def build_risk_distribution_chart() -> go.Figure:
|
|
| 203 |
if DF.empty or "Risk Level" not in DF.columns:
|
| 204 |
return _empty_chart("Dataset not loaded")
|
| 205 |
counts = DF["Risk Level"].value_counts()
|
| 206 |
-
colors = {"Low": "#
|
| 207 |
fig = go.Figure(go.Pie(
|
| 208 |
labels=counts.index, values=counts.values,
|
| 209 |
marker=dict(colors=[colors.get(l, "#888") for l in counts.index]),
|
|
@@ -219,7 +228,8 @@ def build_score_distribution_chart() -> go.Figure:
|
|
| 219 |
return _empty_chart("Dataset not loaded")
|
| 220 |
scores = DF["Score"].dropna()
|
| 221 |
fig = go.Figure(go.Histogram(
|
| 222 |
-
x=scores, nbinsx=15, marker_color="#
|
|
|
|
| 223 |
hovertemplate="Score range: %{x}<br>Jobs: %{y}<extra></extra>",
|
| 224 |
))
|
| 225 |
fig.update_layout(**_styled_layout(
|
|
@@ -236,11 +246,14 @@ def build_score_distribution_chart() -> go.Figure:
|
|
| 236 |
|
| 237 |
def render_kpi_cards() -> str:
|
| 238 |
if DF.empty:
|
| 239 |
-
return ('<div style="background:#
|
| 240 |
-
'border-radius:
|
| 241 |
-
'<div style="font-
|
| 242 |
-
'
|
| 243 |
-
'
|
|
|
|
|
|
|
|
|
|
| 244 |
'</div></div>')
|
| 245 |
|
| 246 |
total_jobs = len(DF)
|
|
@@ -254,37 +267,38 @@ def render_kpi_cards() -> str:
|
|
| 254 |
all_flags.extend(label for label, _ in extract_flag_labels(str(cell)))
|
| 255 |
top_flag = pd.Series(all_flags).value_counts().index[0] if all_flags else "β"
|
| 256 |
|
| 257 |
-
def card(label, value,
|
| 258 |
return f"""
|
| 259 |
-
<div style="
|
| 260 |
-
padding:
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
<div style="color:#94a3b8;font-size:11px;font-weight:600;
|
| 266 |
-
text-transform:uppercase;letter-spacing:0.08em;margin-bottom:12px;">
|
| 267 |
{label}
|
| 268 |
</div>
|
| 269 |
-
<div style="color:#
|
| 270 |
-
letter-spacing:-0.03em;margin-bottom:
|
| 271 |
-
font-family:'
|
| 272 |
{value}
|
| 273 |
</div>
|
| 274 |
-
<div style="
|
| 275 |
-
|
|
|
|
|
|
|
|
|
|
| 276 |
</div>
|
| 277 |
</div>"""
|
| 278 |
|
| 279 |
cards = [
|
| 280 |
-
card("Jobs
|
| 281 |
-
card("Avg
|
| 282 |
-
card("High
|
| 283 |
-
card("Top
|
| 284 |
-
top_flag if top_flag != "β" else "
|
| 285 |
]
|
| 286 |
-
return ('<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(
|
| 287 |
-
'gap:
|
| 288 |
|
| 289 |
|
| 290 |
# =========================================================
|
|
@@ -415,8 +429,9 @@ def load_css() -> str:
|
|
| 415 |
with gr.Blocks(title="Job Risk Analyzer β CS1 Group 14") as demo:
|
| 416 |
|
| 417 |
gr.Markdown(
|
| 418 |
-
"# Job
|
| 419 |
-
"
|
|
|
|
| 420 |
elem_id="escp_title",
|
| 421 |
)
|
| 422 |
|
|
|
|
| 58 |
("benefits clearly mentioned", -4, ["health insurance", "paid leave", "meal vouchers", "transport", "benefits include", "profit-sharing"]),
|
| 59 |
]
|
| 60 |
|
| 61 |
+
CHART_PALETTE = ["#34d399", "#60a5fa", "#f472b6", "#fbbf24", "#a78bfa",
|
| 62 |
+
"#22d3ee", "#fb7185", "#84cc16", "#f97316", "#e879f9"]
|
| 63 |
|
| 64 |
# =========================================================
|
| 65 |
# DATA LOADING
|
|
|
|
| 135 |
cdf = pd.DataFrame(detected, columns=["Signal", "Weight"])
|
| 136 |
cdf["Type"] = cdf["Weight"].apply(lambda w: "Red flag" if w > 0 else "Positive")
|
| 137 |
fig = px.bar(cdf, x="Weight", y="Signal", color="Type", orientation="h",
|
| 138 |
+
color_discrete_map={"Red flag": "#ef4444", "Positive": "#34d399"},
|
| 139 |
title="Signal breakdown")
|
| 140 |
fig.update_layout(**_styled_layout(height=420, showlegend=True))
|
| 141 |
else:
|
|
|
|
| 150 |
|
| 151 |
def _styled_layout(**kwargs) -> dict:
|
| 152 |
defaults = dict(
|
| 153 |
+
template="plotly_dark",
|
| 154 |
+
paper_bgcolor="#131313",
|
| 155 |
+
plot_bgcolor="#131313",
|
| 156 |
+
font=dict(family="Geist, system-ui, -apple-system, sans-serif", color="#ededed", size=12),
|
| 157 |
margin=dict(l=60, r=20, t=70, b=70),
|
| 158 |
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1,
|
| 159 |
+
bgcolor="rgba(19,19,19,0.9)",
|
| 160 |
+
bordercolor="#262626", borderwidth=1,
|
| 161 |
+
font=dict(color="#a1a1a1", size=11)),
|
| 162 |
+
title=dict(font=dict(size=14, color="#ededed", family="Geist, system-ui, sans-serif")),
|
| 163 |
+
xaxis=dict(gridcolor="#1f1f1f", zerolinecolor="#262626",
|
| 164 |
+
tickfont=dict(color="#a1a1a1", size=11),
|
| 165 |
+
title=dict(font=dict(color="#a1a1a1", size=12))),
|
| 166 |
+
yaxis=dict(gridcolor="#1f1f1f", zerolinecolor="#262626",
|
| 167 |
+
tickfont=dict(color="#a1a1a1", size=11),
|
| 168 |
+
title=dict(font=dict(color="#a1a1a1", size=12))),
|
| 169 |
)
|
| 170 |
defaults.update(kwargs)
|
| 171 |
return defaults
|
|
|
|
| 174 |
def _empty_chart(title: str) -> go.Figure:
|
| 175 |
fig = go.Figure()
|
| 176 |
fig.update_layout(
|
| 177 |
+
title=title, height=420, template="plotly_dark",
|
| 178 |
+
paper_bgcolor="#131313",
|
| 179 |
+
plot_bgcolor="#131313",
|
| 180 |
+
font=dict(color="#ededed", family="Geist, system-ui, sans-serif"),
|
| 181 |
+
annotations=[dict(text="(no data available)", x=0.5, y=0.5, xref="paper", yref="paper",
|
| 182 |
+
showarrow=False, font=dict(size=13, color="#666"))],
|
| 183 |
)
|
| 184 |
return fig
|
| 185 |
|
|
|
|
| 198 |
fig = go.Figure(go.Bar(
|
| 199 |
y=counts.index[::-1], x=counts.values[::-1], orientation="h",
|
| 200 |
marker=dict(color=counts.values[::-1],
|
| 201 |
+
colorscale=[[0, "#0d4d3a"], [1, "#34d399"]]),
|
| 202 |
hovertemplate="<b>%{y}</b><br>Detected in %{x} jobs<extra></extra>",
|
| 203 |
))
|
| 204 |
fig.update_layout(**_styled_layout(
|
|
|
|
| 212 |
if DF.empty or "Risk Level" not in DF.columns:
|
| 213 |
return _empty_chart("Dataset not loaded")
|
| 214 |
counts = DF["Risk Level"].value_counts()
|
| 215 |
+
colors = {"Low": "#34d399", "Medium": "#f59e0b", "High": "#ef4444"}
|
| 216 |
fig = go.Figure(go.Pie(
|
| 217 |
labels=counts.index, values=counts.values,
|
| 218 |
marker=dict(colors=[colors.get(l, "#888") for l in counts.index]),
|
|
|
|
| 228 |
return _empty_chart("Dataset not loaded")
|
| 229 |
scores = DF["Score"].dropna()
|
| 230 |
fig = go.Figure(go.Histogram(
|
| 231 |
+
x=scores, nbinsx=15, marker_color="#34d399",
|
| 232 |
+
marker_line_color="#0d4d3a", marker_line_width=1,
|
| 233 |
hovertemplate="Score range: %{x}<br>Jobs: %{y}<extra></extra>",
|
| 234 |
))
|
| 235 |
fig.update_layout(**_styled_layout(
|
|
|
|
| 246 |
|
| 247 |
def render_kpi_cards() -> str:
|
| 248 |
if DF.empty:
|
| 249 |
+
return ('<div style="background:#131313;padding:32px;text-align:center;'
|
| 250 |
+
'border-radius:12px;border:1px solid #262626;">'
|
| 251 |
+
'<div style="font-family:\'Geist Mono\',monospace;font-size:11px;'
|
| 252 |
+
'color:#666;letter-spacing:0.08em;text-transform:uppercase;margin-bottom:12px;">No Data</div>'
|
| 253 |
+
'<div style="color:#a1a1a1;font-size:14px;">'
|
| 254 |
+
'Upload <code style="background:#161616;color:#34d399;padding:2px 6px;border-radius:4px;'
|
| 255 |
+
'font-family:\'Geist Mono\',monospace;font-size:0.85em;border:1px solid #1f1f1f;">'
|
| 256 |
+
'job_description_data.xlsx</code> to populate metrics.'
|
| 257 |
'</div></div>')
|
| 258 |
|
| 259 |
total_jobs = len(DF)
|
|
|
|
| 267 |
all_flags.extend(label for label, _ in extract_flag_labels(str(cell)))
|
| 268 |
top_flag = pd.Series(all_flags).value_counts().index[0] if all_flags else "β"
|
| 269 |
|
| 270 |
+
def card(label, value, delta_text, accent_color="#34d399"):
|
| 271 |
return f"""
|
| 272 |
+
<div style="background:#131313;border:1px solid #262626;border-radius:12px;
|
| 273 |
+
padding:20px 22px;position:relative;overflow:hidden;
|
| 274 |
+
transition:border-color 0.15s, background 0.15s;">
|
| 275 |
+
<div style="font-family:'Geist Mono','SF Mono',monospace;
|
| 276 |
+
color:#666;font-size:11px;font-weight:500;
|
| 277 |
+
text-transform:uppercase;letter-spacing:0.08em;margin-bottom:14px;">
|
|
|
|
|
|
|
| 278 |
{label}
|
| 279 |
</div>
|
| 280 |
+
<div style="color:#ededed;font-size:32px;font-weight:600;line-height:1;
|
| 281 |
+
letter-spacing:-0.03em;margin-bottom:10px;
|
| 282 |
+
font-family:'Geist',-apple-system,system-ui,sans-serif;">
|
| 283 |
{value}
|
| 284 |
</div>
|
| 285 |
+
<div style="display:flex;align-items:center;gap:6px;
|
| 286 |
+
font-family:'Geist Mono',monospace;font-size:11px;color:#a1a1a1;">
|
| 287 |
+
<span style="display:inline-block;width:6px;height:6px;border-radius:50%;
|
| 288 |
+
background:{accent_color};box-shadow:0 0 8px {accent_color};"></span>
|
| 289 |
+
<span>{delta_text}</span>
|
| 290 |
</div>
|
| 291 |
</div>"""
|
| 292 |
|
| 293 |
cards = [
|
| 294 |
+
card("Total.Jobs", f"{total_jobs}", "real labeled postings", "#34d399"),
|
| 295 |
+
card("Avg.Score", f"{avg_score:.1f}", "weighted across dataset", "#60a5fa"),
|
| 296 |
+
card("High.Risk %", f"{high_pct:.0f}%", f"{risk_counts.get('High', 0)} postings flagged", "#ef4444"),
|
| 297 |
+
card("Top.Signal", top_flag.split(' ')[0].title() if top_flag != "β" else "β",
|
| 298 |
+
top_flag if top_flag != "β" else "no data", "#f59e0b"),
|
| 299 |
]
|
| 300 |
+
return ('<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));'
|
| 301 |
+
'gap:12px;margin-bottom:32px;">' + "".join(cards) + "</div>")
|
| 302 |
|
| 303 |
|
| 304 |
# =========================================================
|
|
|
|
| 429 |
with gr.Blocks(title="Job Risk Analyzer β CS1 Group 14") as demo:
|
| 430 |
|
| 431 |
gr.Markdown(
|
| 432 |
+
"# Job Risk Analyzer\n"
|
| 433 |
+
"Detect hidden risk patterns in job postings using a weighted signal taxonomy "
|
| 434 |
+
"calibrated on 47 real labeled descriptions.",
|
| 435 |
elem_id="escp_title",
|
| 436 |
)
|
| 437 |
|