Adds two single-source-of-truth recipe files that drive both the
hot-path render server and the offline pre-render scripts:
- role_scenes.ts: per-role-band scene clauses (clothing + backdrop).
Forklift operators look like forklift operators instead of
collapsing to interchangeable studio shots. SCENES_VERSION mixes
into the headshot cache key so a coordinator tweak refreshes every
matching face on next view.
- icon_recipes.ts: cert / role-prop / status / hazard / empty icons
with deterministic per-recipe seeds + fuzzy text resolver.
ICONS_VERSION suffix on the cached file means edits don't
overwrite in place — misfires are recoverable.
Routes (mcp-server/index.ts):
- GET /headshots/_scenes — exposes SCENES + version to the
pre-render script so prompts don't drift between batch and hot-path.
- GET /icons/_recipes — same idea for icons.
- GET /icons/cert?text=... — resolves free-text cert names to a
recipe and 302s to the rendered icon. 404 (not 500) when no recipe
matches so the front-end can hang `onerror="this.remove()"`.
- GET /icons/render/{category}/{slug} — cache-or-render at 256² (8
steps) for crisper edges than 512² when downsampled to 14px.
ComfyUI portrait support (scripts/serve_imagegen.py):
The editorial workflow had `human, person, face` baked into its
negative prompt — actively sabotaging portraits. _comfyui_generate
now accepts negative_prompt/cfg/sampler/scheduler overrides, and
those mix into the cache key so portrait calls don't collapse into
hero-shot cache hits.
scripts/staffing/render_role_pool.py: pre-renders the role-aware
face pool by reading SCENES from /headshots/_scenes — single source
of truth verified at run time.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
124 lines
8.4 KiB
TypeScript
124 lines
8.4 KiB
TypeScript
// Visual filler iconography rendered through ComfyUI. Distinct from
|
||
// role_scenes.ts (which renders portraits) — these are object/badge
|
||
// style renders that fill dead space on worker cards: cert pills,
|
||
// role-prop chips, hazard indicators, empty-state heroes.
|
||
//
|
||
// Layout on disk:
|
||
// data/icons_pool/{category}/{slug}.webp
|
||
//
|
||
// Cache invalidation:
|
||
// ICONS_VERSION mixes into the on-disk filename (slug includes
|
||
// version). Bump it after editing a recipe so prior renders are
|
||
// ignored on next view.
|
||
|
||
export type IconCategory = "cert" | "role_prop" | "status" | "hazard" | "empty";
|
||
|
||
export interface IconRecipe {
|
||
slug: string;
|
||
category: IconCategory;
|
||
// Text label that appears next to / under the icon. The front-end
|
||
// already renders this text in cert pills; the icon is supplementary.
|
||
display: string;
|
||
// Full diffusion prompt. Style guidance baked in. SDXL Turbo at 8
|
||
// steps reliably produces clean macro photography, so default to
|
||
// photographic prop shots over flat-vector illustrations (the model
|
||
// hallucinates noise into flat-vector geometry at low step counts).
|
||
prompt: string;
|
||
// Negative prompt — what NOT to render. Crucial for icons because
|
||
// SDXL likes to add hands/text/people unprompted.
|
||
negative?: string;
|
||
}
|
||
|
||
// Default negative prompt baked into every icon render unless the
|
||
// recipe overrides. Empirically, these terms are the top SDXL Turbo
|
||
// off-style failures.
|
||
export const DEFAULT_NEGATIVE =
|
||
"people, hands, faces, blurry, low quality, watermark, signature, "
|
||
+ "logos, copyright, distorted text, garbled letters, multiple objects";
|
||
|
||
// TODO J — review and tune the prompts here. Each one is what diffusion
|
||
// sees verbatim. The visual decision: photographic prop shots (macro
|
||
// photo of an actual badge / placard / sticker) vs flat-icon vector
|
||
// style. Default below is photographic — matches the worker headshot
|
||
// aesthetic. Flip a recipe to flat-vector by replacing "macro photograph"
|
||
// with "flat icon illustration on solid color background, minimal vector".
|
||
//
|
||
// Visual cues that work well in SDXL Turbo at 8 steps:
|
||
// - "macro photograph", "isolated on plain background", "studio lighting"
|
||
// - Concrete colors ("orange and black warning diamond") not adjectives
|
||
// - Avoid: small text in the prompt (model garbles it), specific brand
|
||
// names (creates fake logos), detailed scene composition
|
||
const CERT_ICONS: IconRecipe[] = [
|
||
{ slug: "osha-10", category: "cert", display: "OSHA-10",
|
||
prompt: "macro photograph of a circular yellow safety badge with a black hard hat icon at center, isolated on neutral grey backdrop, photorealistic, sharp focus, studio lighting" },
|
||
{ slug: "osha-30", category: "cert", display: "OSHA-30",
|
||
prompt: "macro photograph of a circular orange safety badge with a black hard hat icon at center, isolated on neutral grey backdrop, photorealistic, sharp focus, studio lighting" },
|
||
{ slug: "first-aid-cpr", category: "cert", display: "First Aid/CPR",
|
||
prompt: "macro photograph of a small enamel pin badge featuring a bold red cross on a white circular background, isolated on neutral grey backdrop, photorealistic, sharp focus, studio lighting" },
|
||
{ slug: "hazmat", category: "cert", display: "Hazmat",
|
||
prompt: "macro photograph of a HAZMAT warning placard, bold orange and black diamond shape with a flame icon, isolated on neutral grey backdrop, photorealistic, sharp focus, studio lighting" },
|
||
{ slug: "forklift", category: "cert", display: "Forklift",
|
||
prompt: "macro photograph of a yellow industrial forklift safety badge with a forklift silhouette icon, isolated on neutral grey backdrop, photorealistic, sharp focus, studio lighting" },
|
||
{ slug: "reach-truck", category: "cert", display: "Reach Truck",
|
||
prompt: "macro photograph of a navy blue industrial certification badge with a warehouse reach-truck silhouette icon, isolated on neutral grey backdrop, photorealistic, sharp focus, studio lighting" },
|
||
{ slug: "order-picker", category: "cert", display: "Order Picker",
|
||
prompt: "macro photograph of a green industrial certification badge with a warehouse order-picker silhouette icon, isolated on neutral grey backdrop, photorealistic, sharp focus, studio lighting" },
|
||
{ slug: "lockout-tagout", category: "cert", display: "Lockout/Tagout",
|
||
prompt: "macro photograph of a bright red padlock tag with a danger warning, hanging on a metal industrial valve, isolated on neutral grey backdrop, photorealistic, sharp focus, studio lighting" },
|
||
{ slug: "msds", category: "cert", display: "MSDS",
|
||
prompt: "macro photograph of a folded chemical safety data sheet booklet with chemical hazard pictograms visible on cover, isolated on neutral grey backdrop, photorealistic, sharp focus, studio lighting" },
|
||
{ slug: "confined-space", category: "cert", display: "Confined Space",
|
||
prompt: "macro photograph of a yellow confined space warning sign featuring a manhole entry icon, isolated on neutral grey backdrop, photorealistic, sharp focus, studio lighting" },
|
||
{ slug: "servsafe", category: "cert", display: "ServSafe",
|
||
prompt: "macro photograph of a dark green food safety certification badge featuring a stylized chef hat icon, isolated on neutral grey backdrop, photorealistic, sharp focus, studio lighting" },
|
||
{ slug: "fire-safety", category: "cert", display: "Fire Safety",
|
||
prompt: "macro photograph of a red enamel pin badge featuring a flame icon and a fire extinguisher silhouette, isolated on neutral grey backdrop, photorealistic, sharp focus, studio lighting" },
|
||
{ slug: "iso-9001", category: "cert", display: "ISO 9001",
|
||
prompt: "macro photograph of a deep blue circular quality-management certification seal with embossed metallic ring, isolated on neutral grey backdrop, photorealistic, sharp focus, studio lighting" },
|
||
];
|
||
|
||
// Role-band visual chips — small icons that go in the role pill area.
|
||
// One per band, optional inline supplement to the existing colored pill.
|
||
const ROLE_PROP_ICONS: IconRecipe[] = [
|
||
{ slug: "warehouse", category: "role_prop", display: "Warehouse",
|
||
prompt: "macro photograph of a yellow hard hat with a high-visibility safety vest folded behind it, isolated on neutral grey backdrop, photorealistic, sharp focus, studio lighting" },
|
||
{ slug: "production", category: "role_prop", display: "Production",
|
||
prompt: "macro photograph of a navy blue work shirt and protective safety glasses on neutral grey backdrop, photorealistic, sharp focus, studio lighting" },
|
||
{ slug: "trades", category: "role_prop", display: "Trades",
|
||
prompt: "macro photograph of a leather work glove and a small adjustable wrench on a neutral grey backdrop, photorealistic, sharp focus, studio lighting" },
|
||
{ slug: "driver", category: "role_prop", display: "Driver",
|
||
prompt: "macro photograph of a navy delivery driver baseball cap and a clipboard manifest on a neutral grey backdrop, photorealistic, sharp focus, studio lighting" },
|
||
{ slug: "lead", category: "role_prop", display: "Lead",
|
||
prompt: "macro photograph of a tablet showing a bar chart and a high-vis vest folded beside it on neutral grey backdrop, photorealistic, sharp focus, studio lighting" },
|
||
];
|
||
|
||
export const ICONS: Record<string, IconRecipe> = Object.fromEntries(
|
||
[...CERT_ICONS, ...ROLE_PROP_ICONS].map((r) => [`${r.category}/${r.slug}`, r]),
|
||
);
|
||
|
||
// v2 — 256×256 canvas, intended to be displayed monochrome via CSS
|
||
// `filter: grayscale(1)`. Smaller canvas, tighter crops, crisper at
|
||
// 14px display size.
|
||
export const ICONS_VERSION = "v2";
|
||
|
||
// Map a free-form cert string from the data ("First Aid/CPR",
|
||
// "OSHA-10", "Lockout/Tagout") to the canonical slug used here.
|
||
// Returns null if no recipe matches.
|
||
export function certToSlug(cert: string): string | null {
|
||
const c = (cert || "").trim().toLowerCase().replace(/\s+/g, "-");
|
||
if (c === "osha-10") return "osha-10";
|
||
if (c === "osha-30") return "osha-30";
|
||
if (c.startsWith("first") || c.includes("cpr")) return "first-aid-cpr";
|
||
if (c === "hazmat" || c.startsWith("hazwoper")) return "hazmat";
|
||
if (c === "forklift" || c.startsWith("pit")) return "forklift";
|
||
if (c.startsWith("reach")) return "reach-truck";
|
||
if (c.startsWith("order")) return "order-picker";
|
||
if (c.startsWith("lockout") || c.includes("tagout")) return "lockout-tagout";
|
||
if (c === "msds" || c.startsWith("ghs")) return "msds";
|
||
if (c.startsWith("confined")) return "confined-space";
|
||
if (c === "servsafe") return "servsafe";
|
||
if (c.startsWith("fire")) return "fire-safety";
|
||
if (c.startsWith("iso")) return "iso-9001";
|
||
return null;
|
||
}
|