Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -685,6 +685,7 @@ def nav_prev(ranked_dicts: List[Dict[str, Any]], idx: int):
|
|
| 685 |
def nav_next(ranked_dicts: List[Dict[str, Any]], idx: int):
|
| 686 |
return render_single_html(ranked_dicts, _safe_int(idx, 0) + 1)
|
| 687 |
|
|
|
|
| 688 |
# =========================================================
|
| 689 |
# Shortlist export
|
| 690 |
# =========================================================
|
|
@@ -782,7 +783,9 @@ def rank_app(
|
|
| 782 |
continue
|
| 783 |
|
| 784 |
filename = os.path.basename(p)
|
| 785 |
-
contacts_map[filename] =
|
|
|
|
|
|
|
| 786 |
|
| 787 |
chunks = chunk_text_safe(raw)
|
| 788 |
if not chunks:
|
|
@@ -844,7 +847,9 @@ def rank_app(
|
|
| 844 |
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".csv")
|
| 845 |
with open(tmp.name, "w", newline="", encoding="utf-8") as f:
|
| 846 |
w = csv.writer(f)
|
| 847 |
-
w.writerow(
|
|
|
|
|
|
|
| 848 |
for ridx, c in enumerate(ranked, start=1):
|
| 849 |
ci = contacts_map.get(c.filename, {"name": "", "email": "", "phone": ""})
|
| 850 |
local = next((x["local_score"] for x in local_pool if x["filename"] == c.filename), "")
|
|
@@ -865,9 +870,22 @@ def rank_app(
|
|
| 865 |
shortlist_rows = []
|
| 866 |
for ridx, c in enumerate(ranked, start=1):
|
| 867 |
ci = contacts_map.get(c.filename, {"name": "", "email": "", "phone": ""})
|
| 868 |
-
shortlist_rows.append(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 869 |
|
| 870 |
-
shortlist_df = pd.DataFrame(
|
|
|
|
|
|
|
| 871 |
|
| 872 |
elapsed = time.time() - t0
|
| 873 |
meta = (
|
|
@@ -884,8 +902,10 @@ def rank_app(
|
|
| 884 |
|
| 885 |
progress(1.0, desc="Done ✅")
|
| 886 |
return first_html, meta, tmp.name, shortlist_df, "", "", ranked_dicts, idx0, nav
|
|
|
|
|
|
|
| 887 |
# =========================================================
|
| 888 |
-
# SGS CSS (neutral light-grey
|
| 889 |
# =========================================================
|
| 890 |
CUSTOM_CSS = """
|
| 891 |
:root{
|
|
@@ -944,8 +964,7 @@ body:before{
|
|
| 944 |
100%{ background-position: 0% 50%; }
|
| 945 |
}
|
| 946 |
|
| 947 |
-
/*
|
| 948 |
-
Keep text dark always so it's readable even if OS is in dark mode */
|
| 949 |
.gradio-container, .gradio-container *{ color: var(--text) !important; }
|
| 950 |
|
| 951 |
/* Hero */
|
|
@@ -1242,14 +1261,63 @@ button.primary:active, .gradio-container button:active{
|
|
| 1242 |
.checkrow.partial .st{ color: rgba(150,95,10,1) !important; }
|
| 1243 |
.checkrow.miss .st{ color: rgba(160,20,20,1) !important; }
|
| 1244 |
|
| 1245 |
-
/*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1246 |
.gradio-container .file,
|
| 1247 |
.gradio-container .file-upload,
|
| 1248 |
.gradio-container .upload-button,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1249 |
.gradio-container .file *,
|
| 1250 |
.gradio-container .file-upload *,
|
| 1251 |
-
.gradio-container .upload-button *
|
| 1252 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1253 |
}
|
| 1254 |
|
| 1255 |
/* Respect reduced motion */
|
|
@@ -1299,7 +1367,11 @@ with gr.Blocks(title="SGS ATS Candidate Matcher", theme=theme, css=CUSTOM_CSS) a
|
|
| 1299 |
|
| 1300 |
with gr.Row():
|
| 1301 |
jd_file = gr.File(label="Job Description file (PDF/DOCX/TXT)", file_types=[".pdf", ".docx", ".txt"])
|
| 1302 |
-
cv_files = gr.File(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1303 |
|
| 1304 |
with gr.Accordion("Settings", open=False):
|
| 1305 |
must_haves = gr.Textbox(
|
|
@@ -1345,7 +1417,17 @@ with gr.Blocks(title="SGS ATS Candidate Matcher", theme=theme, css=CUSTOM_CSS) a
|
|
| 1345 |
run_btn.click(
|
| 1346 |
fn=rank_app,
|
| 1347 |
inputs=[jd_file, cv_files, must_haves, mask_pii_toggle, show_contacts_toggle],
|
| 1348 |
-
outputs=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1349 |
)
|
| 1350 |
|
| 1351 |
# Navigation (Executive Report): render one card at a time for smooth fullscreen
|
|
|
|
| 685 |
def nav_next(ranked_dicts: List[Dict[str, Any]], idx: int):
|
| 686 |
return render_single_html(ranked_dicts, _safe_int(idx, 0) + 1)
|
| 687 |
|
| 688 |
+
|
| 689 |
# =========================================================
|
| 690 |
# Shortlist export
|
| 691 |
# =========================================================
|
|
|
|
| 783 |
continue
|
| 784 |
|
| 785 |
filename = os.path.basename(p)
|
| 786 |
+
contacts_map[filename] = (
|
| 787 |
+
extract_contact_info(raw) if show_contacts_toggle else {"name": "", "email": "", "phone": ""}
|
| 788 |
+
)
|
| 789 |
|
| 790 |
chunks = chunk_text_safe(raw)
|
| 791 |
if not chunks:
|
|
|
|
| 847 |
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".csv")
|
| 848 |
with open(tmp.name, "w", newline="", encoding="utf-8") as f:
|
| 849 |
w = csv.writer(f)
|
| 850 |
+
w.writerow(
|
| 851 |
+
["Rank", "Filename", "FinalScore(0-100)", "FitLevel", "Name", "Email", "Phone", "Summary", "LocalScore"]
|
| 852 |
+
)
|
| 853 |
for ridx, c in enumerate(ranked, start=1):
|
| 854 |
ci = contacts_map.get(c.filename, {"name": "", "email": "", "phone": ""})
|
| 855 |
local = next((x["local_score"] for x in local_pool if x["filename"] == c.filename), "")
|
|
|
|
| 870 |
shortlist_rows = []
|
| 871 |
for ridx, c in enumerate(ranked, start=1):
|
| 872 |
ci = contacts_map.get(c.filename, {"name": "", "email": "", "phone": ""})
|
| 873 |
+
shortlist_rows.append(
|
| 874 |
+
[
|
| 875 |
+
False,
|
| 876 |
+
ridx,
|
| 877 |
+
c.filename,
|
| 878 |
+
round(float(c.final_score), 2),
|
| 879 |
+
c.fit_level,
|
| 880 |
+
ci.get("name", ""),
|
| 881 |
+
ci.get("email", ""),
|
| 882 |
+
ci.get("phone", ""),
|
| 883 |
+
]
|
| 884 |
+
)
|
| 885 |
|
| 886 |
+
shortlist_df = pd.DataFrame(
|
| 887 |
+
shortlist_rows, columns=["Shortlisted", "Rank", "Filename", "Score", "Fit", "Name", "Email", "Phone"]
|
| 888 |
+
)
|
| 889 |
|
| 890 |
elapsed = time.time() - t0
|
| 891 |
meta = (
|
|
|
|
| 902 |
|
| 903 |
progress(1.0, desc="Done ✅")
|
| 904 |
return first_html, meta, tmp.name, shortlist_df, "", "", ranked_dicts, idx0, nav
|
| 905 |
+
|
| 906 |
+
|
| 907 |
# =========================================================
|
| 908 |
+
# SGS CSS (neutral light-grey + visible borders + file uploader fixed)
|
| 909 |
# =========================================================
|
| 910 |
CUSTOM_CSS = """
|
| 911 |
:root{
|
|
|
|
| 964 |
100%{ background-position: 0% 50%; }
|
| 965 |
}
|
| 966 |
|
| 967 |
+
/* Keep text dark always (theme-proof) */
|
|
|
|
| 968 |
.gradio-container, .gradio-container *{ color: var(--text) !important; }
|
| 969 |
|
| 970 |
/* Hero */
|
|
|
|
| 1261 |
.checkrow.partial .st{ color: rgba(150,95,10,1) !important; }
|
| 1262 |
.checkrow.miss .st{ color: rgba(160,20,20,1) !important; }
|
| 1263 |
|
| 1264 |
+
/* =========================================================
|
| 1265 |
+
File uploader: force readable label/filename ALWAYS
|
| 1266 |
+
(fixes the "blue header text = same color" issue)
|
| 1267 |
+
========================================================= */
|
| 1268 |
+
|
| 1269 |
+
/* Make uploader surface light-grey with strong border */
|
| 1270 |
.gradio-container .file,
|
| 1271 |
.gradio-container .file-upload,
|
| 1272 |
.gradio-container .upload-button,
|
| 1273 |
+
.gradio-container .file-upload > div,
|
| 1274 |
+
.gradio-container [data-testid="file"]{
|
| 1275 |
+
background: rgba(245,247,250,.92) !important;
|
| 1276 |
+
border: 1.4px solid rgba(17,24,39,.28) !important;
|
| 1277 |
+
border-radius: 16px !important;
|
| 1278 |
+
box-shadow: 0 12px 24px rgba(2,6,23,.10) !important;
|
| 1279 |
+
}
|
| 1280 |
+
|
| 1281 |
+
/* Force ALL text inside uploader to dark */
|
| 1282 |
.gradio-container .file *,
|
| 1283 |
.gradio-container .file-upload *,
|
| 1284 |
+
.gradio-container .upload-button *,
|
| 1285 |
+
.gradio-container [data-testid="file"] *{
|
| 1286 |
+
color: #111827 !important;
|
| 1287 |
+
}
|
| 1288 |
+
|
| 1289 |
+
/* Normalize any internal header strip that turns blue */
|
| 1290 |
+
.gradio-container .file-upload .file-title,
|
| 1291 |
+
.gradio-container .file-upload .file-label,
|
| 1292 |
+
.gradio-container .file-upload .label,
|
| 1293 |
+
.gradio-container .file-upload .wrap,
|
| 1294 |
+
.gradio-container .file-upload .header,
|
| 1295 |
+
.gradio-container [data-testid="file"] .label{
|
| 1296 |
+
background: rgba(245,247,250,.92) !important;
|
| 1297 |
+
border-bottom: 1.4px solid rgba(17,24,39,.20) !important;
|
| 1298 |
+
}
|
| 1299 |
+
|
| 1300 |
+
/* Filename emphasis */
|
| 1301 |
+
.gradio-container .file-upload .file-name,
|
| 1302 |
+
.gradio-container .file-upload .filename,
|
| 1303 |
+
.gradio-container [data-testid="file"] .file-name{
|
| 1304 |
+
font-weight: 900 !important;
|
| 1305 |
+
}
|
| 1306 |
+
|
| 1307 |
+
/* Close / clear button visible */
|
| 1308 |
+
.gradio-container .file-upload button,
|
| 1309 |
+
.gradio-container [data-testid="file"] button{
|
| 1310 |
+
background: rgba(255,255,255,.85) !important;
|
| 1311 |
+
border: 1.2px solid rgba(17,24,39,.28) !important;
|
| 1312 |
+
color: #111827 !important;
|
| 1313 |
+
}
|
| 1314 |
+
|
| 1315 |
+
/* Hover feedback */
|
| 1316 |
+
.gradio-container .file:hover,
|
| 1317 |
+
.gradio-container .file-upload:hover,
|
| 1318 |
+
.gradio-container [data-testid="file"]:hover{
|
| 1319 |
+
border-color: rgba(17,24,39,.36) !important;
|
| 1320 |
+
box-shadow: 0 16px 32px rgba(2,6,23,.12) !important;
|
| 1321 |
}
|
| 1322 |
|
| 1323 |
/* Respect reduced motion */
|
|
|
|
| 1367 |
|
| 1368 |
with gr.Row():
|
| 1369 |
jd_file = gr.File(label="Job Description file (PDF/DOCX/TXT)", file_types=[".pdf", ".docx", ".txt"])
|
| 1370 |
+
cv_files = gr.File(
|
| 1371 |
+
label=f"Upload CVs (max {MAX_CV_UPLOADS})",
|
| 1372 |
+
file_count="multiple",
|
| 1373 |
+
file_types=[".pdf", ".docx", ".txt"],
|
| 1374 |
+
)
|
| 1375 |
|
| 1376 |
with gr.Accordion("Settings", open=False):
|
| 1377 |
must_haves = gr.Textbox(
|
|
|
|
| 1417 |
run_btn.click(
|
| 1418 |
fn=rank_app,
|
| 1419 |
inputs=[jd_file, cv_files, must_haves, mask_pii_toggle, show_contacts_toggle],
|
| 1420 |
+
outputs=[
|
| 1421 |
+
report_html,
|
| 1422 |
+
meta_md,
|
| 1423 |
+
export_full,
|
| 1424 |
+
shortlist_df,
|
| 1425 |
+
export_shortlist_msg,
|
| 1426 |
+
email_list,
|
| 1427 |
+
ranked_state,
|
| 1428 |
+
idx_state,
|
| 1429 |
+
nav_text,
|
| 1430 |
+
],
|
| 1431 |
)
|
| 1432 |
|
| 1433 |
# Navigation (Executive Report): render one card at a time for smooth fullscreen
|