saitejatirunagari Claude Opus 4.8 commited on
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 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
- "JD Match": j.get("jd_match", j.get("ats_score_after", "")),
2099
  "Independent": j.get("independent_jd_match", ""),
2100
  "Readability": j.get("ats_readability", ""),
2101
- "Risk": "review" if j.get("review_terms") else "low",
 
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()