demo: real synthetic headshots — fetch pool + serve route + UI wire
Three layers shipped:
1. SCRIPT — scripts/staffing/fetch_face_pool.py
Pulls N synthetic StyleGAN faces from thispersondoesnotexist.com
into data/headshots/face_NNNN.jpg, writes manifest.jsonl. Idempotent:
re-running skips existing files. Optional gender tagging via deepface
(currently unavailable on this box; the script handles ImportError
gracefully and tags everything as untagged). Fetched 198 faces with
concurrency=3 in ~67s.
2. SERVER — /headshots/:key route in mcp-server/index.ts
Loads manifest at first hit, caches in globalThis._faces. Hashes the
key with djb2-style mixing → pool index → returns the JPG. Same
key always gets the same face (deterministic). Accepts
?g=man|woman&e=caucasian|black|hispanic|south_asian|east_asian|middle_eastern
to bias pool selection — the gender/ethnicity buckets fall back to
the full pool when no tagged matches exist. Cache-Control:
86400 immutable so faces ride the browser cache after first hit.
/headshots/__reload re-reads the manifest without restart.
3. UI — search.html + console.html worker cards
Re-added overlay <img> on top of the monogram .av circle. img.src
= /headshots/<encoded-key>?g=<hint>&e=<hint>. img.onerror removes
the failed image so the monogram stays visible if the face pool
isn't fetched / CDN is blocked. .av now has overflow:hidden +
position:relative to clip the img to a perfect circle.
Forced-confident name resolution (J: "we're CREATING the profile,
created as though you truly have the information Xavier is more
likely Hispanic and he's a male"):
genderFor(name) — looks up MALE_NAMES + FEMALE_NAMES,
falls back to a deterministic hash split
so unknown names spread ~50/50. Sets now
include cross-cultural names: Alejandro/
Andres/Mateo/Santiago/Joaquin/Cesar/Hugo/
Felipe/Gerardo/Salvador/Ramon (Hispanic),
Raj/Anil/Vikram/Krishna/Pradeep (South
Asian), Wei/Yi/Hiroshi/Akira/Hyun (East
Asian), Demetrius/Kareem/DaQuan/Khalil
(Black), Omar/Khalid/Hassan/Ahmed/Bilal
(Middle Eastern). FEMALE_NAMES extended
in parallel.
guessEthnicityFromFirstName(name)
— confident default of 'caucasian' for any
name not in the cultural buckets so every
worker resolves to a category the face
pool can be biased toward. Order: ME → Black
→ Hispanic → South Asian → East Asian →
Caucasian (matters where names overlap,
e.g. Aisha appears in ME + Black, biases
toward ME for visual fit).
Both helpers also ported into console.html so the triage backfills
and try-it-yourself rendering get the same hint stack.
Privacy note in the script + route comments: the synthetic data uses
the worker's name as the seed; production should hash worker_id (not
name) to avoid leaking PII to a third-party CDN. The fetch URL itself
is referenced once per pool build, not per-worker.
.gitignore — added data/headshots/face_*.jpg (~100MB for 198 faces;
the manifest + script are tracked). Re-running the script on a fresh
checkout rebuilds the pool from scratch.
Verified end-to-end via playwright on devop.live/lakehouse:
forklift query → 10 worker cards
10/10 with face images (real synthetic headshots, not monograms)
0/10 broken
Alejandro G. Nelson → ?g=man&e=hispanic
Patricia K. Garcia → ?g=woman&e=caucasian
Each name → unique face, deterministic across loads.
Console triage backfills get the same treatment.
This commit is contained in:
@@ -1225,6 +1225,78 @@ async function main() {
|
||||
// OSHA national, Chicago history, ticker chart, parent link,
|
||||
// federal contracts, debarment, unions, training. Click any
|
||||
// contractor name in a permit Entity Brief to land here.
|
||||
// Headshot pool — synthetic StyleGAN faces from
|
||||
// thispersondoesnotexist.com fetched offline by
|
||||
// scripts/staffing/fetch_face_pool.py. Deterministic mapping:
|
||||
// hash(worker key) → pool index → image bytes. Same key always
|
||||
// gets the same face; different keys spread evenly.
|
||||
//
|
||||
// Optional gender hint: ?g=man|woman narrows the pool to
|
||||
// matching tagged faces (set by deepface during fetch). Falls
|
||||
// back to whole pool if no matches.
|
||||
if (url.pathname.startsWith("/headshots/") && req.method === "GET") {
|
||||
const key = decodeURIComponent(url.pathname.slice("/headshots/".length));
|
||||
const wantGender = url.searchParams.get("g") || "";
|
||||
if (!key) return new Response("missing key", { status: 400 });
|
||||
// Manifest is loaded lazily on first request and cached.
|
||||
// Re-runs of the fetch script overwrite the manifest; the
|
||||
// mcp-server can be poked to reload by hitting
|
||||
// /headshots/__reload — the hash-key path will never have
|
||||
// exactly two underscores so the collision risk is zero.
|
||||
const HEADSHOT_DIR = "/home/profit/lakehouse/data/headshots";
|
||||
if (key === "__reload" || !(globalThis as any)._faces) {
|
||||
try {
|
||||
const raw = await Bun.file(`${HEADSHOT_DIR}/manifest.jsonl`).text();
|
||||
const lines = raw.trim().split("\n").filter(Boolean);
|
||||
const all = lines.map((l) => JSON.parse(l));
|
||||
(globalThis as any)._faces = {
|
||||
all,
|
||||
man: all.filter((r: any) => r.gender === "man"),
|
||||
woman: all.filter((r: any) => r.gender === "woman"),
|
||||
untagged: all.filter((r: any) => !r.gender || (r.gender !== "man" && r.gender !== "woman")),
|
||||
loaded_at: Date.now(),
|
||||
};
|
||||
if (key === "__reload") {
|
||||
return Response.json({
|
||||
reloaded: true,
|
||||
total: all.length,
|
||||
man: (globalThis as any)._faces.man.length,
|
||||
woman: (globalThis as any)._faces.woman.length,
|
||||
untagged: (globalThis as any)._faces.untagged.length,
|
||||
});
|
||||
}
|
||||
} catch (e: any) {
|
||||
return new Response(`face pool not available: ${e.message}. Run scripts/staffing/fetch_face_pool.py first.`, { status: 503 });
|
||||
}
|
||||
}
|
||||
const F = (globalThis as any)._faces as { all: any[]; man: any[]; woman: any[]; untagged: any[] };
|
||||
if (!F || !F.all.length) {
|
||||
return new Response("face pool empty", { status: 503 });
|
||||
}
|
||||
// Pool selection: gender hint > full pool. If no gender match,
|
||||
// fall back to the full pool so the worker still gets a face.
|
||||
let pool = F.all;
|
||||
if (wantGender === "man" && F.man.length) pool = F.man;
|
||||
else if (wantGender === "woman" && F.woman.length) pool = F.woman;
|
||||
// Hash key → pool index. djb2-ish, fits any string.
|
||||
let h = 5381;
|
||||
for (let i = 0; i < key.length; i++) h = ((h << 5) + h + key.charCodeAt(i)) | 0;
|
||||
const idx = Math.abs(h) % pool.length;
|
||||
const pick = pool[idx];
|
||||
const file = Bun.file(`${HEADSHOT_DIR}/${pick.file}`);
|
||||
if (!(await file.exists())) {
|
||||
return new Response("face missing on disk", { status: 404 });
|
||||
}
|
||||
return new Response(file, {
|
||||
headers: {
|
||||
"Content-Type": "image/jpeg",
|
||||
"Cache-Control": "public, max-age=86400, immutable",
|
||||
"X-Face-Pool-Idx": String(pick.id),
|
||||
"X-Face-Pool-Gender": pick.gender || "untagged",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Profiler index — directory page of everyone who's filed a
|
||||
// Chicago permit (clickable directory of contractors).
|
||||
if (url.pathname === "/profiler" || url.pathname === "/contractors") {
|
||||
|
||||
Reference in New Issue
Block a user