Spaces:
Sleeping
Sleeping
Commit Β·
cc74dfc
1
Parent(s): 7b41b0f
feat(ui): batch risk column + bulk vault controls; isolate test vaults
Browse files- Batch table: add Risk Level (LOW/MEDIUM/HIGH) + Review Terms count; rename to
Internal/Independent; caption explains HIGH = needs confirmation.
- Bulk vault controls: confirm "I have these" (β safe in all future resumes) or
block "never use these". Only user confirmation upgrades a term to safe β the
system never auto-promotes (vault source labels enforce this).
- Tests: isolate verify_anticheat / verify_90_pipeline to temp vault paths so
runs are deterministic and never pollute the real per-user vault.
All 3 regression suites pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- scripts/verify_90_pipeline.py +4 -0
- scripts/verify_anticheat.py +5 -0
- ui.py +48 -3
scripts/verify_90_pipeline.py
CHANGED
|
@@ -11,6 +11,10 @@ sys.path.insert(0, os.path.abspath("."))
|
|
| 11 |
|
| 12 |
from src.resume_customizer import ResumeCustomizer
|
| 13 |
from src.llm_client import LLMClient
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
src_pdf = r"C:\Users\Nxtwave\Desktop\resume\Saiteja_Tirunagari_Resume A 26 - Copy.pdf"
|
| 16 |
dst = "data/resume/resume.pdf"
|
|
|
|
| 11 |
|
| 12 |
from src.resume_customizer import ResumeCustomizer
|
| 13 |
from src.llm_client import LLMClient
|
| 14 |
+
import src.candidate_vault as _cv
|
| 15 |
+
_cv._VAULT_PATH = "data/_test_vault_90.json"
|
| 16 |
+
if os.path.exists(_cv._VAULT_PATH):
|
| 17 |
+
os.remove(_cv._VAULT_PATH)
|
| 18 |
|
| 19 |
src_pdf = r"C:\Users\Nxtwave\Desktop\resume\Saiteja_Tirunagari_Resume A 26 - Copy.pdf"
|
| 20 |
dst = "data/resume/resume.pdf"
|
scripts/verify_anticheat.py
CHANGED
|
@@ -22,6 +22,11 @@ from src.resume_customizer import ResumeCustomizer, _read_docx_text
|
|
| 22 |
from src.llm_client import LLMClient
|
| 23 |
from src.ats_validator import validate_resume
|
| 24 |
from src.resume_parser_v2 import parse_resume_pdf
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
src_pdf = r"C:\Users\Nxtwave\Desktop\resume\Saiteja_Tirunagari_Resume A 26 - Copy.pdf"
|
| 27 |
dst = "data/resume/resume.pdf"
|
|
|
|
| 22 |
from src.llm_client import LLMClient
|
| 23 |
from src.ats_validator import validate_resume
|
| 24 |
from src.resume_parser_v2 import parse_resume_pdf
|
| 25 |
+
# Isolate the vault so prior user confirmations don't change anti-cheat results.
|
| 26 |
+
import src.candidate_vault as _cv
|
| 27 |
+
_cv._VAULT_PATH = "data/_test_vault_anticheat.json"
|
| 28 |
+
if os.path.exists(_cv._VAULT_PATH):
|
| 29 |
+
os.remove(_cv._VAULT_PATH)
|
| 30 |
|
| 31 |
src_pdf = r"C:\Users\Nxtwave\Desktop\resume\Saiteja_Tirunagari_Resume A 26 - Copy.pdf"
|
| 32 |
dst = "data/resume/resume.pdf"
|
ui.py
CHANGED
|
@@ -2086,31 +2086,76 @@ if not st.session_state.running and st.session_state.results:
|
|
| 2086 |
_ORDER = {"READY_90_PLUS": 0, "READY_90_PLUS_REVIEW_RECOMMENDED": 1,
|
| 2087 |
"NEEDS_USER_INPUT": 2, "NEEDS_REPAIR": 3,
|
| 2088 |
"NOT_ELIGIBLE_LOW_FIT": 4, "PARSE_FAILED": 5}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2089 |
_rows = []
|
| 2090 |
for j in sorted(st.session_state.results,
|
| 2091 |
key=lambda x: _ORDER.get(x.get("status", ""), 3)):
|
|
|
|
|
|
|
|
|
|
| 2092 |
_rows.append({
|
| 2093 |
"Job Title": (j.get("title", "") or "")[:40],
|
| 2094 |
"Company": (j.get("company", "") or "")[:24],
|
| 2095 |
"Platform": j.get("platform", j.get("source", "")),
|
| 2096 |
"Status": j.get("status", ""),
|
| 2097 |
"Quality": j.get("quality_flag", ""),
|
| 2098 |
-
"
|
| 2099 |
"Independent": j.get("independent_jd_match", ""),
|
| 2100 |
"Readability": j.get("ats_readability", ""),
|
| 2101 |
-
"Risk":
|
|
|
|
| 2102 |
"Download": "β
" if j.get("download_allowed") else "β",
|
| 2103 |
})
|
| 2104 |
with st.expander(f"π Batch summary β {len(_rows)} jobs (ranked by readiness)", expanded=True):
|
| 2105 |
_ready = sum(1 for r in _rows if r["Download"] == "β
")
|
| 2106 |
st.caption(f"{_ready}/{len(_rows)} ready to apply (both internal & "
|
| 2107 |
-
f"independent β₯ 90). Independent = anti-circular evidence-based score."
|
|
|
|
| 2108 |
try:
|
| 2109 |
import pandas as _pd
|
| 2110 |
st.dataframe(_pd.DataFrame(_rows), use_container_width=True, hide_index=True)
|
| 2111 |
except Exception:
|
| 2112 |
st.table(_rows)
|
| 2113 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2114 |
if st.session_state.running:
|
| 2115 |
time.sleep(0.8)
|
| 2116 |
st.rerun()
|
|
|
|
| 2086 |
_ORDER = {"READY_90_PLUS": 0, "READY_90_PLUS_REVIEW_RECOMMENDED": 1,
|
| 2087 |
"NEEDS_USER_INPUT": 2, "NEEDS_REPAIR": 3,
|
| 2088 |
"NOT_ELIGIBLE_LOW_FIT": 4, "PARSE_FAILED": 5}
|
| 2089 |
+
def _risk_level(j):
|
| 2090 |
+
q = j.get("quality_flag", "")
|
| 2091 |
+
if j.get("status") == "NEEDS_USER_INPUT":
|
| 2092 |
+
return "HIGH"
|
| 2093 |
+
if q == "REVIEW_REQUIRED_90_PLUS" or j.get("review_terms"):
|
| 2094 |
+
return "MEDIUM"
|
| 2095 |
+
if q in ("WEAK_90_INTERNAL_ONLY",):
|
| 2096 |
+
return "HIGH"
|
| 2097 |
+
return "LOW"
|
| 2098 |
_rows = []
|
| 2099 |
for j in sorted(st.session_state.results,
|
| 2100 |
key=lambda x: _ORDER.get(x.get("status", ""), 3)):
|
| 2101 |
+
_nrev = len(j.get("review_terms", []) or []) + \
|
| 2102 |
+
len(j.get("_v2_report", {}).get("high_risk_terms_for_confirmation", []) or []) \
|
| 2103 |
+
if isinstance(j.get("_v2_report"), dict) else len(j.get("review_terms", []) or [])
|
| 2104 |
_rows.append({
|
| 2105 |
"Job Title": (j.get("title", "") or "")[:40],
|
| 2106 |
"Company": (j.get("company", "") or "")[:24],
|
| 2107 |
"Platform": j.get("platform", j.get("source", "")),
|
| 2108 |
"Status": j.get("status", ""),
|
| 2109 |
"Quality": j.get("quality_flag", ""),
|
| 2110 |
+
"Internal": j.get("jd_match", j.get("ats_score_after", "")),
|
| 2111 |
"Independent": j.get("independent_jd_match", ""),
|
| 2112 |
"Readability": j.get("ats_readability", ""),
|
| 2113 |
+
"Risk": _risk_level(j),
|
| 2114 |
+
"Review Terms": _nrev,
|
| 2115 |
"Download": "β
" if j.get("download_allowed") else "β",
|
| 2116 |
})
|
| 2117 |
with st.expander(f"π Batch summary β {len(_rows)} jobs (ranked by readiness)", expanded=True):
|
| 2118 |
_ready = sum(1 for r in _rows if r["Download"] == "β
")
|
| 2119 |
st.caption(f"{_ready}/{len(_rows)} ready to apply (both internal & "
|
| 2120 |
+
f"independent β₯ 90). Independent = anti-circular evidence-based score. "
|
| 2121 |
+
f"Risk HIGH = needs your confirmation before applying.")
|
| 2122 |
try:
|
| 2123 |
import pandas as _pd
|
| 2124 |
st.dataframe(_pd.DataFrame(_rows), use_container_width=True, hide_index=True)
|
| 2125 |
except Exception:
|
| 2126 |
st.table(_rows)
|
| 2127 |
|
| 2128 |
+
# ββ Bulk vault controls (confirm/block terms across all future jobs) ββ
|
| 2129 |
+
with st.expander("π Manage skills vault (confirm or block terms)", expanded=False):
|
| 2130 |
+
st.caption("Confirmed terms are treated as fully safe in every future "
|
| 2131 |
+
"resume; blocked terms are never used. Only YOUR confirmation "
|
| 2132 |
+
"upgrades a term to safe β the system never auto-promotes.")
|
| 2133 |
+
vc1, vc2 = st.columns(2)
|
| 2134 |
+
with vc1:
|
| 2135 |
+
_confirm_in = st.text_input(
|
| 2136 |
+
"β
Confirm I have these (comma-separated)",
|
| 2137 |
+
key="vault_confirm_in",
|
| 2138 |
+
placeholder="e.g. SIEM, SOAR, Tableau")
|
| 2139 |
+
if st.button("Confirm to vault", key="vault_confirm_btn") and _confirm_in.strip():
|
| 2140 |
+
try:
|
| 2141 |
+
from src.candidate_vault import confirm_terms
|
| 2142 |
+
confirm_terms([t.strip() for t in _confirm_in.split(",") if t.strip()], confirmed=True)
|
| 2143 |
+
st.success("Confirmed β these are now safe for future resumes.")
|
| 2144 |
+
except Exception as _e:
|
| 2145 |
+
st.error(f"Could not update vault: {_e}")
|
| 2146 |
+
with vc2:
|
| 2147 |
+
_block_in = st.text_input(
|
| 2148 |
+
"π« Never use these (comma-separated)",
|
| 2149 |
+
key="vault_block_in",
|
| 2150 |
+
placeholder="e.g. Kubernetes, CISSP")
|
| 2151 |
+
if st.button("Block in vault", key="vault_block_btn") and _block_in.strip():
|
| 2152 |
+
try:
|
| 2153 |
+
from src.candidate_vault import confirm_terms
|
| 2154 |
+
confirm_terms([t.strip() for t in _block_in.split(",") if t.strip()], confirmed=False)
|
| 2155 |
+
st.success("Blocked β these will never be added to resumes.")
|
| 2156 |
+
except Exception as _e:
|
| 2157 |
+
st.error(f"Could not update vault: {_e}")
|
| 2158 |
+
|
| 2159 |
if st.session_state.running:
|
| 2160 |
time.sleep(0.8)
|
| 2161 |
st.rerun()
|