Download app.js from Airmanv/OpenJobs: direct link, hf CLI and curl.
- Browser
- Download file 14.7 kB
-
https://huggingface.co/spaces/Airmanv/OpenJobs/resolve/main/app.js
- Command line
-
hf download hf://spaces/Airmanv/OpenJobs/app.js
-
curl -L -o app.js https://huggingface.co/spaces/Airmanv/OpenJobs/resolve/main/app.js
14.7 kB
| // BOS Staffing Market Scanner | |
| // Based on the open-jobs / JobScream local-first client (github.com/elliottdehn/open-jobs). | |
| // Flow: requisition text -> /embed -> descend the cluster manifest -> keep/skip clusters -> | |
| // live logistic regression over the downloaded pool -> ranked openings, company rollup, CSV. | |
| // Everything after the single /embed call runs in this tab. | |
| const API = "https://backend.dehnbostele.workers.dev"; | |
| const $ = (s) => document.querySelector(s); | |
| const state = { vec: null, manifest: null, cent: null, cards: [], decided: new Map(), pool: [], labels: new Map(), w: null, b: 0, shown: 0, dl: { done: 0, total: 0 }, view: "jobs" }; | |
| function show(step) { | |
| document.querySelectorAll(".step").forEach((e) => e.classList.remove("active")); | |
| $(step).classList.add("active"); | |
| const n = Number(step.slice(2)); | |
| for (let i = 1; i <= 3; i++) { | |
| const r = $("#r" + i); | |
| r.className = "rn" + (i === n ? " on" : i < n ? " done" : ""); | |
| } | |
| window.scrollTo({ top: 0, behavior: "smooth" }); | |
| } | |
| function norm(v) { let s = 0; for (const x of v) s += x * x; s = Math.sqrt(s) || 1; return Float32Array.from(v, (x) => x / s); } | |
| function vec(j) { const s = atob(j.v); const buf = new ArrayBuffer(s.length); const u = new Uint8Array(buf); for (let i = 0; i < s.length; i++) u[i] = s.charCodeAt(i); return new Float32Array(buf); } | |
| function dot(a, b) { let s = 0; for (let i = 0; i < a.length; i++) s += a[i] * b[i]; return s; } | |
| function esc(s) { return String(s ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c])); } | |
| // Employer labels come from whatever the board exposes. Hex client keys and GUIDs carry no name, | |
| // so those postings are labelled honestly and the recruiter clicks through. | |
| const HEX = /^[0-9a-f]{20,}$/i, GUID = /^[0-9a-f-]{30,}$/i; | |
| function pretty(s) { return String(s || "").replace(/[-_]+/g, " ").replace(/\b\w/g, (m) => m.toUpperCase()).trim(); } | |
| function coName(c) { | |
| const v = String(c || "").trim(); | |
| if (!v || HEX.test(v) || GUID.test(v)) return ""; | |
| if (v.includes(".")) return pretty(v.split(".")[0]); | |
| return /[\s]/.test(v) || v !== v.toLowerCase() ? v : pretty(v); | |
| } | |
| function co(j) { | |
| const c = String(j.company || "").trim(); | |
| if (c && !HEX.test(c) && !GUID.test(c) && !c.includes(".")) return /[\s.]/.test(c) || c !== c.toLowerCase() ? c : pretty(c); | |
| const slug = String(j.slug || ""); | |
| if (HEX.test(slug) || GUID.test(slug)) return "Employer not listed"; | |
| if (slug.includes(".")) return pretty(slug.split(".")[0]); // workday / oraclecloud hostnames | |
| return pretty(slug) || "Employer not listed"; | |
| } | |
| function say(el, msg, err) { const e = $(el); e.textContent = msg; e.className = "status" + (err ? " err" : ""); } | |
| // ---------- index ---------- | |
| async function readWithProgress(res, onPct) { | |
| const total = Number(res.headers.get("content-length")) || 0; | |
| if (!res.body || !total) return res.arrayBuffer(); | |
| const reader = res.body.getReader(); const chunks = []; let got = 0; | |
| for (;;) { | |
| const { done, value } = await reader.read(); | |
| if (done) break; | |
| chunks.push(value); got += value.length; onPct(Math.round((100 * got) / total)); | |
| } | |
| const out = new Uint8Array(got); let o = 0; | |
| for (const c of chunks) { out.set(c, o); o += c.length; } | |
| return out.buffer; | |
| } | |
| async function loadManifest() { | |
| if (state.manifest) return; | |
| say("#st1", "Loading job index, this runs once per browser…"); | |
| const [mr, cr] = await Promise.all([fetch(`${API}/data/manifest.json`), fetch(`${API}/data/centroids.bin`)]); | |
| if (!mr.ok || !cr.ok) throw new Error(mr.status === 404 ? "The job index has not been published yet. Try again shortly." : `Could not load the job index (HTTP ${mr.status}).`); | |
| const cbuf = await readWithProgress(cr, (p) => say("#st1", `Loading job index… ${p}%`)); | |
| const m = await mr.json(); | |
| state.manifest = m; | |
| const u16 = new Uint16Array(cbuf), f = new Float32Array(u16.length); | |
| for (let i = 0; i < u16.length; i++) f[i] = f16(u16[i]); | |
| state.cent = f; | |
| $("#corpus").textContent = `${m.jobs.toLocaleString()} open postings · ${m.leaves.toLocaleString()} clusters\n${m.recipe}`; | |
| $("#corpus").style.whiteSpace = "pre"; | |
| } | |
| function f16(h) { const s = (h & 0x8000) ? -1 : 1, e = (h >> 10) & 0x1f, m = h & 0x3ff; if (e === 0) return s * m * 2 ** -24; if (e === 31) return m ? NaN : s * Infinity; return s * (1 + m / 1024) * 2 ** (e - 15); } | |
| function centroid(i) { const d = state.manifest.dims; return state.cent.subarray(i * d, (i + 1) * d); } | |
| function nearestNodes(q, want) { | |
| const T = state.manifest.tree, leaves = []; | |
| for (const n of T) if (!n.children.length) leaves.push([dot(q, centroid(n.id)), n]); | |
| leaves.sort((a, b) => b[0] - a[0]); | |
| return leaves.slice(0, want).map(([, n]) => n); | |
| } | |
| function leavesUnder(n) { const T = state.manifest.tree, out = [], st = [n]; while (st.length) { const x = st.pop(); if (!x.children.length) out.push(x); else for (const c of x.children) st.push(T[c]); } return out; } | |
| // ---------- step 1 ---------- | |
| $("#go").onclick = async () => { | |
| const text = $("#resume").value.trim(); | |
| if (text.length < 20) { say("#st1", "Add a bit more detail before scanning.", true); return; } | |
| $("#go").disabled = true; | |
| try { | |
| await loadManifest(); | |
| say("#st1", "Matching against the index…"); | |
| const r = await fetch(`${API}/embed`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ text, title: $("#title").value, location: $("#loc").value }) }); | |
| if (!r.ok) { | |
| const e = await r.json().catch(() => ({})); | |
| throw new Error(e.error ? `${e.error}${e.retryAfterSeconds ? `. Retry in ${e.retryAfterSeconds}s.` : ""}` : `Request failed (HTTP ${r.status}).`); | |
| } | |
| const { vector, recipe } = await r.json(); | |
| state.vec = norm(vector); | |
| say("#st1", recipe !== state.manifest.recipe ? `Heads up: index recipe ${state.manifest.recipe} does not match ${recipe}.` : ""); | |
| renderCards(24); show("#s2"); | |
| } catch (e) { say("#st1", String(e.message || e), true); } | |
| $("#go").disabled = false; | |
| }; | |
| // ---------- step 2 ---------- | |
| function renderCards(want) { | |
| state.cards = nearestNodes(state.vec, want); | |
| const el = $("#cards"); el.innerHTML = ""; | |
| for (const n of state.cards) { | |
| const sim = dot(state.vec, centroid(n.id)); | |
| const d = document.createElement("div"); | |
| d.className = "card " + (state.decided.get(n.id) || ""); | |
| d.innerHTML = `<div class="lbl">${esc(n.label || n.medoid)}</div> | |
| <div class="meta">${n.size.toLocaleString()} openings${n.distinct_titles ? ` · ${n.distinct_titles} distinct titles` : ""} · match ${sim.toFixed(2)} · spread ${n.radius.toFixed(2)}</div> | |
| <ul>${n.exemplars.map((e) => { const c = coName(e.company); return `<li>${esc(e.title)} <span>${c ? "· " + esc(c) : ""}${e.location ? " · " + esc(e.location) : ""}</span></li>`; }).join("")}</ul> | |
| <div class="btns"><button class="go btn-yes">Keep</button><button class="ghost btn-no">Skip</button></div>`; | |
| d.querySelector(".btn-yes").onclick = () => decide(n, "maybe", d); | |
| d.querySelector(".btn-no").onclick = () => decide(n, "no", d); | |
| el.appendChild(d); | |
| } | |
| } | |
| $("#more").onclick = () => renderCards(state.cards.length + 24); | |
| function decide(n, v, el) { | |
| state.decided.set(n.id, v); el.className = "card " + v; | |
| if (v === "maybe") for (const leaf of leavesUnder(n)) fetchGroup(leaf); | |
| $("#next2").disabled = ![...state.decided.values()].includes("maybe"); | |
| } | |
| const groups = new Map(); | |
| async function fetchGroup(leaf) { | |
| if (groups.has(leaf.id)) return; | |
| groups.set(leaf.id, null); state.dl.total++; updDl(); | |
| const gr = await fetch(`${API}/data/groups/${leaf.id}.json`); | |
| if (!gr.ok) { groups.delete(leaf.id); state.dl.total--; updDl(); return; } | |
| const g = await gr.json(); | |
| for (const j of g.jobs) { j.v = vec(j); j.leaf = leaf.id; j.sim = dot(state.vec, j.v); state.pool.push(j); } | |
| groups.set(leaf.id, g); state.dl.done++; updDl(); | |
| } | |
| function updDl() { | |
| $("#dl").textContent = state.dl.total ? `${state.dl.done} of ${state.dl.total} clusters downloaded · ${state.pool.length.toLocaleString()} openings held locally` : ""; | |
| $("#dlbar").style.width = state.dl.total ? `${(100 * state.dl.done) / state.dl.total}%` : "0"; | |
| } | |
| $("#next2").onclick = () => { seedNegatives(); refit(); render(true); show("#s3"); }; | |
| // ---------- step 3 ---------- | |
| function seedNegatives() { | |
| for (const [id, v] of state.decided) if (v === "no") { | |
| const n = state.manifest.tree[id]; | |
| for (const leaf of leavesUnder(n).slice(0, 2)) fetch(`${API}/data/groups/${leaf.id}.json`).then((r) => r.json()).then((g) => { | |
| for (const j of g.jobs.slice(0, 5)) { j.v = vec(j); j.neg = true; j.sim = dot(state.vec, j.v); state.pool.push(j); state.labels.set(key(j), 0); } | |
| refit(); render(false); | |
| }).catch(() => {}); | |
| } | |
| } | |
| const key = (j) => `${j.ats}/${j.slug}#${j.id}`; | |
| function refit() { | |
| const D = state.manifest.dims, items = state.pool.filter((j) => state.labels.has(key(j))); | |
| const pos = items.filter((j) => state.labels.get(key(j)) === 1).length, neg = items.length - pos; | |
| if (pos === 0 || neg === 0) { | |
| state.w = null; | |
| say("#st3", `Ranked by requisition match. Mark at least one keep and one drop to train the model. Currently ${pos} keep / ${neg} drop.`); | |
| return; | |
| } | |
| const w = Float32Array.from(state.vec); let b = 0; const lr = 0.5, l2 = 0.01; | |
| for (let ep = 0; ep < 30; ep++) for (const j of items) { | |
| const y = state.labels.get(key(j)), p = 1 / (1 + Math.exp(-(dot(w, j.v) + b))), g = p - y; | |
| for (let i = 0; i < D; i++) w[i] -= lr * (g * j.v[i] + l2 * (w[i] - state.vec[i])); | |
| b -= lr * g; | |
| } | |
| state.w = w; state.b = b; | |
| say("#st3", `Model trained on ${pos} keep / ${neg} drop · ${state.pool.length.toLocaleString()} openings ranked locally`); | |
| } | |
| function score(j) { return state.w ? 1 / (1 + Math.exp(-(dot(state.w, j.v) + state.b))) : j.sim; } | |
| let ranked = [], cur = 0; | |
| function render(reset) { | |
| ranked = state.pool.filter((j) => !j.neg).sort((a, b) => score(b) - score(a)); | |
| if (reset) state.shown = 0; | |
| state.view === "jobs" ? renderJobs() : renderCos(); | |
| } | |
| function renderJobs() { | |
| const el = $("#jobs"); el.innerHTML = ""; | |
| if (!ranked.length) { el.innerHTML = `<div class="empty">No openings loaded. Go back and keep at least one cluster.</div>`; return; } | |
| const N = Math.min(ranked.length, state.shown + 40); | |
| for (let i = 0; i < N; i++) el.appendChild(jobEl(ranked[i], i)); | |
| state.shown = N; cur = Math.min(cur, N - 1); hi(); | |
| } | |
| function jobEl(j, i) { | |
| const d = document.createElement("div"), l = state.labels.get(key(j)), s = score(j); | |
| d.className = "job " + (l === 1 ? "pos" : l === 0 ? "neg" : ""); d.dataset.i = i; | |
| d.innerHTML = `<div class="t"> | |
| <b>${esc(j.title)}</b> | |
| <div class="co">${esc(co(j))}</div> | |
| <div class="loc">${j.location ? esc(j.location) + " · " : ""}<a href="${esc(j.url)}" target="_blank" rel="noopener">open posting</a></div> | |
| <div class="jd">${esc(j.jd || "(no description on file)")}</div> | |
| </div> | |
| <div class="m"><span>${s.toFixed(2)}</span><i><em style="width:${Math.round(s * 100)}%"></em></i></div>`; | |
| d.querySelector("a").onclick = (e) => e.stopPropagation(); | |
| d.onclick = () => { cur = i; hi(); const x = d.querySelector(".jd"); x.style.display = x.style.display === "block" ? "none" : "block"; }; | |
| return d; | |
| } | |
| function hi() { | |
| document.querySelectorAll(".job").forEach((e, i) => e.classList.toggle("cur", i === cur)); | |
| const e = document.querySelector(`.job[data-i="${cur}"]`); if (e) e.scrollIntoView({ block: "nearest" }); | |
| } | |
| // Board-level rollup: one row per hiring board, which is one employer. Names come from the | |
| // backend's company resolution when it has run for that board (opt-in, ~2 KB per board). | |
| function rollup() { | |
| const by = new Map(); | |
| for (const j of ranked) { | |
| const k = `${j.ats}/${j.slug}`; | |
| const c = by.get(k) || { name: co(j), ats: j.ats, n: 0, best: 0, locs: new Set(), url: j.url }; | |
| c.n++; c.best = Math.max(c.best, score(j)); if (j.location) c.locs.add(j.location); | |
| by.set(k, c); | |
| } | |
| return [...by.values()].sort((a, b) => b.n - a.n || b.best - a.best); | |
| } | |
| function renderCos() { | |
| const rows = rollup().slice(0, 150); | |
| const el = $("#cos"); el.innerHTML = ""; | |
| if (!rows.length) { el.innerHTML = `<div class="empty">No openings loaded yet.</div>`; return; } | |
| for (const c of rows) { | |
| const locs = [...c.locs].slice(0, 3).join(" · ") + (c.locs.size > 3 ? ` +${c.locs.size - 3} more` : ""); | |
| const d = document.createElement("div"); d.className = "comp"; | |
| d.innerHTML = `<div class="n"><b>${esc(c.name)}</b> <span class="tag">${esc(c.ats)}</span> | |
| <div>${esc(locs) || "location not listed"}</div></div> | |
| <div class="c">${c.n} open · top ${c.best.toFixed(2)} · <a href="${esc(c.url)}" target="_blank" rel="noopener">view a posting</a></div>`; | |
| el.appendChild(d); | |
| } | |
| } | |
| $("#vJobs").onclick = () => setView("jobs"); | |
| $("#vCos").onclick = () => setView("cos"); | |
| function setView(v) { | |
| state.view = v; | |
| $("#vJobs").classList.toggle("on", v === "jobs"); $("#vCos").classList.toggle("on", v === "cos"); | |
| $("#jobs").style.display = v === "jobs" ? "" : "none"; $("#cos").style.display = v === "cos" ? "" : "none"; | |
| $("#more3").style.display = v === "jobs" ? "" : "none"; | |
| render(false); | |
| } | |
| $("#more3").onclick = () => render(false); | |
| $("#csv").onclick = () => { | |
| const q = (s) => `"${String(s ?? "").replace(/"/g, '""').replace(/\s+/g, " ").trim()}"`; | |
| const head = "match,title,employer,location,url,source,marked\n"; | |
| const body = ranked.map((j) => { | |
| const l = state.labels.get(key(j)); | |
| return [score(j).toFixed(4), q(j.title), q(co(j)), q(j.location), q(j.url), q(j.ats), l === 1 ? "keep" : l === 0 ? "drop" : ""].join(","); | |
| }).join("\n"); | |
| const blob = new Blob([head + body], { type: "text/csv;charset=utf-8" }); | |
| const a = document.createElement("a"); | |
| a.href = URL.createObjectURL(blob); | |
| a.download = `bos-market-scan-${new Date().toISOString().slice(0, 10)}.csv`; | |
| a.click(); URL.revokeObjectURL(a.href); | |
| }; | |
| document.addEventListener("keydown", (ev) => { | |
| if (!$("#s3").classList.contains("active") || state.view !== "jobs" || ev.target.tagName === "TEXTAREA" || ev.target.tagName === "INPUT") return; | |
| const j = ranked[cur]; if (!j) return; | |
| if (ev.key === "j" || ev.key === "J") { state.labels.set(key(j), 1); refit(); render(false); cur++; hi(); } | |
| else if (ev.key === "k" || ev.key === "K") { state.labels.set(key(j), 0); refit(); render(false); cur++; hi(); } | |
| else if (ev.key === " ") { ev.preventDefault(); cur++; hi(); } | |
| else if (ev.key === "ArrowUp") { cur = Math.max(0, cur - 1); hi(); } | |
| else if (ev.key === "ArrowDown") { cur++; hi(); } | |
| }); | |