Surname → ethnicity routing + ComfyUI fallback for sparse pool buckets + cache-buster
Three problems J flagged ("not matching properly", "same faces", "still
showing old icons") had three different roots:
1. MISMATCH: front-end was first-name only, so "Anna Cruz" / "Patricia
Garcia" / "John Jimenez" all defaulted to caucasian. Added
SURNAMES_HISPANIC / _SOUTH_ASIAN / _EAST_ASIAN / _MIDDLE_EASTERN
dicts to both search.html and console.html. Surname is checked
FIRST (stronger signal for hispanic + asian than first names),
then first-name fallback. Cruz → hispanic, Patel → south_asian,
Nguyen → east_asian, regardless of first name.
2. SAME FACES: pool buckets are uneven — woman/south_asian=3,
man/black=4, woman/middle_eastern=2 — so any worker in those
buckets collapses to 2-4 photos no matter how good the hash is.
/headshots/:key now 302-redirects to /headshots/generate/:key
when the gender × race intersection is below 30 faces. ComfyUI
on-demand gives infinite uniqueness for the sparse buckets
(deterministic-per-worker via djb2 seed). Dense buckets still
serve from the pool — no GPU cost there.
3. STALE CACHE: Cache-Control was max-age=86400, immutable — pinned
old photos in browsers for 24h after any server-side update.
Dropped to max-age=3600, must-revalidate, and added a v=2
cache-buster query param to all front-end /headshots/ URLs so
existing cached entries are bypassed on next page load.
Also surfacing X-Face-Pool-Bucket / Bucket-Size headers for diagnosis.
Verified: playwright run shows surname routing correct (Torres,
Rivera, Alvarez, Gutierrez, Patel, Nguyen, Omar all bucketed
correctly), sparse buckets 302 to ComfyUI, dense buckets stay on
the thumb pool.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+40
-5
@@ -262,6 +262,37 @@ var NAMES_EAST_ASIAN_C=new Set(['Wei','Mei','Yi','Jin','Chen','Lin','Liu','Wang'
|
||||
var NAMES_HISPANIC_C=new Set(['Carmen','Carlos','Maria','Diego','Hector','Jorge','Julio','Manuel','Miguel','Pedro','Raul','Ricardo','Roberto','Sergio','Antonio','Esperanza','Luz','Sofia','Lucia','Isabella','Camila','Valentina','Mariana','Elena','Rosa','Catalina','Esteban','Fernando','Eduardo','Javier','Alejandro','Andres','Mateo','Santiago','Sebastian','Emilio','Tomas','Cristina','Daniela','Gabriela','Ximena','Adriana','Beatriz','Pilar','Mercedes','Xavier','Marisol','Guadalupe','Lupita','Inez','Itzel','Yesenia','Joaquin','Ignacio','Rafael','Salvador','Cesar','Arturo','Armando','Hugo','Marco','Alejandra','Felipe','Gerardo','Jaime','Leonardo','Luis','Pablo','Ramon']);
|
||||
var NAMES_BLACK_C=new Set(['DeShawn','Jamal','Aisha','Latoya','Tyrone','Malik','Imani','Keisha','Tariq','Lakisha','Kenya','Tamika','Andre','Marcus','Demetrius','Jermaine','Reggie','Tyrese','Darius','Trevon','Kareem','Damon','Jalen','Jaylen','Dwayne','DaQuan','Aaliyah','Kiara','Janelle','Jasmine','Tanisha','Maurice','Tyrell','Kwame','Khalil','Terrell','Cedric','Nia','Zuri','Jada','Ebony','Dominique']);
|
||||
var NAMES_MIDDLE_EASTERN_C=new Set(['Layla','Omar','Khalid','Fatima','Yasmin','Hassan','Hussein','Ahmed','Mohamed','Mohammed','Ali','Karim','Yusuf','Yara','Nadia','Zainab','Rania','Samira','Mariam','Salma','Ibrahim','Mahmoud','Saif','Anwar','Bilal','Faisal','Hamza','Imran','Sami','Wael','Zaid','Amira','Iman','Lina','Mona','Noor','Rana','Soha','Zara']);
|
||||
// Surname → ethnicity. Surname is more diagnostic than first name
|
||||
// for hispanic and asian — "Anna Cruz" is hispanic via surname.
|
||||
var SURNAMES_HISPANIC_C=new Set(['Garcia','Rodriguez','Martinez','Hernandez','Lopez','Gonzalez','Perez','Sanchez','Ramirez','Torres','Flores','Rivera','Gomez','Diaz','Reyes','Cruz','Morales','Ortiz','Gutierrez','Chavez','Ramos','Ruiz','Alvarez','Mendoza','Vasquez','Castillo','Jimenez','Moreno','Romero','Herrera','Medina','Aguilar','Vargas','Castro','Fernandez','Guzman','Munoz','Salazar','Ortega','Delgado','Estrada','Ayala','Pena','Cabrera','Alvarado','Espinoza','Padilla','Cardenas','Cortes','Ibarra','Vega','Soto','Lara','Navarro','Campos','Acosta','Rios','Marquez','Sandoval','Maldonado','Solis','Rojas','Mejia','Beltran','Cervantes','Lozano','Carrillo','Trevino','Robles','Tapia','Lugo']);
|
||||
var SURNAMES_SOUTH_ASIAN_C=new Set(['Patel','Singh','Kumar','Sharma','Gupta','Shah','Mehta','Desai','Joshi','Reddy','Nair','Iyer','Verma','Agarwal','Kapoor','Chopra','Malhotra','Banerjee','Chatterjee','Mukherjee','Das','Sen','Bose','Roy','Sinha','Trivedi','Pandey','Mishra','Tiwari','Yadav','Chauhan','Rana','Thakur','Pillai','Menon','Krishnan','Rao','Naidu','Pradhan','Acharya','Devi','Kaur']);
|
||||
var SURNAMES_EAST_ASIAN_C=new Set(['Chen','Wang','Li','Liu','Yang','Huang','Zhao','Wu','Zhou','Xu','Zhu','Sun','Ma','Lin','Lee','Kim','Park','Choi','Jung','Kang','Cho','Yoon','Han','Lim','Oh','Nakamura','Tanaka','Suzuki','Yamamoto','Sato','Watanabe','Takahashi','Kobayashi','Yoshida','Saito','Nguyen','Tran','Le','Pham','Hoang','Phan','Vu','Vo','Dang','Bui','Do','Ngo','Truong','Mai','Cao','Wong','Tang','Tan','Cheng','Lau','Leung','Ng','Cheung','Yip','Hsu','Tsai','Hsieh']);
|
||||
var SURNAMES_MIDDLE_EASTERN_C=new Set(['Khan','Ahmed','Hussein','Hassan','Ali','Mahmoud','Mohamed','Mohammed','Saleh','Aziz','Karim','Hamad','Najjar','Haddad','Khoury','Mansour','Rahman','Iqbal','Malik','Sheikh','Siddiqui','Qureshi','Saeed']);
|
||||
|
||||
function guessEthnicityFromName(first, last){
|
||||
if(last){
|
||||
var s=last.replace(/[^A-Za-z]/g,'');
|
||||
if(s){
|
||||
var sc=s[0].toUpperCase()+s.slice(1).toLowerCase();
|
||||
if(SURNAMES_HISPANIC_C.has(sc)) return 'hispanic';
|
||||
if(SURNAMES_MIDDLE_EASTERN_C.has(sc)) return 'middle_eastern';
|
||||
if(SURNAMES_SOUTH_ASIAN_C.has(sc)) return 'south_asian';
|
||||
if(SURNAMES_EAST_ASIAN_C.has(sc)) return 'east_asian';
|
||||
}
|
||||
}
|
||||
if(first){
|
||||
var clean=first.replace(/[^A-Za-z]/g,'');
|
||||
if(clean){
|
||||
var c=clean[0].toUpperCase()+clean.slice(1).toLowerCase();
|
||||
if(NAMES_MIDDLE_EASTERN_C.has(c)) return 'middle_eastern';
|
||||
if(NAMES_BLACK_C.has(c)) return 'black';
|
||||
if(NAMES_HISPANIC_C.has(c)) return 'hispanic';
|
||||
if(NAMES_SOUTH_ASIAN_C.has(c)) return 'south_asian';
|
||||
if(NAMES_EAST_ASIAN_C.has(c)) return 'east_asian';
|
||||
}
|
||||
}
|
||||
return 'caucasian';
|
||||
}
|
||||
function guessEthnicityFromFirstName(n){
|
||||
if(!n) return 'caucasian';
|
||||
var clean=n.replace(/[^A-Za-z]/g,''); if(!clean) return 'caucasian';
|
||||
@@ -285,15 +316,19 @@ function workerRow(name, role, detail, opts){
|
||||
// same worker always gets the same face. Falls back to monogram if
|
||||
// pool isn't fetched yet.
|
||||
var faceKey = (opts.face_key) || name || '';
|
||||
var firstName = (name||'').split(/\s+/)[0]||'';
|
||||
var nameParts = (name||'').trim().split(/\s+/);
|
||||
var firstName = nameParts[0]||'';
|
||||
var lastName = nameParts.length > 1 ? nameParts[nameParts.length-1] : '';
|
||||
var gHint = genderFor(firstName);
|
||||
var eHint = guessEthnicityFromFirstName(firstName);
|
||||
var eHint = (typeof guessEthnicityFromName === 'function')
|
||||
? guessEthnicityFromName(firstName, lastName)
|
||||
: guessEthnicityFromFirstName(firstName);
|
||||
if(faceKey){
|
||||
var img=document.createElement('img');
|
||||
img.alt='';
|
||||
// Eager: 11KB thumbs make lazy unnecessary and lazy was racing
|
||||
// playwright + retina-decode in field testing.
|
||||
img.src = P + '/headshots/' + encodeURIComponent(faceKey) + '?g='+gHint+'&e='+eHint;
|
||||
// Eager + cache-buster v=2: 11KB thumbs are cheap to load fresh
|
||||
// and the v= param invalidates browsers holding old photos.
|
||||
img.src = P + '/headshots/' + encodeURIComponent(faceKey) + '?g='+gHint+'&e='+eHint+'&v=2';
|
||||
img.onerror=function(){ this.remove(); };
|
||||
av.appendChild(img);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user