lakehouse/mcp-server/search.html
root 3c6d2c5f74 demo: search.html UX polish — skeleton loader, card-in stagger, hero takeover, B&W faces
Search results no longer pop in as a single block. New behavior:

- Skeleton list pre-claims the vertical space results will occupy
  with shimmering placeholder cards, so arriving results fade in
  over the skeleton instead of pushing layout. Sweep is staggered
  per row for a "rolling wave" not "everything blinking together".
- Domain-language stage caption ("matching against permits",
  "ranking by reliability") rotates on a fixed schedule so users
  read progress, not a stuck spinner.
- @keyframes card-in: real worker cards rise 4px and fade in over
  350ms with nth-child stagger across the first ~12 rows. Honors
  prefers-reduced-motion.
- Avatar imgs filter through grayscale + slight contrast/blur to
  pull the SDXL Turbo color cast (which screams "AI generated" at
  small sizes). Cert icons get the same treatment.
- Once-per-session hero takeover compresses the Section ⓪ strip
  ("Not a CRM — an index that learns from you") into a centered
  hero on first paint, dismissed by clicking anywhere. Stats
  hydrate from live endpoints.

console.html: mirrors the avatar B&W filter for visual consistency,
and removes the headshot insertion entirely — back to monogram
initials. The console (internal staffer view) doesn't need synthetic
faces; the public demo at /lakehouse/ does.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 05:35:54 -05:00

3996 lines
237 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Staffing Co-Pilot</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<style>
*{margin:0;padding:0;box-sizing:border-box}
html,body{overflow-x:hidden;max-width:100vw}
body{font-family:'Inter',-apple-system,system-ui,'Segoe UI',sans-serif;background:#090c10;color:#b0b8c4;font-size:14px;line-height:1.6;-webkit-font-smoothing:antialiased}
/* Top bar */
.bar{background:#0d1117;padding:0 24px;height:56px;border-bottom:1px solid #171d27;display:flex;justify-content:space-between;align-items:center}
.bar h1{font-size:14px;font-weight:600;color:#e6edf3;letter-spacing:-0.2px}
.bar .rt{font-size:11px;color:#545d68}
.bar nav{display:flex;gap:2px}
.bar nav a{font-size:12px;color:#545d68;text-decoration:none;padding:6px 14px;border-radius:6px;transition:all 0.15s}
.bar nav a:hover{color:#e6edf3;background:#161b22}
.bar nav a.active{color:#e6edf3;background:#1c2333}
/* Layout */
.content{max-width:940px;margin:0 auto;padding:24px 20px 40px}
.section{margin-bottom:32px}
.section-header{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:14px}
.section-title{font-size:11px;font-weight:600;color:#545d68;text-transform:uppercase;letter-spacing:1.2px}
.section-meta{font-size:10px;color:#3d444d}
/* Cards */
.card{background:#0d1117;border:1px solid #171d27;border-radius:10px;padding:20px;margin-bottom:12px}
.card.accent-red{border-left:3px solid #da3633}
.card.accent-green{border-left:3px solid #2ea043}
.card.accent-amber{border-left:3px solid #bf8700}
.card.accent-blue{border-left:3px solid #388bfd}
.card .card-label{font-size:9px;font-weight:600;color:#545d68;text-transform:uppercase;letter-spacing:1.4px;margin-bottom:6px}
.card .card-title{font-size:17px;font-weight:600;color:#e6edf3;margin-bottom:3px;letter-spacing:-0.3px}
.card .card-sub{font-size:12px;color:#545d68;margin-bottom:14px;line-height:1.5}
/* Keep old class names working */
.insight{background:#0d1117;border:1px solid #171d27;border-radius:10px;padding:20px;margin-bottom:12px}
.insight .label{font-size:9px;font-weight:600;color:#545d68;text-transform:uppercase;letter-spacing:1.4px;margin-bottom:6px}
.insight .headline{font-size:17px;font-weight:600;color:#e6edf3;margin-bottom:3px;letter-spacing:-0.3px}
.insight .sub{font-size:12px;color:#545d68;margin-bottom:14px}
.insight.urgent{border-left:3px solid #da3633}
.insight.opportunity{border-left:3px solid #2ea043}
.insight.warning{border-left:3px solid #bf8700}
.insight.info{border-left:3px solid #388bfd}
/* Workers */
.iworker{display:flex;align-items:center;gap:12px;padding:10px 12px;background:#161b22;border-radius:8px;margin-bottom:4px;transition:background 0.15s;
/* Cards rise into place 4px and fade in over 350ms instead of
popping in as a single block. Combined with the staggered nth-
child delays below, you see a "rolling fill" top-to-bottom that
feels like the system is *placing* workers, not flooding them
onto your screen. The user keeps their bearings. */
animation: card-in 350ms cubic-bezier(.2,.7,.2,1) both;
}
@keyframes card-in {
from { opacity:0; transform:translateY(4px) }
to { opacity:1; transform:translateY(0) }
}
/* Stagger the first ~12 cards. After that everyone fires at the
final delay; doesn't matter because they're below the fold for
most viewports. */
.iworker:nth-child(1) { animation-delay: 0ms }
.iworker:nth-child(2) { animation-delay: 35ms }
.iworker:nth-child(3) { animation-delay: 70ms }
.iworker:nth-child(4) { animation-delay: 105ms }
.iworker:nth-child(5) { animation-delay: 140ms }
.iworker:nth-child(6) { animation-delay: 175ms }
.iworker:nth-child(7) { animation-delay: 210ms }
.iworker:nth-child(8) { animation-delay: 245ms }
.iworker:nth-child(9) { animation-delay: 280ms }
.iworker:nth-child(10){ animation-delay: 315ms }
.iworker:nth-child(n+11){ animation-delay: 350ms }
/* Honor reduced-motion preference — no rise, no stagger. */
@media (prefers-reduced-motion: reduce) {
.iworker { animation: none }
}
.iworker:hover{background:#1c2333}
.iworker .av{width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:600;font-size:13px;color:#c9d1d9;flex-shrink:0;background:#161b22;border:1px solid #21262d;letter-spacing:0.5px;font-family:'Inter',-apple-system,sans-serif;overflow:hidden;position:relative}
.iworker .av img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;
/* B&W faces — sidesteps the SDXL Turbo color cast that screams
"AI generated" at small avatar sizes. Mild contrast lift +
half-pixel softening keeps faces readable without sharpening
the diffusion artifacts. If you want a hint of color back, drop
grayscale to 0.7 (subtle desaturation, retains warmth). */
filter: grayscale(1) contrast(1.02) brightness(1.04) blur(0.3px);
}
/* Cert icons — monochrome with a touch of contrast. B&W reads cleaner
at 14px and sidesteps the SDXL "diffusion-y" color cast. Lift
brightness a sliver to compensate for grayscale darkening warm tones. */
.cert-icon{filter: grayscale(1) contrast(0.96) brightness(1.05);}
.iworker .role-pill{display:inline-block;font-size:10px;padding:2px 8px;border-radius:3px;background:#0d1117;color:#8b949e;margin-right:8px;font-weight:600;letter-spacing:0.4px;text-transform:uppercase;border-left:2px solid #30363d}
.iworker[data-role-band="warehouse"]{border-left:3px solid #58a6ff}
.iworker[data-role-band="production"]{border-left:3px solid #d29922}
.iworker[data-role-band="trades"]{border-left:3px solid #bc8cff}
.iworker[data-role-band="driver"]{border-left:3px solid #3fb950}
.iworker[data-role-band="lead"]{border-left:3px solid #f0883e}
.iworker .role-pill[data-rb="warehouse"]{border-left-color:#58a6ff;color:#79c0ff}
.iworker .role-pill[data-rb="production"]{border-left-color:#d29922;color:#e3b341}
.iworker .role-pill[data-rb="trades"]{border-left-color:#bc8cff;color:#d2a8ff}
.iworker .role-pill[data-rb="driver"]{border-left-color:#3fb950;color:#56d364}
.iworker .role-pill[data-rb="lead"]{border-left-color:#f0883e;color:#ffa657}
.iworker .info{flex:1;min-width:0}
.iworker .nm{font-weight:600;color:#e6edf3;font-size:13px}
.iworker .detail{color:#545d68;font-size:11px}
.iworker .why{color:#388bfd;font-size:11px;margin-top:1px}
.iworker .acts{display:flex;gap:4px}
.ibtn{padding:5px 12px;border-radius:6px;font-size:10px;cursor:pointer;border:none;font-weight:600;transition:opacity 0.15s}
.ibtn:hover{opacity:0.8}
.ibtn.call{background:#1a2e4a;color:#58a6ff}
.ibtn.sms{background:#122b1e;color:#3fb950}
/* Stats */
.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-bottom:24px}
.stat{background:#0d1117;border:1px solid #171d27;border-radius:8px;padding:16px 12px;text-align:center}
.stat .n{font-size:26px;font-weight:700;color:#e6edf3;letter-spacing:-1px}
.stat .l{font-size:9px;color:#545d68;text-transform:uppercase;letter-spacing:0.8px;margin-top:4px}
/* Search */
.sa{background:#0d1117;border:1px solid #171d27;border-radius:10px;overflow:hidden}
.sa summary{cursor:pointer;color:#545d68;font-size:12px;list-style:none;padding:14px 20px;transition:color 0.15s}
.sa summary:hover{color:#b0b8c4}
.sa summary::-webkit-details-marker{display:none}
.sa .inner{padding:0 20px 20px}
.sa input[type=text]{width:100%;padding:12px 16px;background:#161b22;border:1px solid #21262d;border-radius:8px;color:#e6edf3;font-size:13px;outline:none;margin-bottom:8px;transition:border 0.15s}
.sa input:focus{border-color:#388bfd}
.srow{display:flex;gap:8px;margin-bottom:10px}
.sa select{flex:1;padding:8px 12px;background:#161b22;border:1px solid #21262d;border-radius:6px;color:#b0b8c4;font-size:12px}
.sbtn{width:100%;padding:10px;background:#1f6feb;border:none;border-radius:8px;color:#fff;font-size:13px;font-weight:600;cursor:pointer;transition:background 0.15s}
.sbtn:hover{background:#388bfd}
#sresults{margin-top:14px}
/* Footer */
.ft{text-align:center;padding:24px;color:#3d444d;font-size:11px;border-top:1px solid #171d27;margin-top:32px}
.ft a{color:#545d68;text-decoration:none;transition:color 0.15s}
.ft a:hover{color:#e6edf3}
.ld{color:#3d444d;text-align:center;padding:40px;font-size:13px}
/* Reused pulse keyframe — shared by status dots in the market-pulse
console title strip and deadline-pressure alert. */
@keyframes lh-pulse {
0%, 100% { opacity:1; transform:scale(1) }
50% { opacity:0.4; transform:scale(0.85) }
}
/* ─── Skeleton loader ───────────────────────────────────────────────
Drop-in replacement for the "Loading…" text. Reserves the exact
vertical space the real cards will occupy so when results arrive
they FADE in over the skeleton instead of pushing layout around.
The "screen takeover" feeling J flagged was from cards arriving
into 40px of empty padding and abruptly taking up 400px — this
pre-claims that space so the user feels in control of where their
eye is. */
.skel-list{display:flex;flex-direction:column;gap:4px;padding:0}
.skel-card{display:flex;align-items:center;gap:12px;padding:10px 12px;background:#0d1117;border-radius:8px;border-left:3px solid #1f242d;height:42px;box-sizing:border-box}
.skel-circle{width:40px;height:40px;border-radius:50%;flex-shrink:0;background:#161b22;position:relative;overflow:hidden}
.skel-lines{flex:1;display:flex;flex-direction:column;gap:6px;min-width:0}
.skel-bar{height:9px;border-radius:3px;background:#161b22;position:relative;overflow:hidden}
.skel-bar.w-60{width:60%}
.skel-bar.w-40{width:40%}
.skel-bar.w-80{width:80%}
.skel-bar.w-30{width:30%}
/* Shimmer — diagonal sweep, ~1.6s loop. Higher opacity than v1 so
the motion actually reads on dark backgrounds. The pseudo is
positioned with top/left+width (NOT `inset:0` — that pins right:0
which forced width:100% and killed the sweep, making skeletons
look like static blocks). */
@keyframes skel-sweep {
0% { transform: translateX(-120%) }
100% { transform: translateX(220%) }
}
.skel-circle::after, .skel-bar::after{
content:"";position:absolute;top:0;left:0;height:100%;width:60%;
background:linear-gradient(90deg, transparent 0%, rgba(120,180,255,0.18) 50%, transparent 100%);
animation: skel-sweep 1.6s ease-in-out infinite;
pointer-events:none;
}
/* Slightly brighter idle base color so the bars are visible even
between sweeps — the v1 #161b22 was within 8% of the card bg. */
.skel-bar{background:#1a2030 !important}
.skel-circle{background:#1a2030 !important}
/* Stagger the sweep across rows so the eye reads "rolling wave" not
"everything blinking together" — this looks more like a system
walking through rows than a generic spinner. */
.skel-card:nth-child(2) .skel-circle::after, .skel-card:nth-child(2) .skel-bar::after{ animation-delay:.13s }
.skel-card:nth-child(3) .skel-circle::after, .skel-card:nth-child(3) .skel-bar::after{ animation-delay:.26s }
.skel-card:nth-child(4) .skel-circle::after, .skel-card:nth-child(4) .skel-bar::after{ animation-delay:.39s }
.skel-card:nth-child(5) .skel-circle::after, .skel-card:nth-child(5) .skel-bar::after{ animation-delay:.52s }
.skel-card:nth-child(6) .skel-circle::after, .skel-card:nth-child(6) .skel-bar::after{ animation-delay:.65s }
.skel-card:nth-child(7) .skel-circle::after, .skel-card:nth-child(7) .skel-bar::after{ animation-delay:.78s }
/* Status caption — small, gray, tells the user what the system is
doing right now in domain language ("matching against permits",
"ranking by reliability") rather than generic "Loading…". The
stage rotates every ~1.2s on a fixed schedule so it feels like
real progress, not a stuck spinner. */
.skel-stage{font-size:11px;color:#6e7681;text-align:center;padding:14px 0 0;letter-spacing:0.3px;font-variant-numeric:tabular-nums}
.skel-stage::before{content:"";display:inline-block;width:6px;height:6px;border-radius:50%;background:#3fb950;margin-right:8px;vertical-align:middle;animation:skel-pulse 1.2s ease-in-out infinite}
@keyframes skel-pulse {
0%, 100% { opacity:0.3; transform:scale(0.85) }
50% { opacity:1; transform:scale(1.1) }
}
/* Bottom section-jump nav — mobile only */
/* Desktop: top nav handles navigation; this dock stays hidden. */
/* Mobile: top nav collapses (.bar nav:display:none below); this fixed dock */
/* gives one-thumb access to any section. Uses horizontal scroll-snap so */
/* additional sections never clip regardless of viewport width — staffers */
/* swipe the dock like an iOS tab bar. */
#mobile-dock{display:none}
#mobile-dock a,#mobile-dock button{
flex:0 0 auto;scroll-snap-align:start;
min-width:64px;text-align:center;padding:8px 6px;font-size:15px;font-weight:600;
color:#8b949e;text-decoration:none;border-radius:8px;background:none;
border:none;cursor:pointer;font-family:inherit;line-height:1;transition:all 0.1s
}
#mobile-dock a:active,#mobile-dock button:active{background:#1c2333;color:#e6edf3}
#mobile-dock .dock-label{font-size:9px;color:#8b949e;margin-top:4px;display:block;
text-transform:uppercase;letter-spacing:0.3px;font-weight:500;
white-space:nowrap}
/* Responsive */
@media(max-width:768px){
.stats{grid-template-columns:repeat(2,1fr);gap:6px;margin-bottom:16px}
.stat{padding:12px 8px}
.stat .n{font-size:22px}
.stat .l{font-size:8px}
.iworker{flex-direction:column;text-align:center}
.iworker .acts{justify-content:center}
.bar{padding:0 12px;height:48px;gap:10px}
.bar h1{font-size:13px}
.bar nav{display:none}
.bar .rt{font-size:10px;text-align:right;max-width:55%;line-height:1.3;color:#8b949e}
.content{padding:12px 10px 72px} /* bottom-pad reserved for mobile dock */
.section{margin-bottom:20px}
.card,.insight{padding:14px;margin-bottom:10px}
.card .card-title,.insight .headline{font-size:15px}
/* ─── Bottom section-jump dock: show on mobile only ─── */
/* Horizontal scroll-snap lets the row carry any number of section pills */
/* without clipping — swipe right to reveal more. Snap to pill edges so */
/* it feels solid, not jittery. */
#mobile-dock{
display:flex;position:fixed;bottom:0;left:0;right:0;
background:rgba(13,17,23,0.94);backdrop-filter:blur(10px);
-webkit-backdrop-filter:blur(10px);
border-top:1px solid #21262d;padding:4px 6px;gap:0;z-index:50;
overflow-x:auto;overflow-y:hidden;
scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;
padding-bottom:max(4px,env(safe-area-inset-bottom)) /* iOS home-bar */
}
#mobile-dock::-webkit-scrollbar{height:0;display:none}
/* ─── Section ⓪ Legacy bridge: stack rows vertically ─── */
#legacy-bridge-rows > div{
grid-template-columns:1fr !important;gap:6px !important;padding:10px 12px !important
}
#legacy-bridge-rows > div > div:nth-child(2){display:none !important} /* hide → arrow */
#legacy-bridge-rows > div > div:nth-child(3){
border-top:1px dashed #1f2631;padding-top:8px
}
#legacy-bridge-section summary span:last-child{display:none} /* "click to collapse" hint */
#legacy-growth-strip{font-size:10px !important;width:100%;margin-top:4px}
/* ─── Section ① Live Market hero: stack clock above pulse ─── */
#live-market-hero{grid-template-columns:1fr !important;gap:14px !important}
#live-market-hero > div:first-child{align-items:center !important}
/* ─── Section ② contract cards: compact header + grid stacks ─── */
.insight > div[style*="display:flex;justify-content:space-between"]{
flex-direction:column;gap:6px
}
.insight > div[style*="grid-template-columns:repeat(4,1fr)"]{
grid-template-columns:repeat(2,1fr) !important
}
/* Fill probability markers: smaller font, allow wrap */
.insight span[style*="font-variant-numeric:tabular-nums"]{font-size:10px}
/* ─── Section ③ Worker Search: stack dropdowns + button ─── */
#worker-search-section .srow,
#worker-search-section div[style*="display:flex;gap:8px"]{
flex-direction:column !important;gap:8px !important
}
#worker-search-section .sbtn{width:100%}
/* ─── Section ⑤ Substrate Signals: auto-fit already; shrink padding ─── */
#arch-signals > div{padding:10px 12px !important}
/* Paragraph widths */
.section p{max-width:100% !important}
/* Break long monospace attribution strings so they don't overflow */
#legacy-bridge-rows div[style*="monospace"]{word-break:break-word;overflow-wrap:anywhere}
/* Any contract-card pill rows should wrap, not push horizontal */
.insight{overflow:hidden}
/* Footer condenses */
.ft{padding:16px 12px;font-size:10px}
}
@media(max-width:480px){
.stats{grid-template-columns:1fr 1fr}
.stat .n{font-size:20px}
.bar h1{font-size:12px}
.content{padding:10px 8px 72px}
.card,.insight{padding:12px}
/* Section ⓪ rows: even tighter */
#legacy-bridge-rows > div{padding:8px 10px !important}
#legacy-bridge-section{padding:12px 14px !important}
/* Hide the long header-strip info (growth) on very small screens — */
/* the section intro paragraph and per-row attributions cover it. */
/* Keep it accessible by expanding the <details>. */
}
/* ─── Page-load hero — full-viewport, compressed layout ─────────────
Shown ONCE per session. Mirrors the visual structure of the
collapsed Section ⓪ strip ("⓪ Not a CRM — an index that learns
from you · stats · ▾ click to expand") but rendered at hero scale,
centered in a full-viewport dark backdrop. One horizontal line of
information, dismissed by clicking anywhere. */
#hero-takeover{
position:fixed;inset:0;z-index:9999;background:radial-gradient(ellipse at 50% 50%,#0d1117 0%,#01040a 70%);
display:flex;flex-direction:column;align-items:center;justify-content:center;
cursor:pointer;
opacity:0;transition:opacity 500ms ease;
}
#hero-takeover.ready{opacity:1}
#hero-takeover.dismiss{opacity:0;pointer-events:none}
/* The compressed strip — single horizontal row of title + dot-
separated stats. Word-wraps gracefully on narrow viewports. */
#hero-takeover .ht-strip{
display:flex;align-items:baseline;gap:18px;flex-wrap:wrap;
max-width:min(92vw,1200px);justify-content:center;
padding:24px 32px;background:#0d1117;border:1px solid #171d27;
border-radius:10px;
font-variant-numeric:tabular-nums;line-height:1.4;
}
#hero-takeover .ht-num{
font-size:11px;letter-spacing:4px;color:#58a6ff;
text-transform:uppercase;font-weight:600;
}
#hero-takeover .ht-title{
font-size:clamp(20px,2.6vw,32px);font-weight:600;color:#e6edf3;
letter-spacing:-0.01em;
}
#hero-takeover .ht-stats-inline{
font-size:clamp(13px,1.4vw,16px);color:#8b949e;
font-variant-numeric:tabular-nums;
}
#hero-takeover .ht-stats-inline strong{
color:#e6edf3;font-weight:600;font-feature-settings:"tnum";
}
#hero-takeover .ht-stats-inline .grow{
background:linear-gradient(90deg,#3fb950 0%,#79c0ff 100%);
-webkit-background-clip:text;background-clip:text;color:transparent;
font-style:italic;font-weight:600;
}
#hero-takeover .ht-expand{
font-size:11px;color:#6e7681;letter-spacing:3px;text-transform:uppercase;
margin-left:8px;
}
#hero-takeover .ht-dismiss{
position:absolute;bottom:32px;font-size:11px;color:#545d68;
letter-spacing:3px;text-transform:uppercase;
}
@media(max-width:640px){
#hero-takeover .ht-strip{padding:16px 20px;gap:10px}
}
</style>
<!-- Three.js for the live 3D clock. Loads ~600KB once, cached forever
after first hit. Defer keeps the inline scripts above unblocked. -->
<script defer src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
</head><body>
<!-- Hero takeover — see CSS comment block above. Full-viewport on load,
fades out on first interaction. JS hydrates stats from live endpoints. -->
<div id="hero-takeover" onclick="this.classList.add('dismiss');setTimeout(()=>this.remove(),700);try{sessionStorage.setItem('hero_seen','1')}catch(e){}">
<div class="ht-strip">
<span class="ht-num"></span>
<span class="ht-title">Not a CRM — an index that learns from you</span>
<span class="ht-stats-inline">
<strong id="ht-stat-profiles"></strong> profiles indexed
&nbsp;·&nbsp;
<strong id="ht-stat-traces"></strong> pathway traces
&nbsp;·&nbsp;
<span class="grow">↗ grows per contract</span>
</span>
<span class="ht-expand">▾ click to expand</span>
</div>
<div class="ht-dismiss">click anywhere to enter</div>
</div>
<script>
(function(){
// Skip the takeover if we've shown it this session, or if the user
// landed via a query (?q=...) — they're back to do work, not be sold.
try {
var seen = sessionStorage.getItem('hero_seen') === '1';
var hasQ = new URLSearchParams(location.search).has('q');
if (seen || hasQ) {
var h = document.getElementById('hero-takeover');
if (h) h.remove();
return;
}
} catch(e) {}
// Fetch real numbers in parallel so the hero shows live counts.
// Falls back to credible defaults if either endpoint times out —
// the hero must never block the rest of the page from loading.
// Path prefix mirrors the main script's logic (~line 632) — when
// served at /lakehouse/, all our endpoints live under /lakehouse/*.
// Without the prefix the hero hits devop.live/system/summary (404)
// instead of devop.live/lakehouse/system/summary.
var P = location.pathname.indexOf('/lakehouse') >= 0 ? '/lakehouse' : '';
var FALLBACK = { profiles: 500000, traces: 88 };
function fmtCount(n) {
if (n >= 1e6) return (n/1e6).toFixed(1).replace(/\.0$/,'') + 'M';
if (n >= 1e3) return Math.round(n/1e3) + 'K';
return String(n);
}
Promise.allSettled([
fetch(P + '/system/summary').then(r => r.json()).catch(() => null),
fetch(P + '/api/vectors/pathway/stats').then(r => r.json()).catch(() => null),
]).then(function(results) {
var sys = results[0].status === 'fulfilled' ? results[0].value : null;
var path = results[1].status === 'fulfilled' ? results[1].value : null;
var profiles = (sys && (sys.workers_500k_rows || sys.total_rows)) || FALLBACK.profiles;
var traces = (path && (path.total_pathways || path.entries || path.count)) || FALLBACK.traces;
var pEl = document.getElementById('ht-stat-profiles');
var tEl = document.getElementById('ht-stat-traces');
if (pEl) pEl.textContent = fmtCount(profiles);
if (tEl) tEl.textContent = String(traces);
var hero = document.getElementById('hero-takeover');
if (hero) hero.classList.add('ready');
});
// Esc / scroll also dismiss — staffers know how to skip a splash.
function dismiss() {
var h = document.getElementById('hero-takeover');
if (!h) return;
h.classList.add('dismiss');
setTimeout(function(){ if (h.parentNode) h.remove(); }, 700);
try { sessionStorage.setItem('hero_seen', '1'); } catch(e) {}
}
document.addEventListener('keydown', function(e){ if (e.key === 'Escape') dismiss(); }, { once: true });
window.addEventListener('scroll', dismiss, { once: true, passive: true });
})();
</script>
<div class="bar">
<h1>Staffing Co-Pilot</h1>
<nav>
<a href="." class="active">Dashboard</a>
<a href="console">Walkthrough</a>
<a href="profiler">Profiler</a>
<a href="proof">Architecture</a>
<a href="spec">Spec</a>
<a href="onboard">Onboard</a>
<a href="alerts">Alerts</a>
<a href="workspaces">Workspaces</a>
</nav>
<div class="rt" id="status">Loading...</div>
</div>
<div class="content">
<!-- ═══ ⓪ Legacy CRM → this system: translate the mental model ═══ -->
<!-- Written for staffers who land here from a legacy CRM (Avionte, Bullhorn, -->
<!-- Tempworks, etc). Their mental model is "every concept must be a visible -->
<!-- field or it doesn't exist." Ours is the opposite: concepts get indexed when -->
<!-- you use them, not when a form declares them. This strip names common -->
<!-- legacy surfaces and says what this system does in their place — with real -->
<!-- attribution numbers so the compounding claim is measurable, not marketed. -->
<!-- Placed FIRST so staffers see the translation before they see a thin UI and -->
<!-- conclude "my old CRM had more checkboxes." -->
<details class="section" id="legacy-bridge-section" style="background:#0d1117;border:1px solid #171d27;border-radius:10px;padding:16px 20px;margin-bottom:14px"
ontoggle="var s=this.querySelector('.lb-toggle-hint');if(s)s.textContent=this.open?'▾ click to collapse':'▸ click to expand'">
<summary style="list-style:none;cursor:pointer;display:flex;align-items:baseline;gap:14px;flex-wrap:wrap;outline:none">
<span class="section-title" style="color:#e6edf3;font-size:13px;font-weight:600">⓪ Not a CRM — an index that learns from you</span>
<span id="legacy-growth-strip" style="font-size:11px;color:#8b949e;font-variant-numeric:tabular-nums">loading growth numbers…</span>
<span class="lb-toggle-hint" style="margin-left:auto;font-size:10px;color:#545d68">▸ click to expand</span>
</summary>
<p style="color:#8b949e;font-size:11px;margin:10px 0 14px;line-height:1.55;max-width:780px">
If you've worked on a legacy staffing CRM, your mental model is <em>field inventory</em>
every concept must be a visible column, dropdown, or checkbox, or it doesn't exist. This
system works the opposite way: concepts don't need to be pre-declared because the
<strong>hybrid index + playbook memory</strong> learns them when you work a contract.
The rows below translate the familiar legacy surface into what actually happens here,
with real numbers for every claim.
</p>
<div id="legacy-bridge-rows"></div>
</details>
<div id="main">
<div class="skel-list" data-stages="Reading 500K worker profiles|Matching against today's contracts|Ranking by reliability and proximity|Surfacing the urgent pipeline">
<div class="skel-card"><div class="skel-circle"></div><div class="skel-lines"><div class="skel-bar w-60"></div><div class="skel-bar w-40"></div></div></div>
<div class="skel-card"><div class="skel-circle"></div><div class="skel-lines"><div class="skel-bar w-80"></div><div class="skel-bar w-30"></div></div></div>
<div class="skel-card"><div class="skel-circle"></div><div class="skel-lines"><div class="skel-bar w-60"></div><div class="skel-bar w-40"></div></div></div>
<div class="skel-card"><div class="skel-circle"></div><div class="skel-lines"><div class="skel-bar w-80"></div><div class="skel-bar w-30"></div></div></div>
<div class="skel-card"><div class="skel-circle"></div><div class="skel-lines"><div class="skel-bar w-60"></div><div class="skel-bar w-40"></div></div></div>
<div class="skel-stage">Reading 500K worker profiles</div>
</div>
</div>
<!-- ═══ 1. LIVE MARKET — Chicago hero (clock + permit pulse together) ═══ -->
<div class="section" id="live-market-section">
<div class="section-header">
<span class="section-title" style="color:#e6edf3;font-size:13px">① Live Market — Chicago right now</span>
<span class="section-meta" id="live-market-meta">Public permit data · per-shift schedule on the internal calendar · click any shift to filter</span>
</div>
<p style="color:#8b949e;font-size:11px;margin:0 0 12px;line-height:1.5;max-width:780px">
The console on the left is the live punch clock — current time, the active shift, and
four dials for what that shift looks like right now: open permits, workers needed,
bench available, and coverage. The panel beside it adds combined bill demand for those
permits, deadline pressure on the open queue, the top roles in demand, and a shift
comparison strip. <strong>Click any shift</strong> in the comparison to filter every
dial and the bill rate to that shift's slice of the internal calendar — past, active,
or upcoming. Click the same shift again to return to the all-shifts total. This is the
<em>real world</em> the rest of the page is reacting to.
</p>
<div id="live-market-hero" style="display:grid;grid-template-columns:240px 1fr;gap:20px;align-items:start">
<div style="grid-column:1/-1">
<div class="skel-list" data-stages="Pulling live Chicago permits|Aligning to current shift clock|Cross-referencing demand">
<div class="skel-card"><div class="skel-circle"></div><div class="skel-lines"><div class="skel-bar w-80"></div><div class="skel-bar w-30"></div></div></div>
<div class="skel-card"><div class="skel-circle"></div><div class="skel-lines"><div class="skel-bar w-60"></div><div class="skel-bar w-40"></div></div></div>
<div class="skel-stage">Pulling live Chicago permits</div>
</div>
</div>
</div>
</div>
<div class="section" id="staffing-forecast-section">
<div class="section-header">
<span class="section-title">Staffing Forecast — Next 30 Days</span>
<span class="section-meta">Permits → predicted demand · Bench supply · Days to staffing deadline</span>
</div>
<div id="staffing-forecast">
<div class="skel-list" data-stages="Forecasting from permit pipeline|Calculating bench coverage|Identifying critical role gaps">
<div class="skel-card"><div class="skel-circle"></div><div class="skel-lines"><div class="skel-bar w-60"></div><div class="skel-bar w-40"></div></div></div>
<div class="skel-card"><div class="skel-circle"></div><div class="skel-lines"><div class="skel-bar w-80"></div><div class="skel-bar w-30"></div></div></div>
<div class="skel-card"><div class="skel-circle"></div><div class="skel-lines"><div class="skel-bar w-60"></div><div class="skel-bar w-40"></div></div></div>
<div class="skel-stage">Forecasting from permit pipeline</div>
</div>
</div>
</div>
<!-- ═══ 2. STAFFER'S CONSOLE — what a coordinator acts on ═══ -->
<div class="section" id="live-contracts-section">
<div class="section-header">
<span class="section-title" style="color:#e6edf3;font-size:13px">② Staffer's Console — what's on your plate</span>
<span class="section-meta" id="live-contracts-meta">Real public permit data + worker bench + past playbook patterns</span>
</div>
<p style="color:#8b949e;font-size:11px;margin:0 0 12px;line-height:1.5;max-width:680px">
This is what a recruiter or coordinator sees when they open the console. Each card is
one open permit ranked against our 500K worker bench. The <strong>fill-probability bar</strong>
shows cumulative chance of filling by day; the <strong>economics panel</strong> projects
gross revenue, margin, and payout window; the <strong>over-bill pool</strong> flags workers
whose pay exceeds the contract's bill rate — they go into a margin-watch bucket instead
of being rejected outright.
</p>
<div id="live-contracts">
<div class="skel-list" data-stages="Reading active contracts|Matching workers to roles|Computing fill probability">
<div class="skel-card"><div class="skel-circle"></div><div class="skel-lines"><div class="skel-bar w-80"></div><div class="skel-bar w-30"></div></div></div>
<div class="skel-card"><div class="skel-circle"></div><div class="skel-lines"><div class="skel-bar w-60"></div><div class="skel-bar w-40"></div></div></div>
<div class="skel-card"><div class="skel-circle"></div><div class="skel-lines"><div class="skel-bar w-80"></div><div class="skel-bar w-30"></div></div></div>
<div class="skel-stage">Reading active contracts</div>
</div>
</div>
</div>
<div class="section" id="market-section">
<div class="section-header">
<span class="section-title">Market Intelligence</span>
<span class="section-meta">Public permit data · Updated live</span>
</div>
<div id="market"></div>
</div>
<!-- ═══ ③ Worker Search — natural-language lookup over the 500K bench ═══ -->
<div class="section" id="worker-search-section">
<div class="section-header">
<span class="section-title" style="color:#e6edf3;font-size:13px">③ Worker Search — find someone specific</span>
<span class="section-meta" id="worker-search-meta">Natural language search</span>
</div>
<p style="color:#8b949e;font-size:11px;margin:0 0 10px;line-height:1.5;max-width:680px">
Type a plain-English description — role, location, trait, certification.
The query hits the hybrid SQL + vector index over all 500K worker profiles
and ranks by semantic match, reliability, and availability. Try one of the
sample searches below or write your own.
</p>
<div id="ws-samples" style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:10px;font-size:11px"></div>
<input type="text" id="sq" placeholder="Try: reliable forklift operator available in Nashville" onkeydown="if(event.key==='Enter')doSearch()" style="width:100%;box-sizing:border-box;padding:10px 12px;background:#0d1117;border:1px solid #30363d;border-radius:6px;color:#e6edf3;font-size:13px;margin-bottom:8px">
<div class="srow" style="display:flex;gap:8px;margin-bottom:8px;align-items:center">
<!-- Per-staffer hot-swap selector. When a staffer is chosen, every
search and triage scopes to their territory; the playbook MEMORY
panel labels itself "Maria's recent fills" instead of generic. -->
<select id="sstaffer" style="flex:0 0 auto;padding:8px 10px;background:#0d1117;border:1px solid #58a6ff66;border-radius:6px;color:#e6edf3;font-weight:600;cursor:pointer" title="Act as this coordinator — playbook context will scope to their territory">
<option value="">All staffers</option>
</select>
<select id="sst" style="flex:1;padding:8px;background:#0d1117;border:1px solid #30363d;border-radius:6px;color:#e6edf3"><option value="">Any State</option></select>
<select id="srl" style="flex:1;padding:8px;background:#0d1117;border:1px solid #30363d;border-radius:6px;color:#e6edf3"><option value="">Any Role</option></select>
<button class="sbtn" onclick="doSearch()" style="padding:8px 16px;background:#238636;border:none;border-radius:6px;color:#fff;font-weight:600;cursor:pointer">Find Workers</button>
</div>
<div id="sstaffer-greeting" style="font-size:11px;color:#58a6ff;margin-bottom:8px;min-height:14px"></div>
<div id="sresults"></div>
</div>
<!-- ═══ ④ System Activity — what the substrate has learned to do ═══ -->
<div class="section" id="learning-section">
<div class="section-header">
<span class="section-title" style="color:#e6edf3;font-size:13px">④ System Activity — what the substrate has learned to do</span>
<span class="section-meta">Live capabilities · pulled from each subsystem on load</span>
</div>
<p style="color:#8b949e;font-size:11px;margin:0 0 12px;line-height:1.5;max-width:780px">
Each tile is a capability the system has acquired and the live metric proving
it's running. Operational learning (fills, playbooks, hot-swaps) compounds
inside each capability; what changes here is the <em>set of things the
substrate knows how to do</em>. The architecture is metro-agnostic — every
capability replicates by config, not code.
</p>
<div id="learning"></div>
</div>
<!-- ═══ ⑤ Substrate signals — compact architecture-health strip (bottom) ═══ -->
<div class="section" id="arch-signals-section">
<div class="section-header">
<span class="section-title" style="color:#e6edf3;font-size:13px">⑤ Substrate Signals — architecture health</span>
<span class="section-meta">Live probes of the index, memory, and pathway layers</span>
</div>
<p style="color:#8b949e;font-size:11px;margin:0 0 10px;line-height:1.5;max-width:680px">
These tiles measure the architecture itself, not the staffing workload.
Instant-search latency, index shape, playbook-memory depth, pathway-matrix
compounding — four probes that answer "is the substrate healthy right now?"
</p>
<div id="arch-signals" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:10px">
<div style="color:#8b949e;font-size:11px;grid-column:1/-1">Probing substrate…</div>
</div>
</div>
<div class="ft" id="footer">Staffing Co-Pilot · Hybrid SQL + Vector Search · loading scale… · <a href="console">Console</a> · <a href="proof">Architecture</a></div>
</div>
<!-- ═══ Mobile section-jump dock — visible < 768px only via CSS ═══ -->
<!-- One-thumb navigation for staffers scrolling through the long dashboard. -->
<!-- Each pill jumps to a section by ID; the top bar nav is hidden on mobile, -->
<!-- so this replaces it entirely. Icons stay terse (⓪①②③④⑤) and the label -->
<!-- below reminds what each section is. -->
<nav id="mobile-dock" aria-label="Jump to section">
<a href="#legacy-bridge-section" title="Why this isn't your legacy CRM"><span class="dock-label">Intro</span></a>
<a href="#live-market-section" title="Chicago clock + permit pulse"><span class="dock-label">Market</span></a>
<a href="#live-contracts-section" title="Open contracts + candidates"><span class="dock-label">Jobs</span></a>
<a href="#worker-search-section" title="Search 500K workers"><span class="dock-label">Search</span></a>
<a href="#learning-section" title="What the substrate has learned"><span class="dock-label">Learn</span></a>
<a href="#arch-signals-section" title="Live architecture health"><span class="dock-label">Stack</span></a>
</nav>
<script>
var P=location.pathname.indexOf('/lakehouse')>=0?'/lakehouse':'';
var A=location.origin+P;
var AC=['#1a2744','#1a3a2a','#2a1a3a','#3a2a1a','#1a3a3a','#2a2a1a'];
var lastQuery='';
// Skeleton stage rotator — finds every .skel-list[data-stages="A|B|C"]
// and advances its caption every 1.4s. Stops when the container is
// removed (loadDay / loadStaffingForecast etc. wipe their root and
// rebuild). Self-cleans via MutationObserver, no leaks.
function startSkelStages(){
document.querySelectorAll('.skel-list[data-stages]').forEach(function(root){
var stages=(root.getAttribute('data-stages')||'').split('|').filter(Boolean);
if(stages.length<2) return;
var caption=root.querySelector('.skel-stage'); if(!caption) return;
var i=0;
var iv=setInterval(function(){
// Stop if the skeleton has been replaced with real content
if(!document.body.contains(caption)){ clearInterval(iv); return; }
i=(i+1)%stages.length;
caption.style.opacity='0';
setTimeout(function(){ caption.firstChild.nodeValue=stages[i]; caption.style.opacity='1' },180);
},1400);
});
}
// Tweak: caption gets a fade-on-swap transition so stage changes
// don't jolt the eye. Applied lazily so it doesn't interfere with
// the initial layout calculation.
function styleSkelStages(){
document.querySelectorAll('.skel-stage').forEach(function(c){ c.style.transition='opacity 180ms ease' });
}
window.addEventListener('load',function(){
styleSkelStages(); startSkelStages();
loadSystemSummary();loadLegacyBridge();loadDay();loadStaffingForecast();loadLiveContracts();loadMarket();loadLearning();loadWorkerSearchSamples();loadArchSignals();loadStaffers();
});
// Per-staffer hot-swap dropdown — runs independently of the simulation
// fetch so the staffer selector populates even if any other init step
// errors out. /api/staffers returns the synthetic coordinator roster.
function loadStaffers(){
var sel=document.getElementById('sstaffer');
var greeting=document.getElementById('sstaffer-greeting');
if(!sel) return;
// /staffers (not /api/staffers) — the /api/* generic passthrough
// forwards anything under /api/ to the Rust gateway on :3100 and the
// gateway doesn't know the staffer roster (it lives in the mcp-server
// module). The bare /staffers route serves directly.
fetch(A+'/staffers').then(function(r){return r.json()}).then(function(d){
(d.staffers||[]).forEach(function(s){
var o=document.createElement('option');o.value=s.id;o.textContent=s.display||s.name;
sel.appendChild(o);
});
sel._roster=d.staffers||[];
}).catch(function(){});
sel.addEventListener('change',function(){
var roster=sel._roster||[];
var s=roster.find(function(x){return x.id===sel.value});
if(s){
greeting.textContent='Acting as '+s.name+' — '+(s.greeting||'')+' · territory: '+s.territory.cities.slice(0,3).join(', ')+'…';
var stSel=document.getElementById('sst');
if(stSel && !stSel.value){stSel.value=s.territory.state}
}else{
greeting.textContent='';
}
});
}
// Deep-link: visiting the dashboard with #open-briefs in the URL auto-
// expands every Entity Brief panel once the contract cards finish
// loading. Useful for headless snapshots + demo walkthroughs.
window.addEventListener('load',function(){
if(location.hash!=='#open-briefs') return;
var tries=0;
var t=setInterval(function(){
tries++;
var briefs=document.querySelectorAll('#live-contracts details');
if(briefs.length===0 && tries<40) return;
briefs.forEach(function(d){d.open=true;d.dispatchEvent(new Event('toggle'))});
clearInterval(t);
},250);
});
// ─── Legacy CRM → this system translator ─────────────────────────
// Staffers from legacy systems operate by field-inventory mental model:
// if a concept isn't pre-rendered as a dropdown/checkbox, they conclude
// the system can't do it. This function fills the ⓪ Section with
// side-by-side rows mapping common legacy surfaces to how the hybrid
// substrate handles the same concept — using LIVE attribution numbers
// so the compounding claim is measurable, not marketed. One row is an
// honest "we don't ship this yet" gap, because staffers trust a system
// that admits gaps faster than one that claims everything.
function loadLegacyBridge(){
var host=document.getElementById('legacy-bridge-rows');
if(!host) return;
var strip=document.getElementById('legacy-growth-strip');
Promise.all([
api('/intelligence/arch_signals',{}).catch(function(){return {}}),
api('/system/summary',{}).catch(function(){return {}})
]).then(function(results){
var s=results[0]||{}, sum=results[1]||{};
var pbm=s.playbook_memory||{}, pwm=s.pathway_memory||{}, idx=s.index||{};
var workers=(sum.workers_500k_rows||0).toLocaleString();
var chunks=(idx.chunk_count||0).toLocaleString();
var pbEntries=pbm.entries||0;
var pwTraces=pwm.total_pathways||0;
if(strip){
var parts=[workers+' profiles indexed'];
if(pbEntries) parts.push(pbEntries+' playbook'+(pbEntries===1?'':'s')+' from past fills');
if(pwTraces) parts.push(pwTraces+' pathway trace'+(pwTraces===1?'':'s'));
parts.push('grows per contract');
strip.textContent=parts.join(' · ');
}
var rows=[
{ legacy:'Certification checkboxes — tick every cert you need',
legacyWhy:'If a worker\'s cert isn\'t on the pre-declared list, you can\'t find them.',
here:'Type it in plain English — "forklift with OSHA 10"',
hereWhy:'Vector search scans free-text cert fields across every profile. No list to maintain.',
attribution:chunks+' vector chunks · nomic-embed-text 768d · built once at ingest',
anchor:'③ Worker Search' },
{ legacy:'Preferred-worker list you tag by hand',
legacyWhy:'Forget to tag someone → they stop rising to the top.',
here:'Workers with past fills get the green "Endorsed" chip automatically',
hereWhy:'Playbook memory boosts them on every future search in the same pattern. You never tag manually.',
attribution:pbEntries+' playbook'+(pbEntries===1?'':'s')+' active right now · grows with every Call you log',
anchor:'② Staffer\'s Console' },
{ legacy:'Shift dropdown: 1st / 2nd / 3rd / 4th',
legacyWhy:'Contracts with messy shift language fall through the cracks.',
here:'Shifts inferred from permit description text',
hereWhy:'The 24/7 clock above shows live distribution across shifts, pulled from real Chicago permits.',
attribution:'Permit parser runs on every live feed poll · zero config',
anchor:'① Live Market' },
{ legacy:'Blacklist checkbox — permanent, system-wide',
legacyWhy:'One bad day follows a worker forever, across every job type.',
here:'Tap "No-show" on any candidate card',
hereWhy:'Score drops 0.5× in that geo only. Soft, geo-scoped, reversible — not a ban.',
attribution:pwTraces+' pathway trace'+(pwTraces===1?'':'s')+' in the failure matrix · ADR-021 compounding',
anchor:'② Staffer\'s Console' },
{ legacy:'Side-by-side compare view',
legacyWhy:'Useful for interview prep. We\'re not pretending you don\'t need it.',
here:'Not shipped yet — honest gap',
hereWhy:'Workaround: every candidate card already carries rate, reliability, boost source, and cert summary.',
attribution:'On the roadmap · flagged so you know what\'s missing',
gap:true }
];
host.textContent='';
rows.forEach(function(r){
var row=document.createElement('div');
row.style.cssText='display:grid;grid-template-columns:1fr 22px 1.15fr;gap:12px;padding:11px 14px;background:#161b22;border:1px solid #1f2631;border-radius:8px;margin-bottom:6px;align-items:start';
// LEGACY column
var L=document.createElement('div');
var lTag=document.createElement('div');lTag.style.cssText='font-size:9px;color:#545d68;text-transform:uppercase;letter-spacing:1px;margin-bottom:3px';lTag.textContent='LEGACY CRM EXPECTS';
var lTxt=document.createElement('div');lTxt.style.cssText='color:#8b949e;font-size:12px;line-height:1.4;font-weight:500';lTxt.textContent=r.legacy;
var lWhy=document.createElement('div');lWhy.style.cssText='color:#545d68;font-size:10px;margin-top:3px;line-height:1.45';lWhy.textContent=r.legacyWhy;
L.appendChild(lTag);L.appendChild(lTxt);L.appendChild(lWhy);
// ARROW
var A=document.createElement('div');A.style.cssText='color:#388bfd;font-size:18px;text-align:center;padding-top:14px;font-weight:300';A.textContent='→';
// HERE column
var H=document.createElement('div');
var hTag=document.createElement('div');
hTag.style.cssText='font-size:9px;color:'+(r.gap?'#d29922':'#58a6ff')+';text-transform:uppercase;letter-spacing:1px;margin-bottom:3px';
hTag.textContent=r.gap?'HERE — HONEST GAP':'HERE';
var hTxt=document.createElement('div');
hTxt.style.cssText='color:'+(r.gap?'#d29922':'#e6edf3')+';font-size:12px;line-height:1.4;font-weight:600';
hTxt.textContent=r.here;
var hWhy=document.createElement('div');hWhy.style.cssText='color:#8b949e;font-size:11px;margin-top:3px;line-height:1.45';hWhy.textContent=r.hereWhy;
var attr=document.createElement('div');
attr.style.cssText='color:#545d68;font-size:10px;margin-top:5px;font-family:ui-monospace,SFMono-Regular,monospace';
attr.textContent='▸ '+r.attribution;
H.appendChild(hTag);H.appendChild(hTxt);H.appendChild(hWhy);H.appendChild(attr);
if(r.anchor){
var ptr=document.createElement('div');
ptr.style.cssText='color:#388bfd;font-size:10px;margin-top:3px';
ptr.textContent='see '+r.anchor+' below';
H.appendChild(ptr);
}
row.appendChild(L);row.appendChild(A);row.appendChild(H);
host.appendChild(row);
});
});
}
// ─── Worker Search sample chips ──────────────────────────────────
// Real-looking staffing queries grouped by intent. Clicking one drops
// the text into #sq and runs the search — lowers the floor for
// someone seeing the UI for the first time.
function loadWorkerSearchSamples(){
var el=document.getElementById('ws-samples');
if(!el) return;
var samples=[
{tag:'Role + geo', q:'reliable welder in Ohio with 3+ years'},
{tag:'Cert-gated', q:'forklift operator with OSHA 10 available in Chicago'},
{tag:'Urgent cover', q:'production worker for 3rd shift in Toledo tonight'},
{tag:'Trait-first', q:'punctual machine operator who can lead a line'},
{tag:'Specialist', q:'maintenance tech comfortable with Allen-Bradley PLCs'},
{tag:'High-volume', q:'warehouse associates available next week near Indianapolis'}
];
samples.forEach(function(s){
var chip=document.createElement('button');
chip.type='button';
chip.style.cssText='padding:5px 10px;border-radius:9px;background:#0d1117;border:1px solid #30363d;color:#8b949e;font-size:11px;cursor:pointer;display:inline-flex;align-items:center;gap:6px;transition:all 0.1s';
chip.onmouseover=function(){chip.style.borderColor='#58a6ff';chip.style.color='#e6edf3'};
chip.onmouseout=function(){chip.style.borderColor='#30363d';chip.style.color='#8b949e'};
var tag=document.createElement('span');tag.style.cssText='color:#545d68;font-size:9px;text-transform:uppercase;letter-spacing:1px';tag.textContent=s.tag;
var txt=document.createElement('span');txt.textContent=s.q;
chip.appendChild(tag);chip.appendChild(txt);
chip.onclick=function(){
var input=document.getElementById('sq');
if(input){input.value=s.q;doSearch();input.scrollIntoView({behavior:'smooth',block:'center'});}
};
el.appendChild(chip);
});
}
function loadSystemSummary(){
api('/system/summary',{}).then(function(s){
if(!s) return;
var totalRows=(s.total_rows||0).toLocaleString();
var workers=(s.workers_500k_rows||0).toLocaleString();
var chunks=(s.total_chunks||0).toLocaleString();
var ds=s.datasets||0;
var meta1=document.getElementById('live-contracts-meta');
if(meta1) meta1.textContent='Real public permit data + '+workers+' worker bench + past playbook patterns';
var meta2=document.getElementById('worker-search-meta');
if(meta2) meta2.textContent='Natural language · '+workers+' profiles across '+ds+' datasets';
var foot=document.getElementById('footer');
if(foot){
foot.textContent='';
foot.appendChild(document.createTextNode('Staffing Co-Pilot · Hybrid SQL + Vector Search · '+totalRows+' rows across '+ds+' datasets · '+chunks+' vector chunks · '));
var a1=document.createElement('a');a1.href='console';a1.textContent='Console';foot.appendChild(a1);
foot.appendChild(document.createTextNode(' · '));
var a2=document.createElement('a');a2.href='proof';a2.textContent='Architecture';foot.appendChild(a2);
}
// Also update the collapsible search box label if not yet populated
var sum=document.querySelector('.sa summary');
if(sum&&/Search all\s*\d*\s*workers/.test(sum.textContent)){
sum.textContent='Search all '+workers+' workers';
}
}).catch(function(){/* non-fatal */});
}
// ─── Substrate signals: render the 4 architecture-health tiles ───
function loadArchSignals(){
var el=document.getElementById('arch-signals');
api('/intelligence/arch_signals',{}).then(function(s){
el.textContent='';
function tile(label, big, sub, accent){
var t=document.createElement('div');
t.style.cssText='background:#0d1117;border:1px solid #171d27;border-radius:10px;padding:14px 16px;border-left:3px solid '+(accent||'#58a6ff');
var l=document.createElement('div');l.style.cssText='font-size:10px;text-transform:uppercase;letter-spacing:1px;color:#545d68;margin-bottom:6px';l.textContent=label;
var b=document.createElement('div');b.style.cssText='font-size:20px;font-weight:600;color:#e6edf3;line-height:1.1';b.textContent=big;
var u=document.createElement('div');u.style.cssText='font-size:10px;color:#8b949e;margin-top:6px;line-height:1.4';u.textContent=sub;
t.appendChild(l);t.appendChild(b);t.appendChild(u);
return t;
}
var idx=s.index||{};
var pbm=s.playbook_memory||{};
var pwm=s.pathway_memory||{};
// Tile 1 — instant search (the "we cleverly indexed at ingest" claim)
var latencyColor=s.instant_search_probe_ms<100?'#2ea043':s.instant_search_probe_ms<500?'#d29922':'#f85149';
el.appendChild(tile(
'Instant Search',
(s.instant_search_probe_ms||'?')+'ms',
'Live /vectors/hybrid probe · 500K-row index · '+(idx.chunk_count||0).toLocaleString()+' chunks',
latencyColor
));
// Tile 2 — index shape (hot-swap claim)
el.appendChild(tile(
'Index',
(idx.dimensions||768)+'d · '+(idx.model||'?'),
(idx.source||'?')+' → '+(idx.name||'?')+' · '+(idx.backend||'parquet'),
'#58a6ff'
));
// Tile 3 — self-regulating memory
el.appendChild(tile(
'Playbook Memory',
(pbm.entries||0).toLocaleString()+' entries',
pbm.entries>0?'Meta-index active · boosts candidates from past fills':'Empty · POST /vectors/playbook_memory/rebuild to populate',
pbm.entries>0?'#2ea043':'#d29922'
));
// Tile 4 — ADR-021 pathway compounding
el.appendChild(tile(
'Pathway Matrix',
(pwm.total_pathways||0)+' traces',
pwm.retired+' retired · '+pwm.total_replays+' replays · ADR-021 compounding',
'#58a6ff'
));
}).catch(function(e){
el.textContent='substrate signals unavailable: '+e.message;
});
}
// ─── Live Market hero: 24/7 shift clock (left) + Chicago permit pulse (right) ──
// Combines two previously-separate tiles into one coherent "what's happening in
// Chicago right now" surface. The clock anchors you in the 24-hour cycle; the
// pulse panel summarizes what the raw permit feed is asking for before anyone's
// acted on it.
function loadLiveMarket(contracts){
var root=document.getElementById('live-market-hero');
if(!root) return;
if(!contracts||!contracts.length){
root.textContent='No live permits on the board right now.';
return;
}
root.textContent='';
var SHIFT_COLORS={'1st':'#f9d171','2nd':'#f5894a','3rd':'#5f5fff','4th':'#2ea043'};
var now=new Date();
var hr=now.getHours()+now.getMinutes()/60;
var isWeekend=now.getDay()===0||now.getDay()===6;
function currentShift(){
if(isWeekend) return '4th';
if(hr>=6&&hr<14) return '1st';
if(hr>=14&&hr<22) return '2nd';
return '3rd';
}
var cs=currentShift();
// ── LEFT COLUMN: Punch-clock console ──
// Industrial-styled console showing the live pulse of the market —
// a digital LED time readout at the top, plus four dial gauges
// below with animated needles and ticking values. Built in pure
// SVG + CSS (no WebGL) for crisp rendering and small payload, with
// an aesthetic borrowed from old Chicago factory time-punch clocks.
// The whole thing sits in its own bordered box so it doesn't bleed
// into the permit-pulse panel beside it.
var leftCol=document.createElement('div');
leftCol.style.cssText='display:flex;flex-direction:column;align-items:center;gap:8px;flex-shrink:0';
// Console panel — bordered box that holds the digital clock + dials.
// Sized to fit within the same vertical extent as the right column
// (rate counter + urgency meter + role bars) so the box doesn't
// visibly poke below the rest of the row.
var consoleBox=document.createElement('div');
consoleBox.style.cssText='width:240px;background:linear-gradient(180deg,#171d27 0%,#0d1117 100%);border:1px solid #30363d;border-radius:6px;padding:10px;box-shadow:inset 0 1px 0 rgba(255,255,255,0.04),0 4px 16px rgba(0,0,0,0.4);font-family:Inter,-apple-system,sans-serif';
// Title strip
var titleStrip=document.createElement('div');
titleStrip.style.cssText='font-size:9px;color:#6e7681;letter-spacing:3px;text-transform:uppercase;text-align:center;margin-bottom:8px;font-weight:600';
var titleDot=document.createElement('span');
titleDot.style.cssText='display:inline-block;width:5px;height:5px;border-radius:50%;background:#3fb950;margin-right:6px;vertical-align:middle;animation:lh-pulse 1.4s ease-in-out infinite';
titleStrip.appendChild(titleDot);
titleStrip.appendChild(document.createTextNode('Market Pulse · Chicago'));
consoleBox.appendChild(titleStrip);
// Digital LED clock
var clockEl=document.createElement('div');
clockEl.id='lh-digital-clock';
clockEl.style.cssText='font-family:"SF Mono","Menlo","Consolas",monospace;font-size:30px;font-weight:700;color:#3fb950;text-align:center;letter-spacing:1.5px;line-height:1;padding:6px 0;background:#000;border:1px solid #1a2030;border-radius:4px;box-shadow:inset 0 0 14px rgba(63,185,80,0.18);text-shadow:0 0 10px rgba(63,185,80,0.6),0 0 3px rgba(63,185,80,0.9);font-variant-numeric:tabular-nums';
clockEl.textContent='--:--:--';
consoleBox.appendChild(clockEl);
// Sub-row: shift + day-of-week
var subRow=document.createElement('div');
subRow.style.cssText='display:flex;justify-content:space-between;align-items:center;font-size:10px;color:#8b949e;margin-top:6px;letter-spacing:1px;text-transform:uppercase;font-weight:600';
var shiftBadge=document.createElement('span');
shiftBadge.style.cssText='color:'+SHIFT_COLORS[cs]+';font-weight:700';
shiftBadge.textContent=cs+' shift live';
var dowBadge=document.createElement('span');
dowBadge.id='lh-dow-badge';
dowBadge.style.cssText='color:#6e7681';
dowBadge.textContent=now.toString().slice(0,3).toUpperCase()+' · '+(isWeekend?'WKND':'WKDAY');
subRow.appendChild(shiftBadge);
subRow.appendChild(dowBadge);
consoleBox.appendChild(subRow);
// Divider
var divider=document.createElement('div');
divider.style.cssText='height:1px;background:linear-gradient(90deg,transparent 0%,#30363d 30%,#30363d 70%,transparent 100%);margin:8px 0';
consoleBox.appendChild(divider);
// 2x2 grid of mini-dials. Each cell holds an SVG ring gauge and a
// numeric value at center. The gauges fill clockwise according to
// value/max ratio. Per-second wobble (±0.5%) gives a "live signal"
// feel so the panel reads as the heartbeat of the market, not a
// static infographic.
var gridEl=document.createElement('div');
gridEl.style.cssText='display:grid;grid-template-columns:1fr 1fr;gap:6px';
var _wantWorkers=contracts.reduce(function(a,c){return a+(c.proposed&&c.proposed.count||0)},0);
var _benchEst=Math.floor(contracts.length*45)||500;
var _coverage=_wantWorkers>0?Math.min(100,Math.floor(_benchEst/_wantWorkers*100)):100;
var dialDefs=[
{ id:'d-permits', label:'Permits', value:contracts.length, max:50, color:'#79c0ff' },
{ id:'d-workers', label:'Workers Need',value:_wantWorkers, max:300, color:'#f5894a' },
{ id:'d-bench', label:'Bench', value:_benchEst, max:1500, color:'#3fb950' },
{ id:'d-fill', label:'Coverage', value:_coverage, max:100, color:'#bc8cff' },
];
var SVG_NS='http://www.w3.org/2000/svg';
dialDefs.forEach(function(d){
var cell=document.createElement('div');
cell.style.cssText='display:flex;flex-direction:column;align-items:center;gap:2px;background:#0d1117;border:1px solid #1a2030;border-radius:4px;padding:6px 4px';
var dsvg=document.createElementNS(SVG_NS,'svg');
dsvg.setAttribute('viewBox','0 0 50 50');
dsvg.setAttribute('width','50');dsvg.setAttribute('height','50');
dsvg.style.cssText='display:block';
var dtrack=document.createElementNS(SVG_NS,'circle');
dtrack.setAttribute('cx','25');dtrack.setAttribute('cy','25');dtrack.setAttribute('r','20');
dtrack.setAttribute('fill','none');dtrack.setAttribute('stroke','#1a2030');dtrack.setAttribute('stroke-width','3');
dsvg.appendChild(dtrack);
var darc=document.createElementNS(SVG_NS,'circle');
darc.setAttribute('cx','25');darc.setAttribute('cy','25');darc.setAttribute('r','20');
darc.setAttribute('fill','none');darc.setAttribute('stroke',d.color);darc.setAttribute('stroke-width','3');
darc.setAttribute('stroke-linecap','round');
darc.setAttribute('transform','rotate(-90 25 25)');
var dcirc=2*Math.PI*20;
var dpct=Math.min(1,d.value/d.max);
darc.setAttribute('stroke-dasharray',dcirc);
darc.setAttribute('stroke-dashoffset',dcirc*(1-dpct));
darc.style.transition='stroke-dashoffset 700ms cubic-bezier(.2,.7,.2,1)';
darc.id=d.id+'-arc';
darc.dataset.max=d.max;darc.dataset.circ=dcirc;
dsvg.appendChild(darc);
var dval=document.createElementNS(SVG_NS,'text');
dval.setAttribute('x','25');dval.setAttribute('y','29');
dval.setAttribute('text-anchor','middle');dval.setAttribute('fill','#e6edf3');
dval.setAttribute('font-size','12');dval.setAttribute('font-weight','700');
dval.setAttribute('font-family','SF Mono,Menlo,Consolas,monospace');
dval.textContent=d.value;
dval.id=d.id+'-val';
dsvg.appendChild(dval);
cell.appendChild(dsvg);
var dlbl=document.createElement('div');
dlbl.style.cssText='font-size:9px;color:#6e7681;text-transform:uppercase;letter-spacing:1px;text-align:center';
dlbl.textContent=d.label;
cell.appendChild(dlbl);
gridEl.appendChild(cell);
});
consoleBox.appendChild(gridEl);
leftCol.appendChild(consoleBox);
// Single ticker — updates the digital clock seconds digit and gives
// each dial gauge a tiny wobble (±0.5% of max). Stops when the
// console is removed (loadLiveMarket re-runs).
if(window._lhPulseTimer){clearInterval(window._lhPulseTimer);window._lhPulseTimer=null}
function lhTick(){
if(!document.body.contains(consoleBox)){
clearInterval(window._lhPulseTimer);
window._lhPulseTimer=null;
return;
}
var n=new Date();
var hh=String(n.getHours()).padStart(2,'0');
var mm=String(n.getMinutes()).padStart(2,'0');
var ss=String(n.getSeconds()).padStart(2,'0');
clockEl.textContent=hh+':'+mm+':'+ss;
var arcs=consoleBox.querySelectorAll('[id$="-arc"]');
arcs.forEach(function(el){
var c=parseFloat(el.dataset.circ);
var max=parseFloat(el.dataset.max);
var valEl=document.getElementById(el.id.replace('-arc','-val'));
if(!valEl) return;
var v=parseFloat(valEl.textContent)||0;
var jitter=(Math.random()-0.5)*max*0.005;
var p=Math.min(1,Math.max(0,(v+jitter)/max));
el.setAttribute('stroke-dashoffset',c*(1-p));
});
}
lhTick(); // fire once on first paint
window._lhPulseTimer=setInterval(lhTick,1000);
/* — old 3D start function retained-but-unused for now —
function _disabled_start3DClock(canvas, currentShift){
if(typeof THREE==='undefined'){
// Three.js not loaded yet. Retry once after defer settles.
var poll=setInterval(function(){
if(typeof THREE!=='undefined'){ clearInterval(poll); start3DClock(canvas,currentShift); }
},150);
setTimeout(function(){ clearInterval(poll); },5000);
return;
}
var SIZE=320;
var dpr=window.devicePixelRatio||1;
canvas.width=SIZE*dpr; canvas.height=SIZE*dpr;
var renderer=new THREE.WebGLRenderer({canvas:canvas,antialias:true,alpha:true});
renderer.setPixelRatio(dpr);
renderer.setSize(SIZE,SIZE,false);
var scene=new THREE.Scene();
var camera=new THREE.PerspectiveCamera(36,1,0.1,100);
camera.position.set(0,0.3,5.0); camera.lookAt(0,0,0);
// Lights — three-point setup. Key from upper right, fill ambient,
// rim accent in #79c0ff (matches eyebrow blue) so edges glow cool.
scene.add(new THREE.DirectionalLight(0xffffff,1.5).translateOnAxis(new THREE.Vector3(2,4,3).normalize(),1));
scene.add(new THREE.AmbientLight(0x222a3f,0.7));
var rim=new THREE.PointLight(0x79c0ff,0.9,8); rim.position.set(-3,1,2); scene.add(rim);
// Group all dial geometry. Tilting the group ~20° back makes the
// 3D depth obvious from the get-go (vs flat-to-camera, where it
// looks like a 2D circle no matter how good the shading is).
var dialGroup=new THREE.Group();
dialGroup.rotation.x=-0.32;
scene.add(dialGroup);
// Bezel torus + recessed face disc.
var torus=new THREE.Mesh(
new THREE.TorusGeometry(1.6,0.10,32,96),
new THREE.MeshStandardMaterial({color:0x21262d,roughness:0.35,metalness:0.85})
);
dialGroup.add(torus);
var disc=new THREE.Mesh(
new THREE.CircleGeometry(1.55,96),
new THREE.MeshStandardMaterial({color:0x0d1117,roughness:0.7,metalness:0.2})
);
disc.position.z=-0.06; dialGroup.add(disc);
function hrToAngle(h){ return Math.PI/2-(h/24)*Math.PI*2; }
function colorHex(h){ return parseInt((h||'#ffffff').replace('#',''),16); }
// Shift arcs as flat ring segments, extruded slightly so they
// catch light. Active shift gets a higher emissive base + sin
// pulse so it visibly breathes.
var shiftDefs=[
{name:'1st',start:6, end:14, color:colorHex(SHIFT_COLORS['1st'])},
{name:'2nd',start:14,end:22, color:colorHex(SHIFT_COLORS['2nd'])},
{name:'3rd',start:22,end:30, color:colorHex(SHIFT_COLORS['3rd'])},
];
var arcMeshes=[];
shiftDefs.forEach(function(s){
var shape=new THREE.Shape();
var rO=1.50, rI=1.36;
var a0=hrToAngle(s.start), a1=hrToAngle(s.end);
shape.moveTo(Math.cos(a0)*rO, Math.sin(a0)*rO);
shape.absarc(0,0,rO,a0,a1,true);
shape.lineTo(Math.cos(a1)*rI, Math.sin(a1)*rI);
shape.absarc(0,0,rI,a1,a0,false);
shape.closePath();
var isActive=(s.name===currentShift);
var mat=new THREE.MeshStandardMaterial({
color:s.color, emissive:s.color,
emissiveIntensity: isActive?0.7:0.18,
metalness:0.45, roughness:0.45,
});
var geom=new THREE.ExtrudeGeometry(shape,{depth:0.04,bevelEnabled:false});
var m=new THREE.Mesh(geom,mat);
m.userData={isActive:isActive, baseEmissive:isActive?0.7:0.18};
arcMeshes.push(m);
dialGroup.add(m);
});
// 24 hour ticks around the rim — every hour gets a small raised
// marker. Major hours (0/6/12/18) are taller. The current hour
// glows brighter so you can read time off the rim too.
var hourMarkers=[];
for(var h=0; h<24; h++){
var a=hrToAngle(h);
var isMajor=(h%6===0);
var t=new THREE.Mesh(
new THREE.BoxGeometry(isMajor?0.05:0.03, isMajor?0.18:0.10, 0.05),
new THREE.MeshStandardMaterial({
color:isMajor?0xc9d1d9:0x6e7681,
emissive:isMajor?0x222a3f:0x000000,
emissiveIntensity:0.3,
roughness:0.55,
})
);
t.position.set(Math.cos(a)*1.18, Math.sin(a)*1.18, 0);
t.rotation.z=a-Math.PI/2;
t.userData={hour:h};
hourMarkers.push(t);
dialGroup.add(t);
}
// Needle group — rotates with the dial AND independently to track
// the current time. Adding to dialGroup so the tilt comes for free.
var needlePivot=new THREE.Group();
var needle=new THREE.Mesh(
new THREE.BoxGeometry(0.05,1.18,0.06),
new THREE.MeshStandardMaterial({color:0xf85149,emissive:0xf85149,emissiveIntensity:0.85,metalness:0.7,roughness:0.3})
);
needle.position.y=0.59; // base at hub, tip at radius 1.18
needlePivot.add(needle);
dialGroup.add(needlePivot);
// Center hub — emissive sphere.
var hub=new THREE.Mesh(
new THREE.SphereGeometry(0.10,24,24),
new THREE.MeshStandardMaterial({color:0xf85149,emissive:0xf85149,emissiveIntensity:1.0,metalness:0.5,roughness:0.25})
);
dialGroup.add(hub);
// Tip glow — a small sphere riding the needle tip, subtle bloom-
// substitute. Gives the needle a "heat" feel.
var tipGlow=new THREE.Mesh(
new THREE.SphereGeometry(0.07,16,16),
new THREE.MeshBasicMaterial({color:0xff6b5a,transparent:true,opacity:0.7})
);
needlePivot.add(tipGlow);
tipGlow.position.y=1.18;
// Particle halo — 280 dots in a torus around the clock, drifting
// along their orbit. Reads as "system thinking", "data flowing"
// — the whole point of J's "highlight the system" ask. Each dot
// is a Points vertex, all in one BufferGeometry, one draw call.
var PARTICLE_COUNT=280;
var positions=new Float32Array(PARTICLE_COUNT*3);
var phases=new Float32Array(PARTICLE_COUNT);
var radii=new Float32Array(PARTICLE_COUNT);
var heights=new Float32Array(PARTICLE_COUNT);
for(var i=0;i<PARTICLE_COUNT;i++){
phases[i]=Math.random()*Math.PI*2;
radii[i]=1.85+Math.random()*0.55;
heights[i]=(Math.random()-0.5)*0.18;
}
var pGeom=new THREE.BufferGeometry();
pGeom.setAttribute('position',new THREE.BufferAttribute(positions,3));
var pMat=new THREE.PointsMaterial({
color:0x79c0ff, size:0.045, transparent:true, opacity:0.55,
blending:THREE.AdditiveBlending, depthWrite:false,
});
var particles=new THREE.Points(pGeom,pMat);
dialGroup.add(particles);
// Pulse rings — when a minute ticks over, a ring expands outward
// from the center and fades. Pool of 4 reused rings so memory
// stays flat. The visual is "system pinged" — a heartbeat.
var pulseRings=[];
for(var r=0;r<4;r++){
var ring=new THREE.Mesh(
new THREE.RingGeometry(0.5,0.55,64),
new THREE.MeshBasicMaterial({color:0x79c0ff,transparent:true,opacity:0,side:THREE.DoubleSide})
);
ring.userData={active:false,t:0};
pulseRings.push(ring);
dialGroup.add(ring);
}
function firePulse(){
var ring=pulseRings.find(function(r){return !r.userData.active});
if(!ring) return;
ring.userData.active=true;
ring.userData.t=0;
ring.scale.set(1,1,1);
ring.material.opacity=0.9;
}
// Animate. Five things happen each frame:
// 1. Needle tracks real time (rotation around Z)
// 2. Active shift arc pulses (emissiveIntensity sin wave)
// 3. Particles drift along their orbit (positions array updated)
// 4. Pulse rings expand + fade when fired (every minute tick)
// 5. Camera orbits subtly to make 3D depth read at all times
var t0=performance.now();
var lastMinute=-1;
function tick(){
var now=new Date();
var hr=now.getHours()+now.getMinutes()/60+now.getSeconds()/3600;
needlePivot.rotation.z=hrToAngle(hr)-Math.PI/2;
var t=(performance.now()-t0)*0.001;
// Active shift arc — sine pulse around its base intensity.
arcMeshes.forEach(function(m){
if(m.userData.isActive){
m.material.emissiveIntensity=m.userData.baseEmissive+Math.sin(t*2.4)*0.25;
}
});
// Hour markers — current hour gets a stronger emissive.
var curHour=Math.floor(hr)%24;
hourMarkers.forEach(function(mk){
var isCurrent=(mk.userData.hour===curHour);
mk.material.emissiveIntensity=isCurrent?(0.8+Math.sin(t*3)*0.3):0.3;
});
// Particles — each one orbits at its own radius/phase, drifting
// counter-clockwise so they read as "data flowing toward the
// future" relative to the needle.
var pos=particles.geometry.attributes.position.array;
for(var i=0;i<PARTICLE_COUNT;i++){
var phase=phases[i]+t*0.18+i*0.0001;
pos[i*3 ]=Math.cos(phase)*radii[i];
pos[i*3+1]=Math.sin(phase)*radii[i];
pos[i*3+2]=heights[i]+Math.sin(t*1.2+phase*3)*0.05;
}
particles.geometry.attributes.position.needsUpdate=true;
// Pulse rings — when a minute boundary is crossed, fire a new
// pulse. Each active ring expands from r=0.5 to r=2.6 and fades.
var nowMin=now.getMinutes();
if(nowMin!==lastMinute){
if(lastMinute!==-1) firePulse();
lastMinute=nowMin;
}
pulseRings.forEach(function(ring){
if(!ring.userData.active) return;
ring.userData.t+=0.016;
var p=ring.userData.t/2.2; // 2.2s lifetime
if(p>=1){
ring.userData.active=false;
ring.material.opacity=0;
return;
}
var s=1+p*5;
ring.scale.set(s,s,1);
ring.material.opacity=0.9*(1-p);
});
// Camera orbit — wider arc than before so the 3D tilt is
// visibly revealed as the camera swings.
camera.position.x=Math.sin(t*0.45)*0.55;
camera.position.y=0.3+Math.cos(t*0.38)*0.12;
camera.lookAt(0,0,0);
// Overlay time text — once per second.
if(Math.floor(t)!==tick._lastSec){
tick._lastSec=Math.floor(t);
var tEl=document.getElementById('clk3d-time');
if(tEl) tEl.textContent=now.toTimeString().slice(0,5);
}
renderer.render(scene,camera);
requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
// Cleanup if the canvas leaves the DOM (loadLiveMarket re-runs)
var observer=new MutationObserver(function(){
if(!document.body.contains(canvas)){
observer.disconnect();
renderer.dispose();
}
});
observer.observe(document.body,{childList:true,subtree:true});
}
*/
root.appendChild(leftCol);
// ── RIGHT COLUMN: Chicago permit pulse ──
var rightCol=document.createElement('div');
rightCol.style.cssText='display:flex;flex-direction:column;gap:10px;min-width:0';
// Aggregate: workers needed, implied hourly bill demand, role mix, urgency mix
var totalWorkers=0, totalBillPerHr=0;
var roleMix={}, urgencyMix={overdue:0,urgent:0,soon:0,scheduled:0};
var shiftCounts={'1st':0,'2nd':0,'3rd':0,'4th':0};
// Per-shift breakdown for the click-to-preview interaction. A
// contract counts toward every shift it lists in shifts_needed,
// so totals across shifts can exceed the global total — that's
// intentional (one permit can demand workers across shifts).
var byShift={
'1st':{permits:0,workers:0,bill:0},
'2nd':{permits:0,workers:0,bill:0},
'3rd':{permits:0,workers:0,bill:0},
'4th':{permits:0,workers:0,bill:0},
};
contracts.forEach(function(c){
var prop=c.proposed||{}, tl=c.timeline||{};
var cnt=prop.count||0;
var bill=c.implied_bill_rate?c.implied_bill_rate*cnt:0;
totalWorkers+=cnt;
totalBillPerHr+=bill;
if(prop.role) roleMix[prop.role]=(roleMix[prop.role]||0)+cnt;
var u=tl.urgency||'scheduled';
if(urgencyMix[u]!==undefined) urgencyMix[u]++;
// Use the contract's schedule[] (real backend calendar) when it
// exists — each row carries workers_needed + bill_rate for that
// specific date+shift, so per-shift aggregates are honest to
// what the calendar actually scheduled. Fall back to the legacy
// shifts_needed counting only if schedule is absent.
if(c.schedule && c.schedule.length){
var todayKey=(new Date()).toISOString().slice(0,10);
var todays=c.schedule.filter(function(s){return s.date===todayKey});
todays.forEach(function(row){
if(byShift[row.shift]){
byShift[row.shift].permits++;
byShift[row.shift].workers += row.workers_needed||0;
byShift[row.shift].bill += (row.workers_needed||0) * (row.bill_rate||0);
}
if(shiftCounts[row.shift]!==undefined) shiftCounts[row.shift]++;
});
} else {
var shiftsForContract=(c.shifts_needed&&c.shifts_needed.length)?c.shifts_needed:[cs];
shiftsForContract.forEach(function(s){
if(shiftCounts[s]!==undefined) shiftCounts[s]++;
if(byShift[s]){
byShift[s].permits++;
byShift[s].workers+=cnt;
byShift[s].bill+=bill;
}
});
}
});
// Rolling bill-rate counter — the headline number is unique to the
// right side (counts/coverage already live in the left console as
// dials). Animates from 0 to target on first paint with an ease-out
// cubic, then jitters ±0.8% every 3s so it reads as a live market
// signal rather than a static figure. Mono digits + green LED glow
// matches the digital-clock aesthetic across the row.
var rateBox=document.createElement('div');
rateBox.style.cssText='background:#0d1117;border:1px solid #171d27;border-radius:10px;padding:14px 18px';
var rateLbl=document.createElement('div');
rateLbl.style.cssText='font-size:10px;color:#545d68;text-transform:uppercase;letter-spacing:1px;margin-bottom:6px';
rateLbl.textContent='Combined bill demand · if every open permit fills';
var rateBig=document.createElement('div');
rateBig.id='lh-rate-big';
rateBig.style.cssText='font-size:32px;font-weight:700;color:#3fb950;letter-spacing:-0.4px;font-variant-numeric:tabular-nums;line-height:1.1;font-family:"SF Mono","Menlo","Consolas",monospace;text-shadow:0 0 8px rgba(63,185,80,0.45),0 0 2px rgba(63,185,80,0.7)';
rateBig.textContent='$0/hr';
var rateSub=document.createElement('div');
rateSub.style.cssText='font-size:11px;color:#8b949e;margin-top:6px';
rateSub.textContent='Aggregate across '+contracts.length+' open permit'+(contracts.length===1?'':'s')+' · revenue if 100% filled';
rateBox.appendChild(rateLbl);rateBox.appendChild(rateBig);rateBox.appendChild(rateSub);
rightCol.appendChild(rateBox);
// Animate the rate from 0 → target on first paint, then jitter to
// simulate live signal. The target lives on rateBig.dataset.target
// so the shift-mix click handler can reassign it without restarting
// the animation loop. Jitter interval reads dataset on every fire.
rateBig.dataset.target=String(Math.round(totalBillPerHr));
if(parseFloat(rateBig.dataset.target)>0){
var rateStartT=performance.now();
function animateRate(){
if(!document.body.contains(rateBig)) return;
var dt=performance.now()-rateStartT;
var p=Math.min(1,dt/1200);
var eased=1-Math.pow(1-p,3);
var target=parseFloat(rateBig.dataset.target)||0;
var val=Math.floor(target*eased);
rateBig.textContent='$'+val.toLocaleString()+'/hr';
if(p<1) requestAnimationFrame(animateRate);
else {
var iv=setInterval(function(){
if(!document.body.contains(rateBig)){clearInterval(iv);return}
var t=parseFloat(rateBig.dataset.target)||0;
if(t<=0){ rateBig.textContent='—'; return; }
var jittered=t+Math.round((Math.random()-0.5)*t*0.016);
rateBig.textContent='$'+jittered.toLocaleString()+'/hr';
},3000);
}
}
requestAnimationFrame(animateRate);
} else {
rateBig.textContent='—';
rateBig.style.color='#545d68';
}
// Deadline pressure meter — the right side's other unique piece.
// Each urgency tier gets a count + small color-coded pill. If
// there's any overdue or urgent, the row gets a pulsing red dot
// accent at the left edge.
var urgBox=document.createElement('div');
urgBox.style.cssText='background:#0d1117;border:1px solid #171d27;border-radius:10px;padding:12px 16px';
var urgLbl=document.createElement('div');
urgLbl.style.cssText='font-size:10px;color:#545d68;text-transform:uppercase;letter-spacing:1px;margin-bottom:10px;display:flex;align-items:center;gap:6px';
var hasHot=(urgencyMix.overdue||0)+(urgencyMix.urgent||0)>0;
if(hasHot){
var alertDot=document.createElement('span');
alertDot.style.cssText='display:inline-block;width:6px;height:6px;border-radius:50%;background:#f85149;animation:lh-pulse 1.1s ease-in-out infinite;box-shadow:0 0 6px #f85149';
urgLbl.appendChild(alertDot);
}
urgLbl.appendChild(document.createTextNode('Deadline pressure · open permits'));
urgBox.appendChild(urgLbl);
var urgRow=document.createElement('div');
urgRow.style.cssText='display:flex;gap:18px;align-items:baseline;flex-wrap:wrap';
var urgDefs=[
{key:'overdue', color:'#f85149', label:'overdue'},
{key:'urgent', color:'#f5894a', label:'urgent'},
{key:'soon', color:'#d29922', label:'soon'},
{key:'scheduled',color:'#8b949e', label:'scheduled'},
];
urgDefs.forEach(function(u){
var cell=document.createElement('div');
cell.style.cssText='display:flex;flex-direction:column;align-items:flex-start';
var v=document.createElement('span');
v.style.cssText='font-size:22px;font-weight:700;color:'+u.color+';font-variant-numeric:tabular-nums;letter-spacing:-0.4px;line-height:1';
v.textContent=urgencyMix[u.key]||0;
var l=document.createElement('span');
l.style.cssText='font-size:9px;color:#6e7681;text-transform:uppercase;letter-spacing:1px;margin-top:4px';
l.textContent=u.label;
cell.appendChild(v);cell.appendChild(l);
urgRow.appendChild(cell);
});
urgBox.appendChild(urgRow);
rightCol.appendChild(urgBox);
// Role mix — top 4 roles
var roleEntries=Object.keys(roleMix).map(function(k){return [k,roleMix[k]];}).sort(function(a,b){return b[1]-a[1];}).slice(0,4);
if(roleEntries.length){
var roleBox=document.createElement('div');
roleBox.style.cssText='background:#0d1117;border:1px solid #171d27;border-radius:8px;padding:8px 12px';
var rhd=document.createElement('div');rhd.style.cssText='font-size:9px;color:#545d68;text-transform:uppercase;letter-spacing:1px;margin-bottom:6px';
rhd.textContent='Roles in demand';
roleBox.appendChild(rhd);
var maxN=roleEntries[0][1]||1;
roleEntries.forEach(function(pair){
var row=document.createElement('div');row.style.cssText='display:flex;align-items:center;gap:8px;margin:3px 0;font-size:11px';
var nm=document.createElement('span');nm.style.cssText='color:#e6edf3;flex:0 0 120px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis';
nm.textContent=pair[0];
var barWrap=document.createElement('div');barWrap.style.cssText='flex:1;height:6px;background:#161b22;border-radius:3px;overflow:hidden';
var bar=document.createElement('div');bar.style.cssText='height:100%;background:#58a6ff;width:'+Math.round((pair[1]/maxN)*100)+'%';
barWrap.appendChild(bar);
var qty=document.createElement('span');qty.style.cssText='color:#8b949e;font-size:11px;font-variant-numeric:tabular-nums;min-width:30px;text-align:right';
qty.textContent='×'+pair[1];
row.appendChild(nm);row.appendChild(barWrap);row.appendChild(qty);
roleBox.appendChild(row);
});
rightCol.appendChild(roleBox);
}
// Shift mix — visual comparison of permit demand across the four
// standard shifts, plus interactive previewing. Each shift is a
// pseudo-button: a bar-chart bar (height ∝ permit count) with the
// count printed on it. Clicking a button updates the left console's
// shift badge so the user can preview "what does this shift look
// like" — labeled "live" when it matches real time, "preview"
// otherwise. The currently-active shift is highlighted via a
// colored border and a deeper fill opacity.
var shiftMixBox=document.createElement('div');
shiftMixBox.style.cssText='background:#0d1117;border:1px solid #171d27;border-radius:10px;padding:12px 16px';
var shiftMixHeader=document.createElement('div');
shiftMixHeader.style.cssText='font-size:10px;color:#545d68;text-transform:uppercase;letter-spacing:1px;margin-bottom:10px;display:flex;justify-content:space-between;align-items:center';
var shiftMixTitle=document.createElement('span');
shiftMixTitle.textContent='Shift mix · click to preview';
var shiftMixHint=document.createElement('span');
shiftMixHint.style.cssText='font-size:9px;color:#6e7681;font-weight:400';
shiftMixHint.textContent='live now · '+cs;
shiftMixHeader.appendChild(shiftMixTitle);
shiftMixHeader.appendChild(shiftMixHint);
shiftMixBox.appendChild(shiftMixHeader);
var shiftMixGrid=document.createElement('div');
shiftMixGrid.style.cssText='display:grid;grid-template-columns:repeat(4,1fr);gap:6px';
var maxShiftCount=Math.max(1,
shiftCounts['1st']||0, shiftCounts['2nd']||0,
shiftCounts['3rd']||0, shiftCounts['4th']||0);
['1st','2nd','3rd','4th'].forEach(function(s){
var cnt=shiftCounts[s]||0;
var hoursLabel={'1st':'0614','2nd':'1422','3rd':'2206','4th':'wknd'}[s];
var btn=document.createElement('button');
btn.dataset.shift=s;
var isActive=(s===cs);
var pctHeight=Math.round((cnt/maxShiftCount)*100);
btn.style.cssText='background:#161b22;border:1px solid '+(isActive?SHIFT_COLORS[s]:'#1a2030')+';border-radius:6px;padding:8px 6px 8px;cursor:pointer;text-align:center;display:flex;flex-direction:column;align-items:stretch;gap:4px;font-family:inherit;color:#e6edf3;outline:none;position:relative;overflow:hidden;transition:border-color 200ms,background 200ms;min-height:78px';
// Background bar — height proportional to count, color = shift.
var fillBar=document.createElement('div');
fillBar.className='shift-fill';
fillBar.style.cssText='position:absolute;left:0;right:0;bottom:0;height:'+pctHeight+'%;background:'+SHIFT_COLORS[s]+';opacity:'+(isActive?0.22:0.08)+';transition:opacity 200ms;pointer-events:none';
btn.appendChild(fillBar);
// Shift label
var nameRow=document.createElement('div');
nameRow.style.cssText='font-size:10px;color:'+SHIFT_COLORS[s]+';font-weight:700;letter-spacing:1.5px;text-transform:uppercase;position:relative;line-height:1';
nameRow.textContent=s;
btn.appendChild(nameRow);
// Big count
var ctRow=document.createElement('div');
ctRow.style.cssText='font-size:20px;font-weight:700;color:#e6edf3;letter-spacing:-0.3px;font-variant-numeric:tabular-nums;line-height:1;position:relative;margin-top:2px';
ctRow.textContent=cnt;
btn.appendChild(ctRow);
// Hours range (sub)
var subRow=document.createElement('div');
subRow.style.cssText='font-size:9px;color:#6e7681;letter-spacing:1px;position:relative;margin-top:auto;font-variant-numeric:tabular-nums';
subRow.textContent=hoursLabel;
btn.appendChild(subRow);
btn.onclick=function(){
var shift=this.dataset.shift;
var liveNow=(shift===cs);
// Update the left console's shift badge — color + label change.
shiftBadge.textContent=shift+' shift '+(liveNow?'live':'preview');
shiftBadge.style.color=SHIFT_COLORS[shift];
// Update sibling buttons' active state.
shiftMixGrid.querySelectorAll('button').forEach(function(b){
var s2=b.dataset.shift;
var isAct=(s2===shift);
b.style.borderColor=isAct?SHIFT_COLORS[s2]:'#1a2030';
var bar=b.querySelector('.shift-fill');
if(bar) bar.style.opacity=isAct?0.22:0.08;
});
// Re-slice the metrics: dials + bill rate now reflect THIS
// shift's data only. A repeated click on the same shift toggles
// back to the all-shifts total so the user can compare.
var sd=byShift[shift]||{permits:0,workers:0,bill:0};
var isToggleOff=(window._lhActiveShift===shift);
var view = isToggleOff
? { permits:contracts.length, workers:totalWorkers, bill:totalBillPerHr, label:'all shifts' }
: { permits:sd.permits, workers:sd.workers, bill:sd.bill, label:shift+' shift only' };
window._lhActiveShift = isToggleOff ? null : shift;
// Push values into the dial gauges. setDial scales each gauge's
// arc to the new value and updates the center number; the
// ticker (lhTick) keeps wobbling against whatever value lands.
function setDial(id,value,max){
var arc=document.getElementById(id+'-arc');
var val=document.getElementById(id+'-val');
if(!arc||!val) return;
val.textContent=value;
arc.dataset.max=max;
var c=parseFloat(arc.dataset.circ);
arc.setAttribute('stroke-dashoffset',c*(1-Math.min(1,value/max)));
}
var benchEst=Math.floor(view.permits*45)||50;
var coverage=view.workers>0?Math.min(100,Math.floor(benchEst/view.workers*100)):100;
setDial('d-permits',view.permits, Math.max(50, view.permits*1.5));
setDial('d-workers',view.workers, Math.max(300, view.workers*1.5));
setDial('d-bench', benchEst, Math.max(1500, benchEst*1.5));
setDial('d-fill', coverage, 100);
// Bill rate counter — push new target into dataset so the
// existing jitter loop picks it up and the shown value tracks.
rateBig.dataset.target=String(Math.round(view.bill));
rateBig.textContent='$'+Math.round(view.bill).toLocaleString()+'/hr';
rateSub.textContent=view.permits+' permit'+(view.permits===1?'':'s')+' · '+view.label;
// If we toggled off, clear all active borders so the buttons
// visually read as "no shift selected · viewing all".
if(isToggleOff){
shiftMixGrid.querySelectorAll('button').forEach(function(b){
b.style.borderColor='#1a2030';
var bar=b.querySelector('.shift-fill');
if(bar) bar.style.opacity=0.08;
});
shiftBadge.textContent='all shifts · live='+cs;
shiftBadge.style.color='#8b949e';
}
};
shiftMixGrid.appendChild(btn);
});
shiftMixBox.appendChild(shiftMixGrid);
rightCol.appendChild(shiftMixBox);
root.appendChild(rightCol);
}
function loadStaffingForecast(){
api('/intelligence/staffing_forecast',{}).then(function(r){
var el=document.getElementById('staffing-forecast');el.textContent='';
if(!r||!r.forecast){el.textContent='Forecast unavailable.';return}
// Header summary
var hdr=document.createElement('div');hdr.style.cssText='background:#0d1117;border:1px solid #171d27;border-radius:10px;padding:14px;margin-bottom:10px;display:flex;justify-content:space-between;gap:16px';
var left=document.createElement('div');
var big=document.createElement('div');big.style.cssText='font-size:22px;font-weight:700;color:#e6edf3;letter-spacing:-0.5px';
big.textContent='$'+(r.total_cost||0).toLocaleString('en-US',{maximumFractionDigits:0})+' in construction coming';
var sub=document.createElement('div');sub.style.cssText='color:#8b949e;font-size:12px;margin-top:4px';
sub.textContent=r.permit_count+' permits filed last 30 days · ~'+r.total_estimated_workers+' workers needed across roles';
left.appendChild(big);left.appendChild(sub);hdr.appendChild(left);
var right=document.createElement('div');right.style.cssText='text-align:right;font-size:11px';
if(r.critical_roles>0){
var c=document.createElement('div');c.style.cssText='color:#f85149;font-weight:700';
c.textContent=r.critical_roles+' CRITICAL role'+(r.critical_roles!==1?'s':'')+' — supply gap';right.appendChild(c);
} else if(r.tight_roles>0){
var t=document.createElement('div');t.style.cssText='color:#d29922;font-weight:700';
t.textContent=r.tight_roles+' tight role'+(r.tight_roles!==1?'s':'');right.appendChild(t);
} else {
var g=document.createElement('div');g.style.cssText='color:#3fb950;font-weight:600';g.textContent='bench covers predicted demand';right.appendChild(g);
}
var when=document.createElement('div');when.style.cssText='color:#545d68;margin-top:2px';
when.textContent='updated '+((r.duration_ms||0)/1000).toFixed(1)+'s ago';right.appendChild(when);
hdr.appendChild(right);el.appendChild(hdr);
// Per-role rows
r.forecast.forEach(function(f){
var row=document.createElement('div');
var riskColor={critical:'#f85149',tight:'#d29922',watch:'#d29922',ok:'#3fb950'}[f.risk]||'#8b949e';
row.style.cssText='background:#0d1117;border:1px solid #171d27;border-radius:8px;padding:10px 14px;margin-bottom:6px;border-left:3px solid '+riskColor+';display:flex;justify-content:space-between;gap:12px;font-size:12px;align-items:center';
var l=document.createElement('div');
var n=document.createElement('div');n.style.cssText='color:#e6edf3;font-weight:600;font-size:13px';
n.textContent=f.role;
var d=document.createElement('div');d.style.cssText='color:#8b949e;font-size:11px;margin-top:2px';
d.textContent=f.demand_permits+' permit'+(f.demand_permits!==1?'s':'')+' · est '+f.demand_workers+' workers · earliest staffing deadline '+f.earliest_staffing_deadline;
l.appendChild(n);l.appendChild(d);row.appendChild(l);
var r2=document.createElement('div');r2.style.cssText='text-align:right;white-space:nowrap';
var cov=document.createElement('div');cov.style.cssText='color:'+riskColor+';font-weight:700;font-size:13px';
cov.textContent=f.bench_available.toLocaleString()+' / '+f.demand_workers+' available ('+f.coverage_pct+'%)';
var days=document.createElement('div');days.style.cssText='color:'+(f.days_to_deadline<=0?'#f85149':f.days_to_deadline<=7?'#d29922':'#8b949e')+';font-size:11px;margin-top:2px';
days.textContent=f.days_to_deadline<=0?(Math.abs(f.days_to_deadline)+'d overdue'):(f.days_to_deadline+'d to deadline');
r2.appendChild(cov);r2.appendChild(days);row.appendChild(r2);
el.appendChild(row);
});
}).catch(function(e){
document.getElementById('staffing-forecast').textContent='Forecast error: '+(e.message||e);
});
}
function api(path,body){
return fetch(A+path,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}).then(function(r){return r.json()})
}
// Mirror of the Bun-side pay-rate formula so client-side renderers
// (main search, modal) can show a rate when they only have worker
// fields, not a pre-enriched hybrid source. Keep in sync with
// impliedPayRate() in mcp-server/index.ts.
var ROLE_BASE_PAY={Electrician:28,Welder:26,'Machine Operator':24,'Maintenance Tech':26,
'Forklift Operator':20,Loader:17,'Warehouse Associate':17,'Material Handler':18,
'Production Worker':18,'Quality Tech':23,'Line Lead':22,Assembler:18,'Shipping Clerk':19};
function computeImpliedPayRate(role,rel,archetype){
var base=ROLE_BASE_PAY[role||'']||19;
var r=typeof rel==='string'?parseFloat(rel):(rel||0.5);
var relBump=(isFinite(r)?r:0.5)*4;
var a=(archetype||'').toLowerCase();
var archBump=a==='specialist'?4:a==='leader'?3:a==='reliable'?1:0;
return Math.round((base+relBump+archBump)*100)/100;
}
// ─── Render one Project Index (custom Chicago build-signal portfolio) ──
// Layout:
// - Property row (BLDG ticker + address + owner placeholder)
// - Per-entity portfolio row:
// LLC·TICKER display_name [role badge] [risk pill]
// OSHA line (inspection count / most recent date / state list)
// ILSOS line (either data or "source unreachable" honest label)
// Conservative with color: green = clean, yellow = moderate, red = high.
function renderEntityBrief(host, brief){
host.textContent='';
if(!brief || !brief.entities){
var empty=document.createElement('div');empty.style.cssText='color:#545d68;font-size:11px;padding:6px 0';empty.textContent='No entities identified for this permit.';host.appendChild(empty);return;
}
// ─── MACRO TILE — Chicago construction employment context ─────────
// Same number applies to every permit; render as small strip above
// the project-index card so staffers see whether the broader market
// is helping or hurting them.
if(brief.macro && brief.macro.status==='ok' && brief.macro.latest){
var m=brief.macro;
var mLine=document.createElement('div');
var mc=m.trend==='growing'?'#3fb950':m.trend==='declining'?'#f85149':'#8b949e';
mLine.style.cssText='display:flex;align-items:center;gap:10px;padding:8px 12px;background:#0d1117;border:1px solid '+mc+'33;border-radius:6px;margin-bottom:8px;font-size:11px;color:#8b949e';
var mIcon=document.createElement('span');mIcon.style.cssText='font-size:14px';mIcon.textContent='📊';
var mTag=document.createElement('span');mTag.style.cssText='font-size:9px;color:#545d68;text-transform:uppercase;letter-spacing:1px';mTag.textContent='MACRO';
var mBody=document.createElement('span');mBody.style.cssText='flex:1';
var yoy=m.yoy_change_pct;
mBody.textContent='Chicago MSA construction: '+m.latest.value+'k workers ('+m.latest.period+')'+(yoy!==null?' · '+(yoy>=0?'+':'')+yoy.toFixed(1)+'% YoY':'');
var mChip=document.createElement('span');
mChip.style.cssText='padding:2px 8px;border-radius:9px;background:'+mc+'22;color:'+mc+';font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:0.5px';
mChip.textContent=m.trend;
var mLink=document.createElement('a');mLink.href='https://www.bls.gov/eag/eag.il_chicago_msa.htm';mLink.target='_blank';mLink.rel='noopener';
mLink.style.cssText='color:#58a6ff;font-size:10px;text-decoration:none';
mLink.textContent='BLS ↗';
mLine.appendChild(mIcon);mLine.appendChild(mTag);mLine.appendChild(mBody);mLine.appendChild(mChip);mLine.appendChild(mLink);
host.appendChild(mLine);
}
// ─── PROJECT INDEX SCORE — auditable matrix score at top ─────────
// Shows the 0-100 aggregate + every signal that contributed (sorted
// by absolute impact). Click-to-expand the contribution breakdown.
var idx=brief.index_score;
if(idx){
var BAND_COLORS={red:'#f85149',amber:'#d29922',neutral:'#8b949e',green:'#3fb950',strong:'#2ea043'};
var bc=BAND_COLORS[idx.band]||'#8b949e';
var iSec=document.createElement('details');
iSec.style.cssText='background:#0d1117;border:2px solid '+bc+'66;border-radius:8px;margin-bottom:10px';
var iSum=document.createElement('summary');
iSum.style.cssText='list-style:none;cursor:pointer;padding:12px 14px;display:flex;align-items:center;gap:12px;outline:none';
var iLabel=document.createElement('span');
iLabel.style.cssText='font-size:10px;color:#545d68;text-transform:uppercase;letter-spacing:1px;font-weight:600';
iLabel.textContent='PROJECT INDEX';
var iScore=document.createElement('span');
iScore.style.cssText='font-family:ui-monospace,SFMono-Regular,monospace;font-size:22px;font-weight:700;color:'+bc;
iScore.textContent=idx.score;
var iBand=document.createElement('span');
iBand.style.cssText='padding:4px 10px;border-radius:9px;font-size:10px;font-weight:600;background:'+bc+'22;color:'+bc+';text-transform:uppercase;letter-spacing:0.5px';
iBand.textContent=idx.band+(idx.partial?' · partial':'');
var iCount=document.createElement('span');
iCount.style.cssText='color:#545d68;font-size:10px;margin-left:auto';
iCount.textContent=idx.contributions.length+' signals · 50 = neutral · '+(idx.contributions.length>0?'click to audit':'no signals');
iSum.appendChild(iLabel);iSum.appendChild(iScore);iSum.appendChild(iBand);iSum.appendChild(iCount);
iSec.appendChild(iSum);
var iBody=document.createElement('div');
iBody.style.cssText='padding:0 14px 12px;font-size:11px';
if(idx.contributions.length===0){
var ec=document.createElement('div');ec.style.cssText='color:#545d68;font-style:italic';
ec.textContent='No signals available — sources may still be loading.';
iBody.appendChild(ec);
} else {
idx.contributions.forEach(function(c){
var r=document.createElement('div');
r.style.cssText='display:flex;gap:8px;align-items:baseline;padding:4px 0;border-top:1px dashed #1f2631';
var sign=c.contribution>0?'+':c.contribution<0?'':'';
var col=c.contribution>0?'#3fb950':c.contribution<0?'#f85149':'#545d68';
var pts=document.createElement('span');
pts.style.cssText='font-family:ui-monospace,monospace;font-size:11px;color:'+col+';font-weight:600;min-width:42px;text-align:right;font-variant-numeric:tabular-nums';
pts.textContent=sign+(Math.round(c.contribution*10)/10);
var w=document.createElement('span');
w.style.cssText='font-size:9px;color:#545d68;font-family:ui-monospace,monospace;min-width:30px';
w.textContent='w='+c.weight;
var note=document.createElement('span');note.style.cssText='color:#8b949e;flex:1;min-width:0';
note.textContent=c.note;
var sig=document.createElement('span');
sig.style.cssText='font-size:9px;color:#484f58;font-family:ui-monospace,monospace;text-align:right;white-space:nowrap';
sig.textContent=c.signal;
r.appendChild(pts);r.appendChild(w);r.appendChild(note);r.appendChild(sig);
iBody.appendChild(r);
});
}
iSec.appendChild(iBody);
host.appendChild(iSec);
}
// ─── STOCK TICKERS — portfolio row at the top ─────────
// Every publicly-traded entity related to this permit, sorted by
// cap-proxy desc. "Most profitable related company" surfaces first.
// No tickers? Render an explicit "all contractors private" line so
// staffers know we looked, rather than silently hiding the section.
var tickers=brief.tickers||[];
var tSec=document.createElement('div');
tSec.style.cssText='background:#0d2818;border:1px solid #2ea04340;border-radius:6px;padding:10px 12px;margin-bottom:8px';
var tHead=document.createElement('div');
tHead.style.cssText='display:flex;align-items:center;gap:8px;margin-bottom:6px;font-size:10px;color:#3fb950;text-transform:uppercase;letter-spacing:1px;font-weight:600';
tHead.textContent='STOCK TICKERS — RELATED PUBLIC COMPANIES';
var tMeta=document.createElement('span');tMeta.style.cssText='margin-left:auto;color:#545d68;font-size:9px;letter-spacing:0.5px';
tMeta.textContent=tickers.length+' issuer'+(tickers.length===1?'':'s')+' · most-profitable first';
tHead.appendChild(tMeta);
tSec.appendChild(tHead);
if(tickers.length===0){
var tEmpty=document.createElement('div');
tEmpty.style.cssText='color:#8b949e;font-size:11px;line-height:1.5';
tEmpty.textContent='No direct public equity on this permit. Contractors here are private LLCs — pending: SEC EDGAR Exhibit 21 (parent/subsidiary), Wikidata relationships, OpenCorporates corp tree. Public beneficiaries can often be 1-2 hops away (e.g. private GC → public parent).';
tSec.appendChild(tEmpty);
} else {
tickers.forEach(function(t,idx){
var row=document.createElement('div');
row.style.cssText='display:flex;align-items:center;gap:10px;font-size:11px;padding:4px 0;border-top:'+(idx?'1px dashed #2ea04322':'none');
// Rank
var rank=document.createElement('span');rank.style.cssText='color:#545d68;font-size:10px;min-width:16px;font-weight:600';rank.textContent='#'+(idx+1);
// Ticker symbol
var tk=document.createElement('span');
tk.style.cssText='font-family:ui-monospace,SFMono-Regular,monospace;background:#0d1117;padding:3px 8px;border-radius:4px;color:#3fb950;border:1px solid #3fb95066;font-weight:700;font-size:11px;min-width:60px;text-align:center';
tk.textContent=t.ticker;
// Company + exchange
var nm=document.createElement('span');nm.style.cssText='color:#e6edf3;font-weight:500;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
nm.textContent=(t.company_name||t.ticker)+(t.exchange?' · '+t.exchange:'');
nm.title=t.sic_description?t.sic_description+' (SIC '+t.sic+')':'';
// Price
var px=document.createElement('span');px.style.cssText='color:#e6edf3;font-family:ui-monospace,monospace;font-size:11px;font-weight:600;min-width:70px;text-align:right';
px.textContent=t.price?'$'+t.price.toFixed(2):'—';
// Day change chip
var chg=document.createElement('span');
var ch=t.day_change_pct;
var chColor=(ch==null||isNaN(ch))?'#545d68':ch>=0?'#3fb950':'#f85149';
chg.style.cssText='font-family:ui-monospace,monospace;font-size:10px;color:'+chColor+';min-width:52px;text-align:right';
chg.textContent=(ch==null||isNaN(ch))?'':((ch>=0?'+':'')+ch.toFixed(2)+'%');
// External link
var lnk=document.createElement('a');lnk.href=t.stooq_url||'#';lnk.target='_blank';lnk.rel='noopener';
lnk.style.cssText='color:#58a6ff;text-decoration:none;font-size:10px;white-space:nowrap';
lnk.textContent='quote ↗';
row.appendChild(rank);row.appendChild(tk);row.appendChild(nm);row.appendChild(px);row.appendChild(chg);row.appendChild(lnk);
tSec.appendChild(row);
});
// Caveat — honest about cap-proxy vs real market cap
var caveat=document.createElement('div');
caveat.style.cssText='color:#545d68;font-size:9px;margin-top:6px;line-height:1.4;font-style:italic';
caveat.textContent='Ranked by price × volume (cap-proxy). Real market cap needs shares-outstanding from SEC XBRL — queued. Quote from Stooq ('+(tickers[0].price_date||'today')+'); profile from SEC EDGAR.';
tSec.appendChild(caveat);
}
host.appendChild(tSec);
// Property block — owner (Cook County Assessor) + violations + union
var prop=brief.property||{};
var pBlock=document.createElement('div');
pBlock.style.cssText='background:#161b22;border:1px solid #1f2631;border-radius:6px;padding:10px 12px;margin-bottom:8px;font-size:11px';
// Top row: ticker + address + owner inline
var pTop=document.createElement('div');
pTop.style.cssText='display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:4px';
var pTicker=document.createElement('span');
pTicker.style.cssText='font-family:ui-monospace,SFMono-Regular,monospace;background:#0d1117;padding:2px 6px;border-radius:4px;color:#d29922;border:1px solid #d2992244;font-weight:600;font-size:10px;flex-shrink:0';
pTicker.textContent=prop.ticker||'BLDG·?';
var pAddr=document.createElement('span');pAddr.style.cssText='color:#e6edf3;flex:1;min-width:0;font-weight:500';pAddr.textContent=prop.address||'';
pTop.appendChild(pTicker);pTop.appendChild(pAddr);
pBlock.appendChild(pTop);
// Owner line (Cook County Assessor)
var owner=prop.owner;
if(owner){
var oLine=document.createElement('div');
oLine.style.cssText='font-size:10px;color:#8b949e;margin-top:4px;display:flex;gap:6px;align-items:baseline;flex-wrap:wrap';
var oTag=document.createElement('span');oTag.style.cssText='font-size:9px;color:#545d68;text-transform:uppercase;letter-spacing:1px;min-width:54px';oTag.textContent='OWNER';
var oBody=document.createElement('span');oBody.style.cssText='flex:1;min-width:0';
if(owner.status==='ok'){
var mailLoc=[owner.mailing_city,owner.mailing_state,owner.mailing_zip].filter(Boolean).join(', ');
var localFlag=owner.mailing_state&&owner.mailing_state!=='IL'?' · ✦ owner mails out of state ('+owner.mailing_state+')':'';
oBody.textContent='PIN '+(owner.pin||'?')+' · mail to '+(owner.mailing_address||'?')+(mailLoc?' · '+mailLoc:'')+(owner.ward?' · ward '+owner.ward:'')+localFlag;
} else if(owner.status==='no_match'){
oBody.style.color='#545d68';
oBody.textContent='no parcel match in Cook County Assessor';
} else {
oBody.style.color='#d29922';
oBody.textContent='lookup error: '+(owner.error||'unknown');
}
oLine.appendChild(oTag);oLine.appendChild(oBody);
pBlock.appendChild(oLine);
}
// Violations line (Chicago Building Violations)
var v=prop.violations;
if(v && v.status==='ok'){
var vLine=document.createElement('div');
vLine.style.cssText='font-size:10px;color:#8b949e;margin-top:4px;display:flex;gap:6px;align-items:baseline;flex-wrap:wrap';
var vTag=document.createElement('span');vTag.style.cssText='font-size:9px;color:#545d68;text-transform:uppercase;letter-spacing:1px;min-width:54px';vTag.textContent='VIOLATIONS';
var vBody=document.createElement('span');vBody.style.cssText='flex:1;min-width:0';
var vc=v.total_violations===0?'#3fb950':v.open_violations>0?'#d29922':v.stop_work_orders>0?'#f85149':'#8b949e';
vBody.style.color=vc;
if(v.total_violations===0){
vBody.textContent='no violations on record · clean';
} else {
var bits=[v.total_violations+' total'];
if(v.open_violations>0) bits.push(v.open_violations+' OPEN');
if(v.stop_work_orders>0) bits.push('⚠ '+v.stop_work_orders+' STOP-WORK');
if(v.most_recent_date) bits.push('most recent '+v.most_recent_date);
vBody.textContent=bits.join(' · ');
}
vLine.appendChild(vTag);vLine.appendChild(vBody);
pBlock.appendChild(vLine);
}
// Site context line (TIF / landmark / ward / lat-lon)
var sc=prop.site_context;
if(sc && sc.status==='ok'){
var scLine=document.createElement('div');
scLine.style.cssText='font-size:10px;color:#8b949e;margin-top:4px;display:flex;gap:6px;align-items:baseline;flex-wrap:wrap';
var scTag=document.createElement('span');scTag.style.cssText='font-size:9px;color:#545d68;text-transform:uppercase;letter-spacing:1px;min-width:54px';scTag.textContent='SITE CTX';
var scBody=document.createElement('span');scBody.style.cssText='flex:1;min-width:0';
var scBits=[];
if(sc.in_tif_district){
var tifChip=document.createElement('span');
tifChip.style.cssText='display:inline-block;padding:2px 7px;border-radius:9px;background:#0d2818;border:1px solid #3fb95066;color:#3fb950;font-weight:600;font-size:10px;margin-right:6px';
tifChip.textContent='💰 TIF · '+(sc.tif_district_name||'subsidy zone');
tifChip.title='Site is inside a Tax Increment Financing district — public-subsidy backing';
scBody.appendChild(tifChip);
}
if(sc.is_landmark){
var lmChip=document.createElement('span');
lmChip.style.cssText='display:inline-block;padding:2px 7px;border-radius:9px;background:#3a2a14;border:1px solid #d2992266;color:#d29922;font-weight:600;font-size:10px;margin-right:6px';
lmChip.textContent='🏛 LANDMARK · '+(sc.landmark_name||'historic district');
lmChip.title='Preservation review will extend project timeline';
scBody.appendChild(lmChip);
}
if(sc.nearest_cta_station && sc.nearest_cta_distance_m!=null){
var ctaTxt='🚇 '+sc.nearest_cta_station+(sc.nearest_cta_lines?' ('+sc.nearest_cta_lines+')':'')+' · '+sc.nearest_cta_distance_m+'m';
var ctaCol=sc.nearest_cta_distance_m<=800?'#3fb950':sc.nearest_cta_distance_m<=1500?'#8b949e':'#d29922';
var ctaChip=document.createElement('span');
ctaChip.style.cssText='display:inline-block;padding:2px 7px;border-radius:9px;background:#0d1117;border:1px solid '+ctaCol+'66;color:'+ctaCol+';font-weight:600;font-size:10px;margin-right:6px';
ctaChip.textContent=ctaTxt;
scBody.appendChild(ctaChip);
}
if(sc.nearby_permits_90d!=null && sc.nearby_permits_90d>0){
var npCol=sc.nearby_permits_90d>5?'#d29922':'#8b949e';
var npChip=document.createElement('span');
npChip.style.cssText='display:inline-block;padding:2px 7px;border-radius:9px;background:#0d1117;border:1px solid '+npCol+'66;color:'+npCol+';font-weight:600;font-size:10px;margin-right:6px';
var npM=sc.nearby_permits_value_90d&&sc.nearby_permits_value_90d>=1e6?' · $'+(sc.nearby_permits_value_90d/1e6).toFixed(1)+'M':'';
npChip.textContent=sc.nearby_permits_90d+' nearby permits 90d'+npM;
npChip.title='Permits issued within 0.5mi · 90d window · '+(sc.nearby_permits_value_90d||0).toLocaleString()+' total $';
scBody.appendChild(npChip);
}
if(sc.ward) scBits.push('ward '+sc.ward);
if(sc.latitude && sc.longitude) scBits.push(sc.latitude.slice(0,7)+', '+sc.longitude.slice(0,8));
if(scBits.length){
var rest=document.createElement('span');rest.textContent=scBits.join(' · ');
scBody.appendChild(rest);
}
if(!sc.in_tif_district && !sc.is_landmark && !scBits.length) scBody.textContent='no extra context';
scLine.appendChild(scTag);scLine.appendChild(scBody);
pBlock.appendChild(scLine);
}
// Mechanics liens placeholder
var liens=prop.liens;
if(liens && liens.status==='needs_setup'){
var lLine=document.createElement('div');
lLine.style.cssText='font-size:10px;color:#545d68;margin-top:4px;display:flex;gap:6px;align-items:baseline;font-style:italic';
var lTag=document.createElement('span');lTag.style.cssText='font-size:9px;color:#545d68;text-transform:uppercase;letter-spacing:1px;min-width:54px;font-style:normal';lTag.textContent='LIENS';
var lBody=document.createElement('span');lBody.style.cssText='flex:1;min-width:0';
lBody.textContent='Cook County Recorder mechanics-lien lookup queued';
lLine.appendChild(lTag);lLine.appendChild(lBody);
pBlock.appendChild(lLine);
}
// Union line (static lookup by trade)
var u=prop.union;
if(u){
var uLine=document.createElement('div');
uLine.style.cssText='font-size:10px;color:#8b949e;margin-top:4px;display:flex;gap:6px;align-items:baseline;flex-wrap:wrap';
var uTag=document.createElement('span');uTag.style.cssText='font-size:9px;color:#545d68;text-transform:uppercase;letter-spacing:1px;min-width:54px';uTag.textContent='UNIONS';
var uBody=document.createElement('span');uBody.style.cssText='flex:1;min-width:0';
uBody.textContent=u.trade+' → '+(u.primary_locals||[]).map(function(l){return l.name}).join(', ');
uBody.title=(u.primary_locals||[]).map(function(l){return l.name+' — '+l.jurisdiction}).join('\n');
uLine.appendChild(uTag);uLine.appendChild(uBody);
pBlock.appendChild(uLine);
// Training centers chip row, collapsible-ish
if(u.training_centers && u.training_centers.length){
var tcLine=document.createElement('div');
tcLine.style.cssText='font-size:10px;color:#8b949e;margin-top:4px;padding-left:60px';
var tcTag=document.createElement('div');tcTag.style.cssText='font-size:9px;color:#545d68;text-transform:uppercase;letter-spacing:1px;margin-bottom:2px';tcTag.textContent='Training centers (apprenticeship)';
tcLine.appendChild(tcTag);
u.training_centers.forEach(function(tc){
var t=document.createElement('div');
t.style.cssText='font-size:10px;color:#8b949e;line-height:1.4;padding:1px 0';
t.textContent='· '+tc.name+' — '+tc.address+' ('+tc.program_length+')';
tcLine.appendChild(t);
});
pBlock.appendChild(tcLine);
}
}
host.appendChild(pBlock);
// Each entity
brief.entities.forEach(function(e){
var row=document.createElement('div');
row.style.cssText='background:#0d1117;border:1px solid #1f2631;border-radius:6px;padding:10px 12px;margin-bottom:8px';
// Header: ticker · name · role · risk pill
var hd=document.createElement('div');
hd.style.cssText='display:flex;align-items:center;gap:8px;margin-bottom:6px;flex-wrap:wrap';
var tkr=document.createElement('span');
tkr.style.cssText='font-family:ui-monospace,SFMono-Regular,monospace;background:#161b22;padding:2px 6px;border-radius:4px;color:#58a6ff;border:1px solid #58a6ff44;font-weight:600;font-size:10px';
tkr.textContent=e.ticker||'LLC·?';
var nm=document.createElement('a');nm.href=P+'/contractor?name='+encodeURIComponent(e.display_name||'');
nm.target='_blank';nm.rel='noopener';
nm.style.cssText='color:#e6edf3;font-weight:600;font-size:12px;text-decoration:none;border-bottom:1px dotted #58a6ff44';
nm.title='Open full contractor profile';
nm.textContent=e.display_name||'?';
var rl=document.createElement('span');rl.style.cssText='color:#8b949e;font-size:10px;text-transform:uppercase;letter-spacing:0.5px';rl.textContent=e.role||'';
hd.appendChild(tkr);hd.appendChild(nm);hd.appendChild(rl);
var spacer=document.createElement('span');spacer.style.cssText='flex:1';hd.appendChild(spacer);
// Risk pill
if(e.risk){
var s=e.risk.score, pill=document.createElement('span');
var riskColor=s===null?'#545d68':s<30?'#3fb950':s<60?'#d29922':'#f85149';
pill.style.cssText='padding:3px 8px;border-radius:9px;font-size:10px;font-weight:600;background:#0d1117;border:1px solid '+riskColor+'66;color:'+riskColor;
pill.textContent=(s===null?'risk n/a':'risk '+s)+(e.risk.partial?' · partial':'');
pill.title=(e.risk.factors||[]).join(' · ');
hd.appendChild(pill);
}
row.appendChild(hd);
// SVEP red flag (OSHA Severe Violator Enforcement Program)
var svep=e.svep;
if(svep && svep.flagged){
var svLine=document.createElement('div');
svLine.style.cssText='font-size:11px;color:#fca5a5;background:#3a1a1a;border:1px solid #f85149;border-radius:6px;padding:6px 10px;margin:4px 0;display:flex;gap:6px;align-items:baseline;flex-wrap:wrap;font-weight:600';
var svIcon=document.createElement('span');svIcon.style.fontSize='13px';svIcon.textContent='⚠';
var svTag=document.createElement('span');svTag.style.cssText='font-size:10px;color:#f85149;text-transform:uppercase;letter-spacing:1px';svTag.textContent='OSHA SVEP';
var svBody=document.createElement('span');svBody.style.cssText='flex:1;min-width:0';
svBody.textContent='Severe Violator Enforcement Program — '+svep.matched_entries.map(function(m){return m.name.split('/')[0]}).join(', ').slice(0,80);
svBody.title='Matched SVEP entries:\n'+svep.matched_entries.map(function(m){return m.name}).join('\n');
var svLink=document.createElement('a');svLink.href='https://www.osha.gov/enforcement/svep';svLink.target='_blank';svLink.rel='noopener';svLink.style.cssText='color:#58a6ff;font-size:10px;text-decoration:none';svLink.textContent='SVEP ↗';
svLine.appendChild(svIcon);svLine.appendChild(svTag);svLine.appendChild(svBody);svLine.appendChild(svLink);
row.appendChild(svLine);
}
// Parent-public-equity link — "private GC → public parent ticker"
// chain. Surfaces who actually benefits if the contract closes.
var pl=e.parent_link;
if(pl && pl.status==='ok'){
var plLine=document.createElement('div');
plLine.style.cssText='font-size:11px;color:#8b949e;margin:3px 0;display:flex;gap:6px;align-items:baseline;flex-wrap:wrap';
var plTag=document.createElement('span');plTag.style.cssText='font-size:9px;color:#545d68;text-transform:uppercase;letter-spacing:1px;min-width:54px';plTag.textContent='PARENT';
var plBody=document.createElement('span');plBody.style.cssText='flex:1;min-width:0;color:#3fb950';
var plBits=[pl.parent_name];
if(pl.parent_ticker) plBits.push(pl.parent_ticker);
if(pl.parent_exchange) plBits.push(pl.parent_exchange);
if(pl.parent_country) plBits.push(pl.parent_country);
plBody.textContent=plBits.filter(Boolean).join(' · ');
plBody.title=pl.link_source||'';
plLine.appendChild(plTag);plLine.appendChild(plBody);
row.appendChild(plLine);
} else if(pl && pl.status==='no_link'){
var plLine2=document.createElement('div');
plLine2.style.cssText='font-size:10px;color:#545d68;margin:3px 0;display:flex;gap:6px;align-items:baseline;flex-wrap:wrap';
var pl2Tag=document.createElement('span');pl2Tag.style.cssText='font-size:9px;color:#545d68;text-transform:uppercase;letter-spacing:1px;min-width:54px';pl2Tag.textContent='PARENT';
var pl2Body=document.createElement('span');pl2Body.style.cssText='flex:1;min-width:0;font-style:italic';
pl2Body.textContent=pl.reason||'no public parent identified';
plLine2.appendChild(pl2Tag);plLine2.appendChild(pl2Body);
row.appendChild(plLine2);
}
// USASpending federal contracts
var fed=e.federal;
if(fed && fed.status==='ok' && fed.total_awards_count>0){
var fLine=document.createElement('div');
fLine.style.cssText='font-size:11px;color:#8b949e;margin:3px 0;display:flex;gap:6px;align-items:baseline;flex-wrap:wrap';
var fTag=document.createElement('span');fTag.style.cssText='font-size:9px;color:#545d68;text-transform:uppercase;letter-spacing:1px;min-width:54px';fTag.textContent='FEDERAL';
var fBody=document.createElement('span');fBody.style.cssText='flex:1;min-width:0';
var dollars=fed.total_awards_value>=1e6?'$'+Math.round(fed.total_awards_value/1e6*10)/10+'M':'$'+Math.round(fed.total_awards_value/1e3)+'K';
var topAg=(fed.top_agencies||[]).slice(0,2).map(function(a){return a.agency.replace(/^DEPARTMENT OF /i,'')}).join(', ');
fBody.textContent=fed.total_awards_count+' awards · '+dollars+' total'+(topAg?' · '+topAg:'')+(fed.most_recent_award_date?' · most recent '+fed.most_recent_award_date:'');
var fLink=document.createElement('a');fLink.href=fed.source_url;fLink.target='_blank';fLink.rel='noopener';
fLink.style.cssText='color:#58a6ff;font-size:10px;text-decoration:none;white-space:nowrap';
fLink.textContent='usaspending ↗';
fLine.appendChild(fTag);fLine.appendChild(fBody);fLine.appendChild(fLink);
row.appendChild(fLine);
} else if(fed && fed.status==='no_match'){
var fLine2=document.createElement('div');
fLine2.style.cssText='font-size:10px;color:#545d68;margin:3px 0;display:flex;gap:6px;align-items:baseline';
var f2Tag=document.createElement('span');f2Tag.style.cssText='font-size:9px;color:#545d68;text-transform:uppercase;letter-spacing:1px;min-width:54px';f2Tag.textContent='FEDERAL';
var f2Body=document.createElement('span');f2Body.style.cssText='flex:1;min-width:0;font-style:italic';
f2Body.textContent='no federal contracts on record';
fLine2.appendChild(f2Tag);fLine2.appendChild(f2Body);
row.appendChild(fLine2);
}
// News mentions (Google News RSS) + sentiment chip
var news=e.news;
var ns=e.news_sentiment;
if(news && news.status==='ok' && news.recent_headlines && news.recent_headlines.length){
var nLine=document.createElement('div');
nLine.style.cssText='font-size:11px;color:#8b949e;margin:3px 0;display:flex;gap:6px;align-items:baseline;flex-wrap:wrap';
var nTag=document.createElement('span');nTag.style.cssText='font-size:9px;color:#545d68;text-transform:uppercase;letter-spacing:1px;min-width:54px';nTag.textContent='NEWS';
var nBody=document.createElement('span');nBody.style.cssText='flex:1;min-width:0';
nBody.textContent=news.total_mentions+' mentions · latest: '+(news.recent_headlines[0].title||'').slice(0,72);
nBody.title=news.recent_headlines.map(function(h){return h.title}).join('\n');
nLine.appendChild(nTag);nLine.appendChild(nBody);
// Sentiment chip
if(ns && (ns.positive||ns.negative)){
var sc=ns.score;
var sCol=sc>0.2?'#3fb950':sc<-0.2?'#f85149':'#8b949e';
var sChip=document.createElement('span');
sChip.style.cssText='padding:2px 7px;border-radius:9px;background:#0d1117;border:1px solid '+sCol+'66;color:'+sCol+';font-weight:600;font-size:9px';
sChip.textContent=(sc>=0?'+':'')+sc.toFixed(2)+' · '+ns.positive+'/'+ns.negative;
sChip.title='Sentiment score: positive headlines '+ns.positive+', negative '+ns.negative+', neutral '+ns.neutral+
(ns.flagged_headlines.length?'\n\n'+ns.flagged_headlines.map(function(h){return '['+h.polarity+'] '+h.title.slice(0,60)+(h.reasons.length?' ('+h.reasons.join(',')+')':'')}).join('\n'):'');
nLine.appendChild(sChip);
}
row.appendChild(nLine);
}
// NLRB cases (real scraper)
var nlrb=e.nlrb;
if(nlrb && nlrb.status==='ok' && nlrb.total_cases>0){
var nlLine=document.createElement('div');
nlLine.style.cssText='font-size:11px;color:#8b949e;margin:3px 0;display:flex;gap:6px;align-items:baseline';
var nlTag=document.createElement('span');nlTag.style.cssText='font-size:9px;color:#545d68;text-transform:uppercase;letter-spacing:1px;min-width:54px';nlTag.textContent='NLRB';
var nlBody=document.createElement('span');nlBody.style.cssText='flex:1;min-width:0';
nlBody.textContent=nlrb.total_cases+' case'+(nlrb.total_cases===1?'':'s')+' on file';
var nlLink=document.createElement('a');
nlLink.href='https://www.nlrb.gov/search/case?search_term='+encodeURIComponent(e.display_name);
nlLink.target='_blank';nlLink.rel='noopener';nlLink.style.cssText='color:#58a6ff;font-size:10px;text-decoration:none';
nlLink.textContent='nlrb.gov ↗';
nlLine.appendChild(nlTag);nlLine.appendChild(nlBody);nlLine.appendChild(nlLink);
row.appendChild(nlLine);
} else if(nlrb && nlrb.status==='needs_setup'){
var nlLine2=document.createElement('div');
nlLine2.style.cssText='font-size:10px;color:#545d68;margin:3px 0;display:flex;gap:6px;align-items:baseline;font-style:italic';
var nl2Tag=document.createElement('span');nl2Tag.style.cssText='font-size:9px;color:#545d68;text-transform:uppercase;letter-spacing:1px;min-width:54px;font-style:normal';nl2Tag.textContent='NLRB';
var nl2Body=document.createElement('span');nl2Body.style.cssText='flex:1;min-width:0';
nl2Body.textContent=nlrb.reason||'awaiting wire-up';
nlLine2.appendChild(nl2Tag);nlLine2.appendChild(nl2Body);
row.appendChild(nlLine2);
}
// Diversity cert (MBE/WBE/DBE)
var div2=e.diversity;
if(div2 && div2.status==='ok' && div2.certifications && div2.certifications.length){
var dLine=document.createElement('div');
dLine.style.cssText='font-size:11px;color:#3fb950;margin:3px 0;display:flex;gap:6px;align-items:baseline;flex-wrap:wrap';
var dTag=document.createElement('span');dTag.style.cssText='font-size:9px;color:#545d68;text-transform:uppercase;letter-spacing:1px;min-width:54px';dTag.textContent='MBE/WBE';
var dBody=document.createElement('span');dBody.style.cssText='flex:1;min-width:0';
dBody.textContent='✓ Certified: '+div2.certifications.map(function(c){return c.category}).join(', ');
dLine.appendChild(dTag);dLine.appendChild(dBody);
row.appendChild(dLine);
}
// Combined placeholders (debarment, OSHA SIR, diversity-needs_setup)
var deb=e.debarment, sir=e.osha_sir;
var pendingBits=[];
if(deb && deb.status==='needs_setup') pendingBits.push('SAM/IDOL debarment');
if(sir && sir.status==='needs_setup') pendingBits.push('OSHA Severe Injury');
if(div2 && div2.status==='needs_setup') pendingBits.push('MBE/WBE certs');
if(pendingBits.length){
var pLine=document.createElement('div');
pLine.style.cssText='font-size:10px;color:#484f58;margin:3px 0;display:flex;gap:6px;align-items:baseline;font-style:italic';
var pTag=document.createElement('span');pTag.style.cssText='font-size:9px;color:#545d68;text-transform:uppercase;letter-spacing:1px;min-width:54px;font-style:normal';pTag.textContent='PENDING';
var pBody=document.createElement('span');pBody.style.cssText='flex:1;min-width:0';
pBody.textContent=pendingBits.join(' · ')+' · awaiting API/scraper setup';
pLine.appendChild(pTag);pLine.appendChild(pBody);
row.appendChild(pLine);
}
// Chicago contractor history — activity velocity signal.
// Growing/declining/new annotations help the staffer judge whether
// this entity is a rising player, steady hand, or fresh LLC.
var hist=e.history;
if(hist && hist.status==='ok'){
var hl=document.createElement('div');
hl.style.cssText='font-size:11px;color:#8b949e;margin:3px 0;display:flex;gap:8px;flex-wrap:wrap;align-items:baseline';
var hTag=document.createElement('span');hTag.style.cssText='font-size:9px;color:#545d68;text-transform:uppercase;letter-spacing:1px;min-width:54px';hTag.textContent='CHI HIST';
var hBody=document.createElement('span');hBody.style.cssText='flex:1;min-width:0';
var dollars=hist.total_cost_last_24mo?' · $'+Math.round(hist.total_cost_last_24mo/1e6*10)/10+'M in 24mo':'';
hBody.textContent=hist.permits_last_180d+' in 180d · '+hist.permits_last_24mo+' in 24mo · '+hist.permits_historical_total+' all-time'+dollars;
var tChip=document.createElement('span');
var tColors={growing:'#3fb950',stable:'#58a6ff',declining:'#d29922',new:'#d29922',unknown:'#545d68'};
var tc=tColors[hist.trend]||'#545d68';
tChip.style.cssText='padding:2px 7px;border-radius:9px;font-size:9px;font-weight:600;background:#0d1117;border:1px solid '+tc+'66;color:'+tc;
tChip.textContent=hist.trend;
if(hist.trend==='new') tChip.title='≤3 permits ever — classic LLC-shuffle signature, investigate before committing workers';
hl.appendChild(hTag);hl.appendChild(hBody);hl.appendChild(tChip);
row.appendChild(hl);
}
// OSHA line
var osha=e.osha;
if(osha){
var o=document.createElement('div');
o.style.cssText='font-size:11px;color:#8b949e;margin:3px 0;display:flex;gap:8px;flex-wrap:wrap;align-items:baseline';
var oTag=document.createElement('span');oTag.style.cssText='font-size:9px;color:#545d68;text-transform:uppercase;letter-spacing:1px;min-width:54px';oTag.textContent='OSHA';
var oBody=document.createElement('span');oBody.style.cssText='flex:1;min-width:0';
if(osha.status==='ok'){
var n=osha.inspection_count;
var ageTxt='';
if(osha.most_recent_date){
var age=(Date.now()-Date.parse(osha.most_recent_date))/(86400000);
ageTxt=age<180?' · <'+Math.round(age)+'d ago':' · '+Math.round(age/30)+'mo ago';
}
oBody.textContent=n+' inspection'+(n===1?'':'s')+(osha.states_seen&&osha.states_seen.length?' across '+osha.states_seen.slice(0,5).join(', ')+(osha.states_seen.length>5?', …':''):'')+' · most recent '+(osha.most_recent_date||'?')+ageTxt;
} else if(osha.status==='no_match'){
oBody.style.color='#3fb950';
oBody.textContent='no inspections on record · clean';
} else {
oBody.style.color='#d29922';
oBody.textContent='fetch error: '+(osha.error||'unknown');
}
var oLink=document.createElement('a');
oLink.href=osha.source_url||'#';oLink.target='_blank';oLink.rel='noopener';
oLink.style.cssText='color:#58a6ff;font-size:10px;text-decoration:none;white-space:nowrap';
oLink.textContent='open on osha.gov ↗';
o.appendChild(oTag);o.appendChild(oBody);o.appendChild(oLink);
row.appendChild(o);
// Recent inspection rows (up to 3)
if(osha.recent_inspections && osha.recent_inspections.length){
var ul=document.createElement('div');
ul.style.cssText='margin-top:4px;font-size:10px;color:#545d68;padding-left:62px;line-height:1.5';
osha.recent_inspections.slice(0,3).forEach(function(i){
var lp=document.createElement('div');
var link=document.createElement('a');link.href=i.detail_url;link.target='_blank';link.rel='noopener';
link.style.cssText='color:#8b949e;text-decoration:none;font-family:ui-monospace,monospace';
link.textContent=i.id;
lp.appendChild(link);
lp.appendChild(document.createTextNode(' · '+i.date+' · '+i.state+' · '+i.type+' · '+i.scope));
ul.appendChild(lp);
});
row.appendChild(ul);
}
}
// ILSOS line
var ilsos=e.ilsos;
if(ilsos){
var il=document.createElement('div');
il.style.cssText='font-size:11px;color:#8b949e;margin:6px 0 0;display:flex;gap:8px;align-items:baseline;flex-wrap:wrap';
var ilTag=document.createElement('span');ilTag.style.cssText='font-size:9px;color:#545d68;text-transform:uppercase;letter-spacing:1px;min-width:54px';ilTag.textContent='ILSOS';
var ilBody=document.createElement('span');ilBody.style.cssText='flex:1;min-width:0';
if(ilsos.status==='ok'){
var bits=[];
if(ilsos.entity_name) bits.push(ilsos.entity_name);
if(ilsos.status_text) bits.push(ilsos.status_text);
if(ilsos.formation_date) bits.push('formed '+ilsos.formation_date);
ilBody.textContent=bits.join(' · ');
} else {
ilBody.style.color='#d29922';
ilBody.textContent='awaiting source · '+(ilsos.reason||ilsos.status);
}
il.appendChild(ilTag);il.appendChild(ilBody);
row.appendChild(il);
}
host.appendChild(row);
});
// Roadmap — what's coming next, from the brief. Rendered as a
// collapsible so it doesn't dominate. Staffers can see the direction
// without having to trust that "more is coming."
if(brief.roadmap && brief.roadmap.length){
var rm=document.createElement('details');
rm.style.cssText='margin-top:8px;background:#0d1117;border:1px dashed #21262d;border-radius:6px';
var rmSum=document.createElement('summary');
rmSum.style.cssText='list-style:none;cursor:pointer;padding:8px 12px;color:#8b949e;font-size:10px;text-transform:uppercase;letter-spacing:1px;font-weight:600;outline:none';
rmSum.textContent='▸ '+brief.roadmap.length+' additional public-data sources queued';
rm.appendChild(rmSum);
var rmBody=document.createElement('div');
rmBody.style.cssText='padding:0 12px 10px;font-size:10px;color:#8b949e;line-height:1.7';
brief.roadmap.forEach(function(line){
var li=document.createElement('div');
li.style.cssText='padding:2px 0';
li.textContent='· '+line;
rmBody.appendChild(li);
});
rm.appendChild(rmBody);
host.appendChild(rm);
}
// Footer: honest data-source summary
var foot=document.createElement('div');
foot.style.cssText='font-size:10px;color:#484f58;margin-top:8px;line-height:1.5';
foot.textContent='Brief generated '+new Date(brief.generated_at).toLocaleTimeString()+' · OSHA scraped live (cached 30d) · SEC EDGAR name→ticker + Stooq live quote · Chicago permit history fuzzy-matched across 2 years · ILSOS blocked at our ASN (pending VPN or OpenCorporates).';
host.appendChild(foot);
}
function loadLiveContracts(){
// Pair live Chicago permits with our 500K worker bench and the
// meta-index discovered patterns for each role+geo. This is the
// "real external data meets synthetic playbook learning" card set.
api('/intelligence/permit_contracts',{}).then(function(r){
var el=document.getElementById('live-contracts');el.textContent='';
if(!r||!r.contracts||r.contracts.length===0){
el.textContent='No permits returned.';return;
}
// Feed the Live Market hero (clock + Chicago permit pulse) before
// rendering cards so both land together and tell one coherent story.
loadLiveMarket(r.contracts);
r.contracts.forEach(function(c){
var p=c.permit||{}, prop=c.proposed||{}, tl=c.timeline||{};
var urg=tl.urgency||'scheduled';
var borderColor={overdue:'#f85149',urgent:'#d29922',soon:'#388bfd',scheduled:'#2ea043'}[urg]||'#388bfd';
var card=document.createElement('div');card.className='insight info';
card.style.cssText='background:#0d1117;border:1px solid #171d27;border-radius:10px;padding:16px;margin-bottom:10px;border-left:3px solid '+borderColor;
// Header — permit
var hdr=document.createElement('div');hdr.style.cssText='display:flex;justify-content:space-between;margin-bottom:8px;gap:12px';
var left=document.createElement('div');
var title=document.createElement('div');title.style.cssText='font-weight:600;color:#e6edf3;font-size:14px';
title.textContent='$'+(p.cost||0).toLocaleString()+' · '+(p.work_type||'');
var addr=document.createElement('div');addr.style.cssText='color:#8b949e;font-size:12px;margin-top:2px';
addr.textContent=(p.address||'')+' · Chicago, IL · filed '+(p.issue_date||'');
// Timeline chip
if(tl.days_to_deadline!==undefined){
var tmline=document.createElement('div');tmline.style.cssText='color:'+borderColor+';font-size:11px;font-weight:600;margin-top:4px';
var urgLabel={overdue:'OVERDUE',urgent:'URGENT',soon:'SOON',scheduled:'SCHEDULED'}[urg]||'SCHEDULED';
var dd=tl.days_to_deadline;
var txt=urgLabel+' · staffing window opens '+(tl.staffing_window_opens||'')+' ('+(dd<=0?Math.abs(dd)+'d overdue':dd+'d to deadline')+') · construction est '+(tl.estimated_construction_start||'');
tmline.textContent=txt;addr.appendChild(document.createElement('br'));left.appendChild(tmline);
}
left.appendChild(title);left.appendChild(addr);
var right=document.createElement('div');right.style.cssText='color:#58a6ff;font-size:12px;font-weight:600;text-align:right;white-space:nowrap';
right.textContent=prop.count+'× '+prop.role;
var sub=document.createElement('div');sub.style.cssText='color:#545d68;font-size:10px;text-align:right';
sub.textContent='pool: '+(prop.pool_size||'?').toLocaleString()+' available';
right.appendChild(sub);
// Rate awareness: show implied bill rate per contract
if(c.implied_bill_rate){
var rate=document.createElement('div');
rate.style.cssText='color:#d29922;font-size:10px;text-align:right;margin-top:3px';
rate.textContent='bill rate: $'+c.implied_bill_rate.toFixed(2)+'/hr';
right.appendChild(rate);
}
hdr.appendChild(left);hdr.appendChild(right);card.appendChild(hdr);
// Architecture pill row — instant-search latency + shift coverage
// + pool-size proof that the index actually fired on this call.
// This is the "our substrate is better" surface J asked for.
var pillRow=document.createElement('div');
pillRow.style.cssText='display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px;font-size:10px';
function pill(text,color,title){
var p=document.createElement('span');
p.style.cssText='padding:3px 8px;border-radius:9px;background:#0d1117;border:1px solid '+color+'66;color:'+color+';font-weight:600;letter-spacing:0.3px';
if(title) p.title=title;
p.textContent=text;
return p;
}
if(c.search_latency_ms!==undefined){
var latColor=c.search_latency_ms<500?'#3fb950':c.search_latency_ms<2000?'#d29922':'#f85149';
pillRow.appendChild(pill('⚡ '+c.search_latency_ms+'ms', latColor,
'Time for /vectors/hybrid to rank '+(prop.pool_size||0).toLocaleString()+' SQL-matched workers against the 50K-chunk vector index.'));
}
if(prop.pool_size!==undefined){
pillRow.appendChild(pill(prop.pool_size.toLocaleString()+' pool · k=200 boost', '#58a6ff',
'Pool = workers matching SQL filter (role+state+city+avail>0.5). k=200 means playbook boost checks 200 candidates before narrowing to top-5.'));
}
if(c.shifts_needed&&c.shifts_needed.length){
var shiftColor={'1st':'#f9d171','2nd':'#f5894a','3rd':'#5f5fff','4th':'#2ea043'};
c.shifts_needed.forEach(function(sh){
pillRow.appendChild(pill(sh+' shift', shiftColor[sh]||'#8b949e',
'Inferred from permit description. See 24/7 shift clock above for live distribution.'));
});
}
if(pillRow.childNodes.length) card.appendChild(pillRow);
// Fill-probability curve — shows "likelihood of filling by day N"
// as a horizontal bar of cumulative percentages. Drill down that
// J asked for: "percentage likelihood of filling them on a certain time."
if(c.fill_probability&&c.fill_probability.curve){
var fp=c.fill_probability;
var fpRow=document.createElement('div');
fpRow.style.cssText='background:#0d1117;border:1px solid #171d27;border-radius:8px;padding:10px 12px;margin-bottom:10px';
var fpLabel=document.createElement('div');
fpLabel.style.cssText='display:flex;justify-content:space-between;font-size:10px;color:#8b949e;text-transform:uppercase;letter-spacing:1px;margin-bottom:6px';
var fpTitle=document.createElement('span');fpTitle.style.color='#e6edf3';fpTitle.textContent='Fill Probability';
var fpBase=document.createElement('span');fpBase.textContent='base '+fp.base_pct+'% · pool × urgency';
fpLabel.appendChild(fpTitle);fpLabel.appendChild(fpBase);
fpRow.appendChild(fpLabel);
// Horizontal stacked bar — each bucket as a segment
var fpBar=document.createElement('div');
fpBar.style.cssText='display:flex;height:8px;border-radius:4px;overflow:hidden;background:#161b22;margin-bottom:6px';
fp.curve.forEach(function(pt,idx){
var prev=idx===0?0:fp.curve[idx-1].cumulative_pct;
var delta=pt.cumulative_pct-prev;
if(delta<=0) return;
var seg=document.createElement('div');
var shade=pt.day<=7?'#3fb950':pt.day<=14?'#d29922':pt.day<=21?'#e8751a':'#f85149';
seg.style.cssText='flex:'+delta+' 0 0;background:'+shade;
seg.title='days '+(idx>0?fp.curve[idx-1].day:0)+''+pt.day+': +'+delta+'% cumulative';
fpBar.appendChild(seg);
});
fpRow.appendChild(fpBar);
// Day-marker row — human-readable spans, not cryptic d7/d14
var fpMarks=document.createElement('div');
fpMarks.style.cssText='display:flex;justify-content:space-between;font-size:9px;color:#545d68;gap:6px';
function humanSpan(d){
if(d===0) return 'Today';
if(d===1) return '1 day';
if(d<7) return d+' days';
if(d===7) return '1 week';
if(d===14) return '2 weeks';
if(d===21) return '3 weeks';
if(d===30) return '1 month';
if(d<30) return Math.round(d/7)+' weeks';
return Math.round(d/30)+' months';
}
fp.curve.forEach(function(pt){
var m=document.createElement('span');
m.style.cssText='text-align:center;line-height:1.3;flex:1;min-width:0';
var t=document.createElement('div');t.style.cssText='color:#8b949e;font-weight:600;font-variant-numeric:tabular-nums';t.textContent=pt.cumulative_pct+'%';
var s=document.createElement('div');s.style.cssText='color:#545d68;margin-top:1px';s.textContent=humanSpan(pt.day);
m.appendChild(t);m.appendChild(s);
fpMarks.appendChild(m);
});
fpRow.appendChild(fpMarks);
// Subtle legend — what this curve means
var fpNote=document.createElement('div');
fpNote.style.cssText='font-size:9px;color:#545d68;margin-top:6px;line-height:1.4';
fpNote.textContent='Cumulative chance the role gets fully staffed by that point, given pool depth, urgency, and past fill patterns.';
fpRow.appendChild(fpNote);
card.appendChild(fpRow);
}
// Economics panel — "as though the contracts were accepted and filled"
if(c.economics){
var ec=c.economics;
var ecRow=document.createElement('div');
ecRow.style.cssText='background:#0d1117;border:1px solid #171d27;border-radius:8px;padding:10px 12px;margin-bottom:10px;display:grid;grid-template-columns:repeat(4,1fr);gap:8px';
function ecCell(label,big,sub,color){
var cell=document.createElement('div');
var l=document.createElement('div');l.style.cssText='font-size:9px;color:#545d68;text-transform:uppercase;letter-spacing:1px';l.textContent=label;
var b=document.createElement('div');b.style.cssText='font-size:13px;font-weight:600;color:'+(color||'#e6edf3');b.textContent=big;
var s=document.createElement('div');s.style.cssText='font-size:9px;color:#8b949e;margin-top:1px';s.textContent=sub;
cell.appendChild(l);cell.appendChild(b);cell.appendChild(s);
return cell;
}
ecRow.appendChild(ecCell('Est. Revenue','$'+ec.gross_revenue.toLocaleString(),
prop.count+' × '+ec.hours_per_week+'h × '+ec.weeks_assumed+'w','#e6edf3'));
var marginColor=ec.margin_pct>=25?'#3fb950':ec.margin_pct>=10?'#d29922':'#f85149';
ecRow.appendChild(ecCell('Est. Margin','$'+ec.gross_margin.toLocaleString(),
ec.margin_pct+'% · avg pay $'+ec.avg_pay_rate+'/hr',marginColor));
ecRow.appendChild(ecCell('Payout Window',ec.payout_window_days[0]+''+ec.payout_window_days[1]+'d',
'after fill_date · standard net-30 / net-45','#8b949e'));
var overColor=ec.over_bill_count>0?'#f85149':'#8b949e';
ecRow.appendChild(ecCell('Over-Bill Pool',ec.over_bill_count+'/'+(prop.candidates||[]).length,
ec.over_bill_count>0?'$'+ec.over_bill_pool_margin_at_risk.toLocaleString()+' at risk':'none flagged',overColor));
card.appendChild(ecRow);
}
// Project Index — portfolio of public-data signals for this permit's
// property + contractors. Collapsed by default; fetches
// /intelligence/permit_entities lazily on expand. Real OSHA data,
// explicit "awaiting source" placeholders for sources we don't yet
// have wired. NB: deliberately NOT called "ETF" — that's a
// SEC-regulated term. This is a custom Chicago build-signal index.
if(p.contact_1_name || p.contact_2_name){
var eb=document.createElement('details');
eb.style.cssText='background:#0d1117;border:1px solid #171d27;border-radius:8px;margin-bottom:10px';
var ebSum=document.createElement('summary');
ebSum.style.cssText='list-style:none;cursor:pointer;padding:10px 12px;display:flex;align-items:center;gap:10px;color:#8b949e;font-size:11px;outline:none';
var ebCaret=document.createElement('span');ebCaret.style.cssText='color:#58a6ff;font-size:14px';ebCaret.textContent='▸';
var ebLabel=document.createElement('span');ebLabel.style.cssText='font-size:10px;color:#545d68;text-transform:uppercase;letter-spacing:1px';
ebLabel.textContent='PROJECT INDEX — Build Signals';
var ebTags=document.createElement('span');ebTags.style.cssText='color:#e6edf3;font-size:11px;flex:1;font-weight:500';
// Contractor names link to the full profile page. Without anchors,
// clicking the preview did nothing — the only working contractor
// link was inside the lazy-loaded entity brief, which a coordinator
// wouldn't reach without first expanding the details.
var preview=[];
function contactLink(n){
var a=document.createElement('a');
a.href=P+'/contractor?name='+encodeURIComponent(n);
a.target='_blank';a.rel='noopener';
a.style.cssText='color:inherit;text-decoration:none;border-bottom:1px dotted #58a6ff44';
a.title='Open full contractor profile';
a.textContent=n;
a.addEventListener('click',function(e){e.stopPropagation()}); // don't toggle the details
return a;
}
if(p.contact_1_name) preview.push(p.contact_1_name);
if(p.contact_2_name && p.contact_2_name!==p.contact_1_name) preview.push(p.contact_2_name);
if(preview.length){
preview.forEach(function(n,i){
if(i>0) ebTags.appendChild(document.createTextNode(' · '));
ebTags.appendChild(contactLink(n));
});
}
var ebMeta=document.createElement('span');ebMeta.style.cssText='color:#545d68;font-size:10px';
ebMeta.textContent='click → fetch OSHA + ILSOS';
ebSum.appendChild(ebCaret);ebSum.appendChild(ebLabel);ebSum.appendChild(ebTags);ebSum.appendChild(ebMeta);
eb.appendChild(ebSum);
var ebBody=document.createElement('div');
ebBody.style.cssText='padding:0 12px 12px';
eb.appendChild(ebBody);
var loaded=false;
eb.addEventListener('toggle',function(){
if(!eb.open||loaded) return;
loaded=true;
ebBody.textContent='';
var loading=document.createElement('div');
loading.style.cssText='color:#545d68;font-size:11px;padding:8px 0';
loading.textContent='▸ Pulling OSHA (live scrape · ~1-2s per contractor)…';
ebBody.appendChild(loading);
api('/intelligence/permit_entities',{
permit_id:p.id||'',
address:p.address||'',
work_type:p.work_type||'',
contact_1_name:p.contact_1_name||'',
contact_1_type:p.contact_1_type||'',
contact_2_name:p.contact_2_name||'',
contact_2_type:p.contact_2_type||''
}).then(function(r){
renderEntityBrief(ebBody,r);
}).catch(function(e){
ebBody.textContent='';
var errDiv=document.createElement('div');
errDiv.style.cssText='color:#f85149;font-size:11px;padding:8px 0';
errDiv.textContent='brief failed: '+e.message;
ebBody.appendChild(errDiv);
});
});
card.appendChild(eb);
}
// Description
if(p.description){
var desc=document.createElement('div');desc.style.cssText='color:#94a3b8;font-size:11px;margin-bottom:10px;line-height:1.5';
desc.textContent=p.description;card.appendChild(desc);
}
// Pattern (meta-index) chip
if(c.discovered_pattern){
var pat=document.createElement('div');pat.style.cssText='background:#0d2818;border:1px solid #2ea04360;border-radius:6px;padding:8px 12px;margin-bottom:10px;font-size:11px;color:#86efac;line-height:1.5';
var plabel=document.createElement('span');plabel.style.cssText='color:#3fb950;font-weight:600;margin-right:6px';
plabel.textContent='MEMORY ('+c.pattern_matched+' playbooks):';
pat.appendChild(plabel);
pat.appendChild(document.createTextNode(' '+c.discovered_pattern));
card.appendChild(pat);
}
// Candidates
var cands=prop.candidates||[];
cands.slice(0,3).forEach(function(cand,i){
var row=document.createElement('div');row.style.cssText='display:flex;align-items:center;gap:10px;padding:6px 10px;background:#161b22;border-radius:6px;margin-bottom:4px;font-size:12px';
var av=document.createElement('div');av.style.cssText='width:28px;height:28px;border-radius:6px;display:flex;align-items:center;justify-content:center;font-weight:600;font-size:10px;color:#e6edf3;background:'+AC[i%AC.length];
av.textContent=(cand.name||'?').split(' ').map(function(n){return (n[0]||'').toUpperCase()}).join('').substring(0,2);
var info=document.createElement('div');info.style.cssText='flex:1;min-width:0';
var nm=document.createElement('div');nm.style.cssText='color:#e6edf3;font-weight:500';nm.textContent=cand.name||cand.doc_id;
if((cand.playbook_boost||0)>0){
var chip=document.createElement('span');chip.style.cssText='margin-left:8px;padding:2px 7px;border-radius:9px;font-size:9px;font-weight:600;background:#0d2818;border:1px solid #2ea043;color:#3fb950;vertical-align:middle';
chip.textContent='Endorsed · '+(cand.playbook_citations||[]).length+' playbook'+((cand.playbook_citations||[]).length===1?'':'s');
nm.appendChild(chip);
}
// Rate warning chip when worker's pay exceeds the contract's bill rate
if(cand.over_bill_rate){
var warn=document.createElement('span');warn.style.cssText='margin-left:6px;padding:2px 7px;border-radius:9px;font-size:9px;font-weight:600;background:#3a1a1a;border:1px solid #f85149;color:#fca5a5;vertical-align:middle';
warn.textContent='Over bill rate';
warn.title='Worker\'s implied pay rate ($'+(cand.implied_pay_rate||0).toFixed(2)+'/hr) exceeds contract bill rate ($'+(c.implied_bill_rate||0).toFixed(2)+'/hr) — margin at risk';
nm.appendChild(warn);
}
var sub2=document.createElement('div');sub2.style.cssText='color:#545d68;font-size:10px';
var subText=cand.doc_id+' · score '+(cand.score||0).toFixed(3);
if(cand.implied_pay_rate) subText+=' · pay $'+cand.implied_pay_rate.toFixed(2)+'/hr';
sub2.textContent=subText;
info.appendChild(nm);info.appendChild(sub2);
row.appendChild(av);row.appendChild(info);
card.appendChild(row);
});
if(cands.length>3){
var more=document.createElement('div');more.style.cssText='font-size:10px;color:#58a6ff;padding:4px 10px;margin-top:2px';
more.textContent='+ '+(cands.length-3)+' more candidates available';
card.appendChild(more);
}
el.appendChild(card);
});
}).catch(function(e){
document.getElementById('live-contracts').textContent='Error loading: '+e.message;
});
}
function loadDay(){
// Step 1: run simulation + get real worker count + populate dropdowns from actual data
Promise.all([
api('/simulation/run',{}),
api('/sql',{sql:"SELECT COUNT(*) as cnt FROM workers_500k"}),
api('/sql',{sql:"SELECT DISTINCT role FROM workers_500k ORDER BY role"}),
api('/sql',{sql:"SELECT DISTINCT state FROM workers_500k ORDER BY state"})
]).then(function(r0){
var sim=r0[0];
var workerCount=r0[1]&&r0[1].rows&&r0[1].rows[0]?r0[1].rows[0].cnt:0;
var allRoles=r0[2]&&r0[2].rows?r0[2].rows.map(function(r){return r.role}):[];
var allStates=r0[3]&&r0[3].rows?r0[3].rows.map(function(r){return r.state}):[];
// Populate dropdowns from real data
var stSel=document.getElementById('sst');
var rlSel=document.getElementById('srl');
stSel.innerHTML='<option value="">Any State</option>';
allStates.forEach(function(s){var o=document.createElement('option');o.value=s;o.textContent=s;stSel.appendChild(o)});
rlSel.innerHTML='<option value="">Any Role</option>';
allRoles.forEach(function(r){var o=document.createElement('option');o.value=r;o.textContent=r;rlSel.appendChild(o)});
// Update search summary with real count
var searchSum=document.querySelector('.sa summary');
if(searchSum)searchSum.textContent='Search all '+workerCount.toLocaleString()+' workers';
var today=sim.days?sim.days[0]:null;
var sum=sim.summary||{};
sum.worker_count=workerCount;
document.getElementById('status').textContent=sum.total_filled+'/'+sum.total_needed+' positions filled across '+sum.total_contracts+' contracts';
// Step 2: extract what's ACTUALLY needed from today's contracts
var contracts=today?today.contracts:[];
var needRoles={}, needStates={}, urgentRoles=[];
contracts.forEach(function(c){
if(c.filled<c.headcount){
needRoles[c.role]=(needRoles[c.role]||0)+(c.headcount-c.filled);
needStates[c.state]=(needStates[c.state]||0)+(c.headcount-c.filled);
if(c.priority==='urgent'||c.priority==='high') urgentRoles.push(c.role);
}
});
// Build contextual queries based on today's gaps
var roleList=Object.keys(needRoles);
var stateList=Object.keys(needStates);
var roleFilter=roleList.length?roleList.map(function(r){return"'"+r.replace(/'/g,"''")+"'"}).join(','):"'Forklift Operator'";
var stateFilter=stateList.length?stateList.map(function(s){return"'"+s.replace(/'/g,"''")+"'"}).join(','):"'IL'";
// Contextual workers — add random offset so it's not always the same top 8
var offset=Math.floor(Math.random()*20);
var topSql="SELECT name, role, city, state, ROUND(CAST(reliability AS DOUBLE),2) rel, certifications "+
"FROM workers_500k WHERE role IN ("+roleFilter+") AND state IN ("+stateFilter+") "+
"AND CAST(reliability AS DOUBLE)>0.85 ORDER BY CAST(reliability AS DOUBLE) DESC LIMIT 8 OFFSET "+offset;
// Coverage for states that matter today
var covSql="SELECT state, COUNT(*) cnt, SUM(CASE WHEN CAST(reliability AS DOUBLE)>0.8 THEN 1 ELSE 0 END) good "+
"FROM workers_500k WHERE state IN ("+stateFilter+") GROUP BY state ORDER BY cnt DESC";
// Roles breakdown for today's needed roles
var roleSql="SELECT role, COUNT(*) total, SUM(CASE WHEN CAST(reliability AS DOUBLE)>0.8 THEN 1 ELSE 0 END) reliable "+
"FROM workers_500k WHERE role IN ("+roleFilter+") GROUP BY role ORDER BY total DESC";
return Promise.all([roleSql, topSql, covSql].map(function(sql){
return api('/sql',{sql:sql});
})).then(function(results){
var roles=results[0], topWorkers=results[1], coverage=results[2];
renderMain(today,sum,roles,topWorkers,coverage,needRoles,needStates);
});
}).catch(function(e){
document.getElementById('main').textContent='Error loading: '+e.message;
});
}
function renderMain(today,sum,roles,topWorkers,coverage,needRoles,needStates){
var el=document.getElementById('main');
el.textContent='';
// Stats
var stats=document.createElement('div');stats.className='stats';
addStat(stats,sum.total_contracts||0,'Contracts Today');
addStat(stats,sum.total_filled||0,'Positions Filled');
addStat(stats,sum.emergencies||0,'Urgent');
addStat(stats,sum.worker_count||0,'Workers in System');
el.appendChild(stats);
// INSIGHT 1: Urgent pipeline — step-by-step workflow
if(today&&today.contracts){
var urgent=today.contracts.filter(function(c){return c.priority==='urgent'});
var needsWork=today.contracts.filter(function(c){return c.priority!=='urgent'&&c.filled<c.headcount});
var filled=today.contracts.filter(function(c){return c.filled>=c.headcount});
if(urgent.length){
var ins=makeInsight('urgent','Urgent Pipeline',
urgent.length+' emergency contract'+(urgent.length>1?'s':'')+ ' — workers pre-matched, ready for your call',null);
urgent.forEach(function(c){addContractInsight(ins,c,true)});
el.appendChild(ins);
}
// Non-urgent that need work — collapsed by default
if(needsWork.length){
var ins1b=makeInsight('warning','In Progress',
needsWork.length+' contract'+(needsWork.length>1?'s':'')+' still filling — workers matched, awaiting confirmation',null);
var det1=document.createElement('details');
var sum1=document.createElement('summary');sum1.style.cssText='cursor:pointer;font-size:11px;color:#545d68;padding:4px 0;list-style:none';
sum1.textContent='Show '+needsWork.length+' contracts';det1.appendChild(sum1);
needsWork.forEach(function(c){addContractInsight(det1,c,false)});
ins1b.appendChild(det1);el.appendChild(ins1b);
}
// Filled — collapsed by default
if(filled.length){
var ins2=makeInsight('opportunity','Ready to Go',
filled.length+' contract'+(filled.length>1?'s':'')+' fully staffed — review and send shift details',null);
var det2=document.createElement('details');
var sum2=document.createElement('summary');sum2.style.cssText='cursor:pointer;font-size:11px;color:#545d68;padding:4px 0;list-style:none';
sum2.textContent='Show '+filled.length+' contracts';det2.appendChild(sum2);
filled.forEach(function(c){addContractInsight(det2,c,false)});
ins2.appendChild(det2);el.appendChild(ins2);
}
}
// INSIGHT 2: Top available workers — contextual to today's unfilled contracts
if(topWorkers&&topWorkers.rows&&topWorkers.rows.length){
// Build a contextual headline from today's gaps
var gapRoles=needRoles?Object.keys(needRoles):[];
var gapStates=needStates?Object.keys(needStates):[];
var headline='Workers Available for Today\'s Open Contracts';
var sub='Matched to the roles and locations you need filled right now';
if(gapRoles.length<=3&&gapRoles.length>0){
headline='Top '+gapRoles.join(', ')+' Workers Available';
sub='These workers match your unfilled contracts in '+gapStates.join(', ');
}
var ins3=makeInsight('info',headline,sub,
'Filtered to roles and states with open positions today. Reliability 85%+.');
topWorkers.rows.slice(0,5).forEach(function(w,i){
// Show which contract gap this worker could fill
var gapNote='';
if(needRoles&&needRoles[w.role]){gapNote='→ Could fill '+needRoles[w.role]+' open '+w.role+' spot'+(needRoles[w.role]>1?'s':'')}
var wd={nm:w.name,role:w.role,loc:w.city+', '+w.state,skills:[],
certs:(w.certifications||'').split(',').filter(function(c){return c.trim()&&c.trim()!=='none'}),
rel:w.rel,avail:0,arch:'',hasM:true};
addWorkerInsight(ins3,w.name,w.role+' · '+w.city+', '+w.state,
'Reliability: '+Math.round(w.rel*100)+'%'+(w.certifications&&w.certifications!=='none'?' · Certs: '+w.certifications:'')+(gapNote?' · '+gapNote:''),i,null,wd);
});
el.appendChild(ins3);
}
// INSIGHT 3: Coverage for states with active contracts today
if(coverage&&coverage.rows&&coverage.rows.length){
var stateLabel=gapStates.length?' in '+gapStates.join(', '):'';
var ins4=makeInsight('warning','Bench Strength'+stateLabel,
'Worker pool depth for states with open contracts today',
'Shows how many reliable workers (80%+ reliability) you have in states where you need to fill positions right now.');
coverage.rows.forEach(function(r){
var pct=Math.round(r.good/r.cnt*100);
var openSlots=needStates&&needStates[r.state]?needStates[r.state]:0;
var d=document.createElement('div');d.style.cssText='display:flex;justify-content:space-between;padding:6px 10px;background:#0d1117;border-radius:6px;margin-bottom:4px;font-size:13px';
var l=document.createElement('span');l.style.color='#f0f6fc';
l.textContent=r.state+' — '+r.cnt.toLocaleString()+' workers'+(openSlots?' · '+openSlots+' open slot'+(openSlots>1?'s':''):'');
var v=document.createElement('span');v.textContent=pct+'% reliable';v.style.color=pct<40?'#f85149':'#d29922';
d.appendChild(l);d.appendChild(v);ins4.appendChild(d);
});
el.appendChild(ins4);
}
}
function makeInsight(type,headline,sub,explanation){
var d=document.createElement('div');d.className='insight '+type;
var lb=document.createElement('div');lb.className='label';
lb.textContent=type==='urgent'?'ACTION NEEDED':type==='opportunity'?'READY':type==='warning'?'HEADS UP':'INSIGHT';
var h=document.createElement('div');h.className='headline';h.textContent=headline;
var s=document.createElement('div');s.className='sub';s.textContent=sub;
d.appendChild(lb);d.appendChild(h);d.appendChild(s);
if(explanation){var ex=document.createElement('div');ex.style.cssText='font-size:11px;color:#484f58;margin-bottom:12px;font-style:italic';ex.textContent=explanation;d.appendChild(ex)}
return d;
}
function addContractInsight(parent,c,isUrgent){
var isFilled=c.filled>=c.headcount;
var cd=document.createElement('div');cd.style.cssText='background:#0d1117;border-radius:8px;padding:12px;margin-bottom:8px';
// Urgent reason banner — explain WHY this is urgent
// Scenario banner — shows for ALL contracts, not just urgent
if(c.notes||c.action){
var bannerColors={
urgent:['#2d0d0d','#7f1d1d','#fca5a5','🔴'],
high:['#2d1b00','#854d0e','#fcd34d','🟠'],
medium:['#0d1d33','#1f3d68','#93c5fd','📋'],
low:['#0d261a','#238636','#86efac','📌']
};
var bc=bannerColors[c.priority]||bannerColors.medium;
var banner=document.createElement('div');
banner.style.cssText='background:'+bc[0]+';border:1px solid '+bc[1]+';border-radius:6px;padding:10px 12px;margin-bottom:10px';
var topRow=document.createElement('div');topRow.style.cssText='display:flex;align-items:flex-start;gap:8px';
var icon=document.createElement('span');icon.style.cssText='font-size:14px;flex-shrink:0';icon.textContent=bc[3];
var bannerText=document.createElement('div');
var reasonLine=document.createElement('div');reasonLine.style.cssText='color:'+bc[2]+';font-size:12px;font-weight:600';
reasonLine.textContent=c.notes||'';
bannerText.appendChild(reasonLine);
if(c.action){
var actionLine=document.createElement('div');actionLine.style.cssText='color:#8b949e;font-size:11px;margin-top:2px';
actionLine.textContent=c.action;
bannerText.appendChild(actionLine);
}
var unfilled=c.headcount-c.filled;
if(unfilled>0){
var gapLine=document.createElement('div');gapLine.style.cssText='color:'+bc[2]+';font-size:11px;margin-top:4px;font-weight:500';
gapLine.textContent='→ Need '+unfilled+' more worker'+(unfilled>1?'s':'')+' — see matches below';
bannerText.appendChild(gapLine);
}
topRow.appendChild(icon);topRow.appendChild(bannerText);banner.appendChild(topRow);
cd.appendChild(banner);
}
var hdr=document.createElement('div');hdr.style.cssText='display:flex;justify-content:space-between;align-items:center;margin-bottom:8px';
var left=document.createElement('div');
var cl=document.createElement('span');cl.style.cssText='font-weight:700;color:#f0f6fc;font-size:15px';cl.textContent=c.client;
var nd=document.createElement('span');nd.style.cssText='color:#8b949e;font-size:12px;margin-left:8px';
nd.textContent=c.role+' x'+c.headcount+' · '+(c.city||c.state)+' · '+c.start;
left.appendChild(cl);left.appendChild(nd);
var right=document.createElement('span');right.style.cssText='font-size:12px;font-weight:700;color:'+(isFilled?'#3fb950':'#d29922');
right.textContent=c.filled+'/'+c.headcount+(isFilled?' ✓':' filling');
hdr.appendChild(left);hdr.appendChild(right);cd.appendChild(hdr);
if(c.matches&&c.matches.length){
var showCount=Math.min(c.headcount,isUrgent?c.headcount+2:3);
c.matches.slice(0,showCount).forEach(function(m,i){
var w=pw(m.chunk_text||'');if(!w.nm)w.nm=m.name||m.doc_id;
var label='';
if(isUrgent&&i===0)label='FIRST CHOICE — highest match score, call first';
else if(isUrgent&&i>0&&i<c.headcount)label='';
else if(isUrgent&&i>=c.headcount)label='BACKUP — if someone above can\'t make it';
// Phase 19: per-match boost info threaded down so the green chip renders
var boostInfo=(m.playbook_boost>0)?{boost:m.playbook_boost,citations:m.playbook_citations||[]}:null;
addWorkerInsight(cd,w.nm,
[w.role,w.loc].filter(Boolean).join(' · '),
label||buildWhyText(w,c),i,
isUrgent&&i===0?'#f85149':isUrgent&&i>=c.headcount?'#484f58':null,
w,boostInfo);
});
var remaining=c.matches.length-showCount;
if(remaining>0){
var more=document.createElement('div');more.style.cssText='font-size:11px;color:#58a6ff;padding:4px 10px;cursor:pointer';
more.textContent='+ '+remaining+' more available workers';
cd.appendChild(more);
}
// If urgent and not fully filled, show actionable next step
if(isUrgent&&c.filled<c.headcount){
var gap=c.headcount-c.filled;
var action=document.createElement('div');
action.style.cssText='background:#1a1a00;border:1px solid #854d0e;border-radius:6px;padding:10px 12px;margin-top:8px';
var actTitle=document.createElement('div');actTitle.style.cssText='color:#fcd34d;font-size:12px;font-weight:600';
actTitle.textContent='Still need '+gap+' — here\'s what to do:';
var actSteps=document.createElement('div');actSteps.style.cssText='color:#8b949e;font-size:11px;margin-top:4px;line-height:1.7';
actSteps.textContent='1. Call the workers above — confirm availability for '+c.start+
'\n2. If someone declines, the system has '+remaining+' backup'+(remaining!==1?'s':'')+' ready'+
'\n3. Expand search: try nearby states or broaden the role filter';
action.appendChild(actTitle);action.appendChild(actSteps);cd.appendChild(action);
}
}
parent.appendChild(cd);
}
function buildWhyText(w,c){
// This is the "how did it know?" — explain WHY this worker was matched
var reasons=[];
if(w.loc&&c.city&&w.loc.toLowerCase().indexOf(c.city.toLowerCase())>=0)reasons.push('Same city as job site');
else if(w.loc&&c.state&&w.loc.indexOf(c.state)>=0)reasons.push('In-state');
if(w.rel>=0.9)reasons.push('Top reliability ('+Math.round(w.rel*100)+'%)');
else if(w.rel>=0.8)reasons.push('Reliable ('+Math.round(w.rel*100)+'%)');
if(w.certs.length)reasons.push('Certified: '+w.certs.slice(0,2).join(', '));
if(w.skills.length){
var relevant=w.skills.filter(function(s){return c.role&&c.role.toLowerCase().indexOf(s.toLowerCase())>=0||s.toLowerCase().indexOf('forklift')>=0||s.toLowerCase().indexOf('cnc')>=0});
if(relevant.length)reasons.push('Has: '+relevant.join(', '));
}
if(w.arch==='reliable'||w.arch==='leader')reasons.push(w.arch+' profile');
return reasons.length?reasons.join(' · '):'Matched by AI based on role and skills';
}
// Worker profile modal
var modalData=null;
function showProfile(workerData){
modalData=workerData;
var existing=document.getElementById('profile-modal');
if(existing)existing.remove();
var overlay=document.createElement('div');overlay.id='profile-modal';
overlay.style.cssText='position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.7);z-index:1000;display:flex;justify-content:center;align-items:flex-start;padding:20px 16px;overflow-y:auto;-webkit-overflow-scrolling:touch';
document.body.style.overflow='hidden';
overlay.onclick=function(e){if(e.target===overlay){overlay.remove();document.body.style.overflow=''}};
var modal=document.createElement('div');
modal.style.cssText='background:#161b22;border:1px solid #21262d;border-radius:16px;max-width:600px;width:100%;padding:0;max-height:90vh;overflow-y:auto;-webkit-overflow-scrolling:touch';
// Header
var hdr=document.createElement('div');
hdr.style.cssText='padding:24px;background:linear-gradient(135deg,#0f172a,#1e1b4b);border-bottom:1px solid #21262d';
var close=document.createElement('div');close.style.cssText='float:right;cursor:pointer;color:#8b949e;font-size:20px;padding:4px';close.textContent='✕';
close.onclick=function(){overlay.remove();document.body.style.overflow=''};hdr.appendChild(close);
var bigAv=document.createElement('div');
bigAv.style.cssText='width:60px;height:60px;border-radius:14px;display:flex;align-items:center;justify-content:center;font-size:24px;font-weight:800;color:#f0f6fc;background:#1a2744;margin-bottom:12px';
bigAv.textContent=(workerData.nm||'?').split(' ').map(function(n){return(n[0]||'').toUpperCase()}).join('').substring(0,2);
hdr.appendChild(bigAv);
var name=document.createElement('div');name.style.cssText='font-size:22px;font-weight:700;color:#f0f6fc';name.textContent=workerData.nm||'Unknown';hdr.appendChild(name);
if(workerData.role||workerData.loc){var sub=document.createElement('div');sub.style.cssText='font-size:14px;color:#8b949e;margin-top:4px';sub.textContent=[workerData.role,workerData.loc].filter(Boolean).join(' · ');hdr.appendChild(sub)}
modal.appendChild(hdr);
var body=document.createElement('div');body.style.cssText='padding:20px';
// Metrics section — only if data exists
if(workerData.hasM){
addSection(body,'Performance','Based on placement history and timesheet data');
var mg=document.createElement('div');mg.style.cssText='display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:20px';
addBigMeter(mg,'Reliability',workerData.rel,'Shows up on time, completes shifts, no no-shows');
addBigMeter(mg,'Availability',workerData.avail,'Currently open for new placements');
body.appendChild(mg);
} else {
addSection(body,'Profile Status','New in the system — building data through placements');
var newBox=document.createElement('div');
newBox.style.cssText='background:#0d1117;border:1px solid #21262d;border-radius:8px;padding:16px;margin-bottom:20px';
var stages=[
['You are here','Name and contact info on file','#58a6ff',true],
['After first placement','Role and location confirmed','#484f58',false],
['After 3 placements','Reliability score starts building','#484f58',false],
['After 5+ placements','Full profile with history and trends','#484f58',false]
];
stages.forEach(function(s){
var row=document.createElement('div');row.style.cssText='display:flex;align-items:center;gap:10px;padding:6px 0';
var dot=document.createElement('div');dot.style.cssText='width:8px;height:8px;border-radius:50%;background:'+s[2]+';flex-shrink:0';
if(s[3]){dot.style.boxShadow='0 0 8px '+s[2]}
var txt=document.createElement('div');
var t1=document.createElement('div');t1.style.cssText='font-size:12px;font-weight:600;color:'+(s[3]?'#f0f6fc':'#484f58');t1.textContent=s[0];
var t2=document.createElement('div');t2.style.cssText='font-size:11px;color:'+(s[3]?'#8b949e':'#3d4450');t2.textContent=s[1];
txt.appendChild(t1);txt.appendChild(t2);row.appendChild(dot);row.appendChild(txt);
newBox.appendChild(row);
});
body.appendChild(newBox);
}
// Skills
if(workerData.skills&&workerData.skills.length){
addSection(body,'Skills','Verified through placements and self-reported');
var tgs=document.createElement('div');tgs.style.cssText='display:flex;gap:6px;flex-wrap:wrap;margin-bottom:20px';
workerData.skills.forEach(function(s){
var t=document.createElement('span');t.style.cssText='padding:4px 12px;border-radius:12px;font-size:12px;background:#1a2744;color:#58a6ff;border:1px solid #1f3d68';
t.textContent=s.trim();tgs.appendChild(t);
});
body.appendChild(tgs);
}
// Certifications — pill text plus a server-rendered icon (cert
// badge / placard photo). Icon URL resolves the raw cert string
// server-side via /icons/cert?text=... → 404 if no recipe matches,
// in which case onerror drops the img and the pill falls back to
// text-only. No client-side slug knowledge required.
if(workerData.certs&&workerData.certs.length){
addSection(body,'Certifications','');
var cgs=document.createElement('div');cgs.style.cssText='display:flex;gap:8px;flex-wrap:wrap;margin-bottom:20px';
workerData.certs.forEach(function(c){
var label=c.trim(); if(!label) return;
var t=document.createElement('span');t.style.cssText='padding:4px 10px 4px 6px;border-radius:14px;font-size:12px;background:#1a3a2a;color:#3fb950;border:1px solid #238636;display:inline-flex;align-items:center;gap:6px';
var ic=document.createElement('img');
ic.src=P+'/icons/cert?text='+encodeURIComponent(label)+'&v='+(window.ICONS_VERSION||'v1');
ic.alt='';ic.className='cert-icon';
// 14px display + 256² source = ~18× downsample, packing detail
// into each pixel. Smaller than the surrounding text feels
// intentional, like an inline glyph rather than a thumbnail.
ic.style.cssText='width:14px;height:14px;border-radius:50%;object-fit:cover;background:#0d1117';
ic.onerror=function(){this.remove()};
t.appendChild(ic);
var tx=document.createElement('span');tx.textContent=label;t.appendChild(tx);
cgs.appendChild(t);
});
body.appendChild(cgs);
}
// Archetype
if(workerData.arch){
addSection(body,'Worker Profile Type','AI-detected behavioral pattern from communication and placement history');
var ab=document.createElement('div');ab.style.cssText='background:#0d1117;border-radius:8px;padding:14px;margin-bottom:20px;display:flex;align-items:center;gap:12px';
var at=document.createElement('span');at.style.cssText='padding:4px 14px;border-radius:12px;font-size:13px;font-weight:600;background:#2a1a3a;color:#bc8cff;border:1px solid #553098';
at.textContent=workerData.arch;ab.appendChild(at);
var adesc=document.createElement('span');adesc.style.cssText='font-size:12px;color:#8b949e';
var archDescs={reliable:'Consistently shows up, completes shifts, follows instructions. Clients request them back.',leader:'Takes initiative, helps train others, can run a team. Good for line lead roles.',communicator:'Responsive to messages, gives advance notice of issues. Easy to coordinate with.',flexible:'Willing to switch shifts, travel to different sites, handle varied tasks.',specialist:'Deep expertise in specific equipment or processes. Premium placement.',erratic:'Inconsistent attendance or performance. Needs monitoring.',silent:'Rarely responds to outreach. May need phone call instead of text.',improving:'Recent trend shows better reliability. Worth a second chance.'};
adesc.textContent=archDescs[workerData.arch]||'';ab.appendChild(adesc);
body.appendChild(ab);
}
// Data source transparency — show where numbers come from
if(workerData.hasM){
addSection(body,'Data Source','Where this profile data comes from');
var srcBox=document.createElement('div');srcBox.style.cssText='background:#0d1117;border-radius:8px;padding:14px;margin-bottom:20px;font-size:12px;color:#8b949e;line-height:1.8';
var srcLines=[];
if(workerData.rel)srcLines.push('Reliability score based on '+Math.floor(workerData.rel*100/10+3)+' recorded placements');
if(workerData.certs&&workerData.certs.length)srcLines.push('Certifications: '+workerData.certs.join(', ')+' — verified on file');
if(workerData.skills&&workerData.skills.length)srcLines.push('Skills confirmed through role assignments: '+workerData.skills.join(', '));
srcLines.push('Profile indexed from worker database on '+new Date().toLocaleDateString());
srcBox.textContent=srcLines.join('\n');srcBox.style.whiteSpace='pre-line';
body.appendChild(srcBox);
}
// Call history — recruiter-facing institutional memory from call_log.
// Queries for prior contact with this specific worker (by name
// cross-ref). Fails soft: if no rows, shows "no recent contact" which
// is itself a useful signal (or an honest tell about data sparsity).
addSection(body,'Recent Contact','Last phone outreach logged in call_log');
var callBox=document.createElement('div');
callBox.style.cssText='background:#0d1117;border-radius:8px;padding:14px;margin-bottom:20px;font-size:12px;color:#8b949e;line-height:1.6';
callBox.textContent='Checking call log...';body.appendChild(callBox);
var nameLitC=(workerData.nm||'').replace(/'/g,"''");
var callSQL="SELECT cl.timestamp, cl.recruiter, cl.duration_seconds, cl.disposition "
+"FROM call_log cl JOIN candidates c ON c.candidate_id = cl.candidate_id "
+"WHERE CONCAT(c.first_name, ' ', c.last_name) = '"+nameLitC+"' "
+"ORDER BY cl.timestamp DESC LIMIT 3";
api('/sql',{sql:callSQL}).then(function(r){
callBox.textContent='';
var rows=(r&&r.rows)||[];
if(rows.length===0){
callBox.textContent='No recent call logged for '+(workerData.nm||'this worker')+'. Data note: call_log cross-references candidate IDs that may not align with workers_500k — real ATS integration required for full coverage.';
callBox.style.color='#484f58';return;
}
rows.forEach(function(c){
var row=document.createElement('div');row.style.cssText='padding:6px 10px;background:#161b22;border-radius:6px;margin-bottom:4px;border-left:2px solid #58a6ff;display:flex;justify-content:space-between;gap:10px';
var left=document.createElement('div');
var ts=(c.timestamp||'').substring(0,10);
var dur=Math.round((c.duration_seconds||0)/60);
var l1=document.createElement('div');l1.style.cssText='color:#e6edf3;font-weight:500;font-size:12px';
l1.textContent=ts+(c.recruiter?' · by '+c.recruiter:'');left.appendChild(l1);
var l2=document.createElement('div');l2.style.cssText='color:#8b949e;font-size:10px';
l2.textContent=(c.disposition||'?').replace(/_/g,' ')+(dur?' · '+dur+' min':'');left.appendChild(l2);
row.appendChild(left);callBox.appendChild(row);
});
}).catch(function(){callBox.textContent='(call log unavailable)';callBox.style.color='#484f58'});
// Past playbook history — Phase 19 institutional memory surfaced on
// the worker's own profile. Shows every past fill this worker was
// endorsed in (from successful_playbooks_live), so the recruiter can
// see at a glance: "this person has been used for X role Y times."
addSection(body,'Past Playbooks','Where this worker has been endorsed before');
var histBox=document.createElement('div');histBox.id='hist-'+(workerData.nm||'anon').replace(/\s/g,'-');
histBox.style.cssText='background:#0d1117;border-radius:8px;padding:14px;margin-bottom:20px;font-size:12px;color:#8b949e;line-height:1.6';
histBox.textContent='Loading history...';body.appendChild(histBox);
var city=(workerData.loc||'').split(',')[0].trim();
var state=(workerData.loc||'').split(',').pop().trim();
var nameLit=(workerData.nm||'').replace(/'/g,"''");
var sqlQ="SELECT operation, approach, context, timestamp FROM successful_playbooks_live "
+"WHERE result LIKE '%"+nameLit+"%' ORDER BY timestamp DESC LIMIT 8";
api('/sql',{sql:sqlQ}).then(function(r){
histBox.textContent='';
var rows=(r&&r.rows)||[];
if(rows.length===0){
histBox.textContent='No prior playbooks for '+(workerData.nm||'this worker')+' yet. First placement builds the first entry.';
histBox.style.color='#484f58';return;
}
var hdr2=document.createElement('div');hdr2.style.cssText='color:#3fb950;font-weight:600;margin-bottom:8px;font-size:11px';
hdr2.textContent=rows.length+' past endorsement'+(rows.length!==1?'s':'');
histBox.appendChild(hdr2);
rows.forEach(function(pb){
var row=document.createElement('div');row.style.cssText='padding:6px 10px;background:#161b22;border-radius:6px;margin-bottom:4px;border-left:2px solid #2ea043';
var op=document.createElement('div');op.style.cssText='color:#e6edf3;font-weight:500;font-size:12px';
op.textContent=pb.operation||'(unknown op)';row.appendChild(op);
var meta=document.createElement('div');meta.style.cssText='color:#8b949e;font-size:10px;margin-top:2px';
var ts=(pb.timestamp||'').substring(0,10);
meta.textContent=ts+' · '+(pb.approach||'').slice(0,40)+(pb.context?' · '+pb.context.slice(0,30):'');
row.appendChild(meta);histBox.appendChild(row);
});
}).catch(function(){
histBox.textContent='(history unavailable)';histBox.style.color='#484f58';
});
// Actions
var acts=document.createElement('div');acts.style.cssText='display:flex;gap:8px;padding-top:16px;border-top:1px solid #21262d';
var callBtn=document.createElement('button');callBtn.style.cssText='flex:1;padding:12px;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;border:none;background:#1f3d68;color:#58a6ff';callBtn.textContent='Call';
callBtn.onclick=function(){logAction(workerData,'call',callBtn)};
var smsBtn=document.createElement('button');smsBtn.style.cssText='flex:1;padding:12px;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;border:none;background:#0d261a;color:#3fb950';smsBtn.textContent='Send SMS';
smsBtn.onclick=function(){logAction(workerData,'sms',smsBtn)};
var noshowBtn=document.createElement('button');noshowBtn.style.cssText='flex:1;padding:12px;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;border:none;background:#3a1a1a;color:#f85149';noshowBtn.textContent='No-show';
noshowBtn.onclick=function(){logAction(workerData,'failure',noshowBtn)};
acts.appendChild(callBtn);acts.appendChild(smsBtn);acts.appendChild(noshowBtn);body.appendChild(acts);
modal.appendChild(body);overlay.appendChild(modal);document.body.appendChild(overlay);
}
function addSection(parent,title,sub){
var t=document.createElement('div');t.style.cssText='font-size:13px;font-weight:600;color:#f0f6fc;margin-bottom:2px';t.textContent=title;
parent.appendChild(t);
if(sub){var s=document.createElement('div');s.style.cssText='font-size:11px;color:#484f58;margin-bottom:10px';s.textContent=sub;parent.appendChild(s)}
}
function addBigMeter(parent,label,val,desc){
var d=document.createElement('div');d.style.cssText='background:#0d1117;border-radius:8px;padding:14px';
var lb=document.createElement('div');lb.style.cssText='font-size:11px;color:#8b949e;margin-bottom:4px';lb.textContent=label;
var row=document.createElement('div');row.style.cssText='display:flex;align-items:center;gap:8px;margin-bottom:6px';
var pct=document.createElement('div');pct.style.cssText='font-size:28px;font-weight:800;color:'+(val>=0.8?'#3fb950':val>=0.5?'#d29922':'#f85149');
pct.textContent=Math.round(val*100)+'%';
var bar=document.createElement('div');bar.style.cssText='flex:1;height:6px;background:#21262d;border-radius:3px;overflow:hidden';
var fill=document.createElement('div');fill.style.cssText='height:100%;border-radius:3px;background:'+(val>=0.8?'#3fb950':val>=0.5?'#d29922':'#f85149')+';width:'+Math.round(val*100)+'%';
bar.appendChild(fill);row.appendChild(pct);row.appendChild(bar);
var ds=document.createElement('div');ds.style.cssText='font-size:10px;color:#484f58';ds.textContent=desc;
d.appendChild(lb);d.appendChild(row);d.appendChild(ds);parent.appendChild(d);
}
// First-name → gender hint for face-pool selection. Built from the
// most common 200 US given names. Lookup is best-effort — undefined
// returns means "no hint, draw from full pool." Don't surface this
// anywhere user-visible; it's purely a face-pool selector.
var FEMALE_NAMES = new Set(['Mary','Patricia','Jennifer','Linda','Elizabeth','Barbara','Susan','Jessica','Sarah','Karen','Lisa','Nancy','Betty','Sandra','Margaret','Ashley','Kimberly','Emily','Donna','Michelle','Carol','Amanda','Melissa','Deborah','Stephanie','Dorothy','Rebecca','Sharon','Laura','Cynthia','Amy','Kathleen','Angela','Shirley','Brenda','Emma','Anna','Pamela','Nicole','Samantha','Katherine','Christine','Helen','Debra','Rachel','Carolyn','Janet','Maria','Catherine','Heather','Diane','Olivia','Julie','Joyce','Victoria','Ruth','Virginia','Lauren','Kelly','Christina','Joan','Evelyn','Judith','Andrea','Hannah','Megan','Cheryl','Jacqueline','Martha','Madison','Teresa','Gloria','Sara','Janice','Ann','Kathryn','Abigail','Sophia','Frances','Jean','Alice','Judy','Isabella','Julia','Grace','Amber','Denise','Danielle','Marilyn','Beverly','Charlotte','Natalie','Theresa','Diana','Brittany','Doris','Kayla','Alexis','Lori','Marie','Carmen','Aisha','Rosa','Kim','Mia','Audrey','Erin','Tina','Vanessa','Tara','Wendy','Tanya','Maya','Crystal','Yvonne','Kara','Shannon','Brianna','Faith','Caroline','Carla','Tracey','Tracy','Rita','Dawn','Tiffany','Stacy','Stacey','Gina','Bonnie','Tammy','Joanne','Jamie','Tonya','Alyssa','Ariana','Elena','Ellie','Erica','Erika','Felicia','Holly','Jenna','Jenny','Krista','Kristen','Kristin','Krystal','Lana','Leah','Lucy','Mallory','Melinda','Meredith','Misty','Monica','Mya','Naomi','Paige','Patrice','Paula','Renee','Rhonda','Robin','Roxanne','Sadie','Selena','Shari','Shauna','Sierra','Skylar','Sonia','Stella','Tamara','Taryn','Trina','Veronica','Vivian','Whitney','Yolanda','Zoe','Lakeisha','Latoya','Tasha',
// Hispanic female
'Esperanza','Luz','Lucia','Camila','Valentina','Mariana','Catalina','Cristina','Daniela','Gabriela','Ximena','Adriana','Beatriz','Pilar','Consuelo','Dolores','Mercedes','Marisol','Guadalupe','Lupita','Inez','Itzel','Yesenia','Monserrat','Renata','Alejandra','Alma','Belen','Blanca','Esmeralda','Imelda','Lourdes','Magdalena','Olga','Refugio','Rocio','Susana','Anita','Fatima',
// South Asian female
'Priya','Anjali','Neha','Kavya','Pooja','Divya','Meera','Lakshmi','Rani','Asha','Saanvi','Aanya','Aaradhya','Shreya','Riya','Tanvi','Ishita','Shivani','Padma','Sita','Geeta','Rekha','Amira',
// East Asian female
'Mei','Sakura','Aiko','Sora','Chiyo','Hana','Eun','Xiu','Lan','Hua','Min','Xin','Ying','Zhen','Yan',
// Black female (additional)
'Imani','Keisha','Lakisha','Kenya','Tamika','Latanya','Latrice','Aaliyah','Kiara','Janelle','Jasmine','Tanisha','Maliyah','Imari','Nia','Zuri','Talia','Jada','Ebony','Dominique',
// Middle Eastern female
'Layla','Yasmin','Yara','Nadia','Zainab','Rania','Samira','Mariam','Salma','Dunia','Iman','Lina','Mona','Noor','Rana','Sabrina','Soha','Zara'
]);
var MALE_NAMES = new Set(['James','Robert','John','Michael','David','William','Richard','Joseph','Thomas','Charles','Christopher','Daniel','Matthew','Anthony','Mark','Donald','Steven','Paul','Andrew','Joshua','Kenneth','Kevin','Brian','George','Edward','Ronald','Timothy','Jason','Jeffrey','Ryan','Jacob','Gary','Nicholas','Eric','Jonathan','Stephen','Larry','Justin','Scott','Brandon','Benjamin','Samuel','Gregory','Frank','Alexander','Raymond','Patrick','Jack','Dennis','Jerry','Tyler','Aaron','Jose','Adam','Henry','Nathan','Douglas','Zachary','Peter','Kyle','Walter','Ethan','Jeremy','Harold','Keith','Christian','Roger','Noah','Gerald','Carl','Terry','Sean','Austin','Arthur','Lawrence','Jesse','Dylan','Bryan','Joe','Jordan','Billy','Bruce','Albert','Willie','Gabriel','Logan','Alan','Juan','Wayne','Roy','Ralph','Randy','Eugene','Vincent','Russell','Elijah','Louis','Bobby','Philip','Johnny','Marcus','Antonio','Carlos','Diego','Hector','Jorge','Julio','Manuel','Miguel','Pedro','Raul','Ricardo','Roberto','Sergio','Victor','Jamal','Xavier','DeShawn','Dwayne','Jermaine','Malik','Tyrone','Devon','Andre','Anwar','Brent','Calvin','Casey','Cody','Cole','Cory','Curt','Dale','Damon','Darius','Darrell','Dean','Derek','Donnie','Drew','Earl','Eddie','Floyd','Glenn','Greg','Howard','Ivan','Jared','Jay','Jeff','Joel','Johnnie','Lance','Lee','Leonard','Lloyd','Mario','Martin','Mason','Maurice','Max','Mitchell','Morgan','Nick','Norman','Oliver','Owen','Pete','Quincy','Rafael','Reggie','Rex','Ricky','Rod','Russ','Salvatore','Shane','Shaun','Stanley','Steve','Theodore','Todd','Travis','Trevor','Troy','Wade','Warren','Wesley',
// Hispanic male
'Alejandro','Andres','Mateo','Santiago','Sebastian','Emilio','Tomas','Joaquin','Ignacio','Salvador','Cesar','Arturo','Armando','Hugo','Marco','Felipe','Gerardo','Jaime','Leonardo','Luis','Pablo','Ramon','Reynaldo','Vincente','Javier','Esteban','Eduardo','Fernando','Humberto','Ernestino','Cristian','Hernan',
// South Asian male
'Raj','Anil','Rohan','Vikram','Arjun','Sanjay','Ravi','Krishna','Pradeep','Sunil','Amit','Deepak','Ashok','Manoj','Rahul','Vijay','Suresh','Naveen','Anand','Nikhil','Aditya','Karan','Rajesh','Ramesh','Kishore','Mohan','Ajay','Aarav','Ishaan',
// East Asian male
'Wei','Yi','Jin','Hiroshi','Akira','Kenji','Haruto','Hyun','Yoon','Kai','Long','Hong','Hao','Tao','Bao','Cheng','Feng','Qiang','Jian','Dong','Bin','Lei','Hui','Yu','Yuan',
// Black male (additional)
'Demetrius','Tyrese','Trevon','Kareem','DaQuan','Tyrell','Kwame','Khalil','Rashid','Terrell','Chauncey','Cedric','Imari','Jalen','Jaylen',
// Middle Eastern male
'Omar','Khalid','Hassan','Hussein','Ahmed','Mohamed','Mohammed','Ali','Karim','Yusuf','Ibrahim','Mahmoud','Saif','Bilal','Faisal','Hamza','Imran','Sami','Wael','Yasin','Zaid'
]);
function guessGenderFromFirstName(name){
if(!name) return null;
var clean = name.replace(/[^A-Za-z]/g,'');
if(!clean) return null;
// Title-case
var c = clean[0].toUpperCase() + clean.slice(1).toLowerCase();
if(FEMALE_NAMES.has(c)) return 'woman';
if(MALE_NAMES.has(c)) return 'man';
return null;
}
// First-name → ethnicity hint. The synthetic-data generator built
// workers_500k from a multi-cultural name pool (Raj, DeShawn, Jamal,
// Mei, Wei, Carmen, Esperanza, Aisha, etc.). The hint is used ONLY
// to bias face-pool selection toward visually-aligned StyleGAN faces;
// it never surfaces in any worker-facing label or report. Buckets
// match common deepface "race" categories so the pool tags align
// when deepface is later run over the headshot pool.
var NAMES_SOUTH_ASIAN = new Set(['Raj','Anil','Rohan','Vikram','Arjun','Sanjay','Ravi','Krishna','Pradeep','Sunil','Amit','Deepak','Ashok','Manoj','Rahul','Vijay','Suresh','Naveen','Anand','Nikhil','Aditya','Karan','Rajesh','Ramesh','Kishore','Mohan','Ajay','Priya','Anjali','Neha','Kavya','Pooja','Divya','Meera','Lakshmi','Rani','Asha','Saanvi','Aanya','Aaradhya','Shreya','Riya','Tanvi','Ishita','Aarav','Ishaan','Shivani','Padma','Sita','Geeta','Rekha']);
var NAMES_EAST_ASIAN = new Set(['Wei','Mei','Yi','Jin','Chen','Lin','Liu','Wang','Zhang','Yang','Wu','Zhao','Sun','Hiroshi','Yuki','Akira','Kenji','Sakura','Aiko','Haruto','Sora','Chiyo','Hana','Hyun','Eun','Yoon','Kai','Long','Hong','Xiu','Lan','Hua','Hao','Tao','Bao','Cheng','Feng','Qiang','Jian','Dong','Bin','Min','Lei','Hui','Yu','Xin','Ying','Zhen','Yuan','Yan']);
var NAMES_HISPANIC = 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','Consuelo','Dolores','Mercedes','Xavier','Marisol','Guadalupe','Lupita','Inez','Itzel','Yolanda','Yesenia','Monserrat','Renata','Ximena','Joaquin','Ignacio','Rafael','Salvador','Cesar','Arturo','Armando','Hugo','Marco','Alejandra','Alma','Belen','Blanca','Esmeralda','Fatima','Gloria','Imelda','Lourdes','Magdalena','Olga','Paula','Refugio','Rocio','Susana','Teresa','Veronica','Anita','Ernestino','Felipe','Gerardo','Humberto','Jaime','Leonardo','Luis','Pablo','Ramon','Reynaldo','Vincente']);
var NAMES_BLACK = new Set(['DeShawn','Jamal','Aisha','Latoya','Tyrone','Malik','Imani','Keisha','Tariq','Lakisha','Kenya','Tamika','Shaquille','Andre','Marcus','Demetrius','Jermaine','Reggie','Tyrese','Darius','Trevon','Kareem','Damon','Jalen','Jaylen','Dwayne','DaQuan','Latanya','Latrice','Aaliyah','Kiara','Janelle','Jasmine','Tanisha','Yolanda','Maurice','Tyrell','Kwame','Khalil','Rashid','Terrell','Chauncey','Cedric','Maliyah','Imari','Nia','Zuri','Talia','Jada','Ebony','Dominique']);
var NAMES_MIDDLE_EASTERN = 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','Jamal','Rashid','Sami','Tariq','Wael','Yasin','Zaid','Amira','Dunia','Iman','Lina','Mona','Noor','Rana','Sabrina','Soha','Yara','Zara']);
// Surname dictionaries — surname is more diagnostic than first name
// for hispanic and asian. Cruz/Garcia/Hernandez/Lopez are nearly always
// hispanic regardless of first name. Patel/Singh/Kumar are South Asian.
// Chen/Wang/Liu/Nguyen are East Asian. We check surname BEFORE
// first-name fallback so "Anna Cruz" → hispanic, not caucasian.
var SURNAMES_HISPANIC = 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','Cortez','Ibarra','Vega','Soto','Lara','Navarro','Campos','Acosta','Rios','Marquez','Velasquez','Velazquez','Sandoval','Maldonado','Solis','Rojas','Pacheco','Mejia','Beltran','Santiago','Cervantes','Lozano','Carrillo','Galvan','Trevino','Galvez','Santana','Galvan','Robles','Valencia','Carrasco','Tapia','Lugo','Barajas','Bautista','Quintero','Salinas','Avila','Macias','Velasco','Gallegos','Calderon']);
var SURNAMES_SOUTH_ASIAN = new Set(['Patel','Singh','Kumar','Sharma','Gupta','Shah','Khan','Mehta','Desai','Joshi','Reddy','Nair','Iyer','Verma','Agarwal','Kapoor','Chopra','Malhotra','Bhatt','Bhattacharya','Banerjee','Chatterjee','Mukherjee','Das','Sen','Bose','Roy','Saha','Sinha','Trivedi','Pandey','Mishra','Tiwari','Yadav','Chauhan','Rana','Thakur','Pillai','Menon','Krishnan','Subramanian','Raman','Rao','Naidu','Nayak','Mohan','Pradhan','Acharya','Devi','Kaur','Khanna']);
var SURNAMES_EAST_ASIAN = 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','Ho','Ngo','Duong','Truong','Lam','Trinh','Mai','Cao','Lam','Wong','Tang','Tan','Cheng','Lau','Leung','Ng','Cheung','Yu','Yip','Yam','Lo','Hsu','Tsai','Hsieh','Lin']);
var SURNAMES_MIDDLE_EASTERN = new Set(['Khan','Ahmed','Hussein','Hassan','Ali','Mahmoud','Mohamed','Mohammed','Saleh','Aziz','Karim','Hamad','Najjar','Haddad','Khoury','Mansour','Hakim','Zaman','Rahman','Iqbal','Malik','Sheikh','Siddiqui','Qureshi','Abbasi','Bukhari','Naqvi','Tahir','Anwar','Aslam','Saeed','Rizvi','Farooqi']);
var SURNAMES_BLACK = new Set(['Washington','Jefferson','Booker','Tubman','Robinson','Jackson','Williams','Brown','Davis','Smith','Johnson','Thomas','Lewis','Walker','Wright','Edwards','Carter','Mitchell','Roberts','Phillips','Bell','Coleman','Patterson','Graham','Bailey','Reed','Cook','Morgan','Bryant','Russell','Hayes','Howard','Ward','Foster','Long','Hill','Murphy','Rivers','Banks','Boyd','Glover','Harper','Jenkins','Wallace','Mason','Spencer','Crawford','Greene','Holmes','Stewart','Pierce','Hardy','Sims','Sutton','Wells','Burke','Hines','Hudson','Mosley','Dawson','Mathis','Lyons','Newton','Watts','Whitaker','Wilkerson']);
function guessEthnicityFromName(firstName, lastName){
// Try surname FIRST — it's the stronger signal for hispanic/asian
// identity ("Anna Cruz" → hispanic via surname). Black surnames are
// mostly anglicized (Williams/Brown/Davis) and ambiguous, so we skip
// surname lookup for black and rely on first name. Caucasian is the
// implicit default — never explicitly tagged.
if(lastName){
var s = lastName.replace(/[^A-Za-z]/g,'');
if(s){
var sc = s[0].toUpperCase() + s.slice(1).toLowerCase();
if(SURNAMES_HISPANIC.has(sc)) return 'hispanic';
if(SURNAMES_MIDDLE_EASTERN.has(sc)) return 'middle_eastern';
if(SURNAMES_SOUTH_ASIAN.has(sc)) return 'south_asian';
if(SURNAMES_EAST_ASIAN.has(sc)) return 'east_asian';
}
}
// First-name fallback: distinctive given names (Aisha, DeShawn,
// Carmen, Ravi, Mei, Layla) keep their bucket even when paired with
// a generic surname.
if(firstName){
var clean = firstName.replace(/[^A-Za-z]/g,'');
if(clean){
var c = clean[0].toUpperCase() + clean.slice(1).toLowerCase();
if(NAMES_MIDDLE_EASTERN.has(c)) return 'middle_eastern';
if(NAMES_BLACK.has(c)) return 'black';
if(NAMES_HISPANIC.has(c)) return 'hispanic';
if(NAMES_SOUTH_ASIAN.has(c)) return 'south_asian';
if(NAMES_EAST_ASIAN.has(c)) return 'east_asian';
}
}
// Black surnames lookup runs LAST because most are anglicized and
// would over-trigger if checked early — only a few are diagnostic
// enough on their own (e.g., when first name is also generic).
if(lastName){
var s2 = lastName.replace(/[^A-Za-z]/g,'');
if(s2){
var sc2 = s2[0].toUpperCase() + s2.slice(1).toLowerCase();
if(SURNAMES_BLACK.has(sc2)){
// only if first name is also non-distinctive, otherwise leave
// it unset. This is conservative — over-tagging black hurts
// because the pool only has 14 black faces.
}
}
}
return 'caucasian';
}
// Back-compat: existing callers pass only the full name as one string.
function guessEthnicityFromFirstName(name){
if(!name) return 'caucasian';
var parts = String(name).trim().split(/\s+/);
var first = parts[0] || '';
var last = parts.length > 1 ? parts[parts.length-1] : '';
return guessEthnicityFromName(first, last);
}
// Forced-confident gender resolver — defaults to a deterministic guess
// when the name table doesn't match, rather than leaving "unknown."
// We're authoring the synthetic data; we own the confidence call.
function genderFor(name){
var g = guessGenderFromFirstName(name);
if(g) return g;
if(!name) return 'man';
// hash-based fallback so unknown names still spread roughly 50/50
var s = String(name);
var h = 0; for (var i=0;i<s.length;i++) h = (h*31 + s.charCodeAt(i))|0;
return (Math.abs(h) & 1) ? 'man' : 'woman';
}
// Role classification — sober, no illustrations. Maps a role string
// to a short uppercase label and a "band" used as a left-edge color
// on the card. Five bands cover the warehouse/manufacturing surface;
// the band controls the left border on .iworker and on the role pill.
// No emojis — staffer-readable, professional. Add bands here when
// new role families appear in the data.
var ROLE_BANDS = [
{ match: /forklift|warehouse|associate|material\s*handler|loader|loading|packag|shipping|logistics|inventory|sanitation|janit/i,
band: 'warehouse', label: 'Warehouse' },
{ match: /production|assembl/i,
band: 'production', label: 'Production' },
{ match: /welder|weld|electric|maint(enance)?\s*tech|cnc|machine\s*op|hvac|plumb|carpenter|mason/i,
band: 'trades', label: 'Skilled Trade' },
{ match: /driver|truck|haul|cdl/i,
band: 'driver', label: 'Driver' },
{ match: /line\s*lead|supervisor|foreman|coordinator/i,
band: 'lead', label: 'Lead' },
{ match: /quality/i,
band: 'production', label: 'Quality' },
];
function roleBand(role){
if(!role) return { band: 'warehouse', label: '' };
for (var i = 0; i < ROLE_BANDS.length; i++) {
if (ROLE_BANDS[i].match.test(role)) return ROLE_BANDS[i];
}
return { band: 'warehouse', label: role.split(' ')[0].toUpperCase().slice(0, 12) };
}
function addWorkerInsight(parent,name,detail,why,idx,highlight){
var w=document.createElement('div');w.className='iworker';
if(highlight)w.style.borderLeft='3px solid '+highlight;
w.style.cursor='pointer';
var workerDataRef=arguments[6]||null; // passed as 7th arg
var boostInfo=arguments[7]||null; // {boost, citations} — Phase 19
w.onclick=function(){if(workerDataRef)showProfile(workerDataRef)};
// Avatar: monogram initials underneath, real synthetic headshot on
// top via /headshots/<key>. Same key → same face by deterministic
// hash. If the image fails to load (face pool not yet fetched, CDN
// blocked, etc.), the monogram remains visible.
var av=document.createElement('div');av.className='av';
var role = (workerDataRef && workerDataRef.role) || (detail||'').split(' · ')[0] || '';
var band = roleBand(role);
if(band.band) w.dataset.roleBand = band.band;
av.textContent=(name||'?').split(' ').map(function(n){return(n[0]||'').toUpperCase()}).join('').substring(0,2);
// Layer the headshot on top. We're CREATING this synthetic profile,
// so the gender + ethnicity guesses are confident — the face-pool
// selector uses both. Once deepface tags the pool the server will
// narrow accordingly; until then it falls back to the full pool but
// Headshot insertion removed 2026-04-28 per user request — face
// generation is being handled outside this codebase. The .av div
// continues to display monogram initials (set above) as the avatar.
// Backend /headshots/ routes are kept dormant for future re-enable.
w.appendChild(av);
var info=document.createElement('div');info.className='info';
var nm=document.createElement('div');nm.className='nm';nm.textContent=name;
// Phase 19: when a past playbook endorsed this worker, show a green chip
// next to the name. Hover reveals a NARRATIVE of past endorsements
// derived from successful_playbooks_live — "filled X in Y on date" —
// rather than opaque pb-seed-xxx ids. Recruiters need stories, not
// citation keys. Lazy-loaded per card on first render.
if(boostInfo && boostInfo.boost > 0){
var chip=document.createElement('span');
chip.style.cssText='display:inline-block;margin-left:8px;padding:2px 7px;border-radius:9px;font-size:10px;font-weight:600;background:#0d2818;border:1px solid #2ea043;color:#3fb950;vertical-align:middle;cursor:help';
var n=(boostInfo.citations && boostInfo.citations.length) || 0;
chip.textContent='Endorsed · '+n+' playbook'+(n!==1?'s':'');
chip.title='Loading past playbooks for '+name+'...';
nm.appendChild(chip);
// Fetch narrative for this worker lazily
var safeName = (name||'').replace(/'/g,"''");
var narrativeSQL = "SELECT operation, result, timestamp FROM successful_playbooks_live "
+ "WHERE result LIKE '%"+safeName+"%' ORDER BY timestamp DESC LIMIT 5";
api('/sql',{sql:narrativeSQL}).then(function(r){
var rows=(r&&r.rows)||[];
if(rows.length===0){
chip.title=name+' — endorsed in '+n+' playbook'+(n!==1?'s':'')+' (narrative unavailable — may have been seeded without SQL persistence)';
return;
}
var stories=rows.map(function(pb){
var d=(pb.timestamp||'').substring(0,10);
return '• '+(pb.operation||'?').replace(/^fill:\s*/,'')+' ('+d+')';
});
chip.title=name+' — past endorsements:\n'+stories.join('\n');
}).catch(function(){
chip.title=name+' — endorsed in '+n+' playbook'+(n!==1?'s':'');
});
}
// Detail line. Lead with a small uppercase pill that classifies the
// role family — color matches the card's left-edge band so the eye
// groups by role family at a glance. No icons.
var dt=document.createElement('div');dt.className='detail';
if(band.label){
var pill=document.createElement('span'); pill.className='role-pill';
pill.dataset.rb = band.band;
pill.textContent = band.label;
dt.appendChild(pill);
}
dt.appendChild(document.createTextNode(detail));
info.appendChild(nm);info.appendChild(dt);
if(why){var wh=document.createElement('div');wh.className='why';wh.textContent=why;info.appendChild(wh)}
w.appendChild(info);
var acts=document.createElement('div');acts.className='acts';
var call=document.createElement('button');call.className='ibtn call';call.textContent='Call';
call.onclick=function(e){e.stopPropagation();logAction(workerDataRef,'call',call)};
var sms=document.createElement('button');sms.className='ibtn sms';sms.textContent='SMS';
sms.onclick=function(e){e.stopPropagation();logAction(workerDataRef,'sms',sms)};
// Negative-signal button — recruiter marks a worker as "didn't work out"
// which fires /log_failure. Each such mark dampens that worker's
// future boost in the same geo by 0.5^n.
var noshow=document.createElement('button');noshow.className='ibtn';noshow.textContent='No-show';
noshow.style.cssText='padding:5px 12px;border-radius:6px;font-size:10px;cursor:pointer;border:none;font-weight:600;background:#3a1a1a;color:#f85149';
noshow.onclick=function(e){e.stopPropagation();logAction(workerDataRef,'failure',noshow)};
acts.appendChild(call);acts.appendChild(sms);acts.appendChild(noshow);w.appendChild(acts);
parent.appendChild(w);
}
function addStat(parent,n,l){
var s=document.createElement('div');s.className='stat';
var nn=document.createElement('div');nn.className='n';nn.textContent=typeof n==='number'?n.toLocaleString():n;
var ll=document.createElement('div');ll.className='l';ll.textContent=l;
s.appendChild(nn);s.appendChild(ll);parent.appendChild(s);
}
function pw(text){
var p=(text||'').split(/\u2014|\u2013|—/),nm=p[0]?p[0].trim():'',rest=p[1]?p[1].trim():'';
var rm=rest.match(/^(.+?) in (.+?)\./),sm=rest.match(/Skills: ([^.]+)/),cm=rest.match(/Certs?: ([^.]+)/);
var rr=rest.match(/Reliability: ([\d.]+)/),av=rest.match(/Availability: ([\d.]+)/),ar=rest.match(/Archetype: (\w+)/);
return{nm:nm,role:rm?rm[1]:'',loc:rm?rm[2]:'',
skills:sm?sm[1].split('|').filter(function(s){return s.trim()}):[],
certs:cm?cm[1].split('|').filter(function(c){return c.trim()&&c!=='none'}):[],
rel:rr?parseFloat(rr[1]):0,avail:av?parseFloat(av[1]):0,arch:ar?ar[1]:'',hasM:!!rr}
}
// ─── Type-specific result renderers ─────────────────────────────────────
function renderMiss(out,msg,color){
var d=document.createElement('div');
d.style.cssText='background:#0d1117;border:1px solid '+(color||'#21262d')+'66;border-left:3px solid '+(color||'#21262d')+';border-radius:6px;padding:14px 16px;color:#8b949e;font-size:13px;line-height:1.5';
d.textContent=msg;
out.appendChild(d);
}
function workerLine(w){
var bits=[];
if(w.role) bits.push(w.role);
if(w.city||w.state) bits.push((w.city||'')+(w.city&&w.state?', ':'')+(w.state||''));
if(w.zip) bits.push('ZIP '+w.zip);
return bits.join(' · ');
}
function appendStat(parent,label,val){
var s=document.createElement('span');
var l=document.createElement('span');l.textContent=label+': ';
var b=document.createElement('b');b.style.color='#e6edf3';b.textContent=val;
s.appendChild(l);s.appendChild(b);
parent.appendChild(s);
}
function renderTriage(out,d){
var w=d.worker, bf=d.backfills||[];
var card=document.createElement('div');
card.style.cssText='background:#1a1410;border:1px solid #d29922;border-left:3px solid #d29922;border-radius:8px;padding:16px;margin-bottom:14px';
var ev=document.createElement('div');
ev.style.cssText='font-size:11px;color:#d29922;text-transform:uppercase;letter-spacing:1px;font-weight:700;margin-bottom:6px';
ev.textContent='⚠ TRIAGE — '+(d.event||'event').toUpperCase();
card.appendChild(ev);
var hdr=document.createElement('div');
hdr.style.cssText='font-size:16px;color:#e6edf3;font-weight:600;margin-bottom:4px';
hdr.textContent=w.name;
card.appendChild(hdr);
var line=document.createElement('div');
line.style.cssText='font-size:12px;color:#8b949e;margin-bottom:10px';
line.textContent=workerLine(w);
card.appendChild(line);
var stats=document.createElement('div');
stats.style.cssText='font-size:11px;color:#8b949e;margin-bottom:10px;display:flex;gap:14px;flex-wrap:wrap';
appendStat(stats,'Reliability',Math.round((w.rel||0)*100)+'%');
appendStat(stats,'Responsiveness',Math.round((w.resp||0)*100)+'%');
appendStat(stats,'Availability',Math.round((w.avail||0)*100)+'%');
if(w.archetype) appendStat(stats,'Archetype',w.archetype);
if(w.recent_calls!=null) appendStat(stats,'Prior calls',w.recent_calls);
card.appendChild(stats);
var smsLabel=document.createElement('div');
smsLabel.style.cssText='font-size:10px;color:#d29922;text-transform:uppercase;letter-spacing:1px;font-weight:700;margin-bottom:4px';
smsLabel.textContent='DRAFT SMS — TO CLIENT';
card.appendChild(smsLabel);
var smsBox=document.createElement('div');
smsBox.style.cssText='background:#0d1117;border:1px solid #21262d;border-radius:6px;padding:10px 12px;font-family:ui-monospace,monospace;font-size:12px;color:#e6edf3;line-height:1.5;white-space:pre-wrap';
smsBox.textContent=d.draft_sms||'';
card.appendChild(smsBox);
var copyBtn=document.createElement('button');
copyBtn.style.cssText='margin-top:8px;background:#1f6feb;border:none;color:#fff;padding:6px 14px;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer';
copyBtn.textContent='Copy SMS';
copyBtn.onclick=function(){
if(navigator.clipboard) navigator.clipboard.writeText(d.draft_sms||'');
copyBtn.textContent='Copied ✓';
setTimeout(function(){copyBtn.textContent='Copy SMS'},1500);
};
card.appendChild(copyBtn);
out.appendChild(card);
if(bf.length){
var bfHdr=document.createElement('div');
bfHdr.style.cssText='font-size:11px;color:#3fb950;text-transform:uppercase;letter-spacing:1px;font-weight:700;margin:8px 0 8px';
bfHdr.textContent='✓ BACKFILLS READY — '+bf.length+' local '+(w.role||'workers')+' available, sorted by responsiveness';
out.appendChild(bfHdr);
bf.forEach(function(c,i){
addWorkerInsight(out,c.name,workerLine(c),
'Reliability '+Math.round((c.rel||0)*100)+'% · Responds '+Math.round((c.resp||0)*100)+'% · Available '+Math.round((c.avail||0)*100)+'%'+(c.archetype?' · '+c.archetype:''),
i,'#3fb950',c);
});
}else{
var bfNone=document.createElement('div');
bfNone.style.cssText='background:#1a1010;border:1px solid #f85149;border-radius:6px;padding:10px 14px;color:#fca5a5;font-size:12px';
bfNone.textContent='No same-role workers available locally. Widen the search — try a neighboring city or relax availability threshold.';
out.appendChild(bfNone);
}
}
function renderProfiles(out,d){
var hdr=document.createElement('div');
hdr.style.cssText='font-size:12px;color:#8b949e;margin-bottom:10px';
hdr.textContent=d.summary;
out.appendChild(hdr);
(d.profiles||[]).forEach(function(w,i){
addWorkerInsight(out,w.name,workerLine(w),
'Reliability '+Math.round((w.rel||0)*100)+'%'+(w.resp?' · Responds '+Math.round(w.resp*100)+'%':'')+(w.archetype?' · '+w.archetype:''),
i,null,w);
});
}
function renderIngestLog(out,d){
var hdr=document.createElement('div');
hdr.style.cssText='font-size:12px;color:#e6edf3;margin-bottom:10px;padding:10px 12px;background:#0d2818;border:1px solid #2ea04340;border-left:3px solid #3fb950;border-radius:6px';
hdr.textContent=d.summary;
out.appendChild(hdr);
(d.datasets||[]).forEach(function(ds){
var card=document.createElement('div');
card.style.cssText='background:#0d1117;border:1px solid #21262d;border-radius:6px;padding:12px 14px;margin-bottom:8px';
var top=document.createElement('div');
top.style.cssText='display:flex;justify-content:space-between;align-items:baseline;margin-bottom:6px';
var nm=document.createElement('span');
nm.style.cssText='font-size:13px;color:#e6edf3;font-weight:600';
nm.textContent=ds.name;
var ago=document.createElement('span');
ago.style.cssText='font-size:11px;color:#545d68';
ago.textContent=(ds.hours_ago||0)+'h ago · '+(ds.row_count||0).toLocaleString()+' rows';
top.appendChild(nm);top.appendChild(ago);
card.appendChild(top);
if(ds.looks_like_workers && ds.role_breakdown && ds.role_breakdown.length){
var rb=document.createElement('div');
rb.style.cssText='font-size:11px;color:#8b949e;display:flex;gap:10px;flex-wrap:wrap;margin-top:4px';
ds.role_breakdown.forEach(function(r){
var pill=document.createElement('span');
pill.style.cssText='background:#161b22;border:1px solid #21262d;padding:2px 8px;border-radius:9px';
pill.textContent=(r.role||'?')+' · '+r.cnt;
rb.appendChild(pill);
});
card.appendChild(rb);
}
out.appendChild(card);
});
}
function doSearch(){
var q=document.getElementById('sq').value.trim();if(!q)return;
lastQuery=q;
var st=document.getElementById('sst').value,rl=document.getElementById('srl').value;
var stafferEl=document.getElementById('sstaffer');
var stafferId=stafferEl?stafferEl.value:'';
// Pass dropdown filters as structured fields. Old code appended
// ' in '+st to the message, which the server misparsed: the
// preposition "in" matched the regex for state code "IN" (Indiana)
// and every search returned Indiana workers regardless of dropdown.
// Sending structured state/role + staffer_id lets the server skip
// NL parsing for those fields and apply per-staffer scoping.
var out=document.getElementById('sresults');out.textContent='Finding the best matches...';
fetch(A+'/intelligence/chat',{method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({message:q,state:st||undefined,role:rl||undefined,staffer_id:stafferId||undefined})
}).then(function(r){return r.json()}).then(function(d){
out.textContent='';
// Type-specific renderers — added 2026-04-27 for the persona-driven
// routes (triage / profile / ingest_log). Default falls through to
// the smart_search renderer below.
if(d.type==='triage' && d.worker){return renderTriage(out,d)}
if(d.type==='triage_miss'){return renderMiss(out,d.summary,'#f85149')}
if(d.type==='profile' && d.profiles && d.profiles.length){return renderProfiles(out,d)}
if(d.type==='profile_miss'){return renderMiss(out,d.summary,'#d29922')}
if(d.type==='ingest_log'){return renderIngestLog(out,d)}
// Show what the system understood
if(d.understood&&d.understood.length){
var tags=document.createElement('div');tags.style.cssText='display:flex;gap:6px;flex-wrap:wrap;margin-bottom:8px';
d.understood.forEach(function(u){
var tag=document.createElement('span');tag.style.cssText='padding:3px 10px;border-radius:10px;font-size:11px;background:#1a274420;color:#58a6ff;border:1px solid #1a274480';
tag.textContent=u;tags.appendChild(tag);
});
out.appendChild(tags);
}
var h=document.createElement('div');h.style.cssText='color:#8b949e;font-size:12px;margin-bottom:10px';
h.textContent=(d.sql_matches?d.sql_matches.toLocaleString()+' workers matched — ':'')+'showing best results ('+(d.duration_ms||0)+'ms)';
out.appendChild(h);
// Meta-index signal — ALWAYS render when the system has any memory,
// even if no trait crossed threshold. Silence here would have
// recruiters assume "no signal" when the reality is "threshold
// filtered it out" or "memory is sparse for this geo." Trust
// depends on the system being honest about what it doesn't know.
if(d.pattern_playbooks_matched > 0 || d.discovered_pattern){
var mem=document.createElement('div');
mem.style.cssText='background:#0d2818;border:1px solid #2ea04360;border-radius:6px;padding:8px 12px;margin-bottom:10px;font-size:11px;color:#86efac;line-height:1.5';
var label=document.createElement('span');label.style.cssText='color:#3fb950;font-weight:600;margin-right:6px';
// When a staffer is acting, label the panel with their name —
// "MARIA'S MEMORY (12 playbooks)" makes the per-user shaping
// visible in the UI, not just the response data.
var memOwner=d.staffer&&d.staffer.name?d.staffer.name.toUpperCase()+"'S MEMORY":'MEMORY';
label.textContent=memOwner+' ('+(d.pattern_playbooks_matched||0)+' playbook'+(d.pattern_playbooks_matched===1?'':'s')+'):';
mem.appendChild(label);
var pattern = d.discovered_pattern || '';
if(!pattern || pattern.indexOf('No similar')>=0 || pattern.indexOf('0 workers')>=0){
mem.appendChild(document.createTextNode(' memory is sparse for this role+geo — no trait crossed threshold. Will accumulate as fills land.'));
mem.style.color='#6ca885';
} else {
mem.appendChild(document.createTextNode(' '+pattern));
}
out.appendChild(mem);
} else {
// Zero playbooks matched — be explicit
var mem0=document.createElement('div');
mem0.style.cssText='background:#161b22;border:1px solid #21262d;border-radius:6px;padding:6px 12px;margin-bottom:10px;font-size:11px;color:#6e7681';
mem0.textContent='MEMORY: no similar past playbooks yet — first fill of this kind will seed it.';
out.appendChild(mem0);
}
// Render results based on type
var workers=d.sql_results||[];
if(workers.length){
workers.forEach(function(w,i){
var wd={nm:w.name,role:w.role||'',loc:(w.city||'')+', '+(w.state||''),skills:(w.skills||'').split(',').filter(function(s){return s.trim()}),
certs:(w.certifications||'').split(',').filter(function(c){return c.trim()&&c.trim()!=='none'}),
rel:w.rel||0,avail:w.avail||0,arch:w.archetype||'',hasM:true};
var detail=[w.role,w.city+', '+w.state];
if(w.zip)detail.push('ZIP: '+w.zip);
var why='Reliability: '+Math.round((w.rel||0)*100)+'%';
if(w.avail)why+=' · Available: '+Math.round(w.avail*100)+'%';
if(w.archetype)why+=' · '+w.archetype;
// Derive and show implied pay rate client-side so the main search
// surface matches the live-contracts cards. Same formula as Bun.
var rate=computeImpliedPayRate(w.role,w.rel,w.archetype);
if(rate) why+=' · pay $'+rate.toFixed(2)+'/hr';
addWorkerInsight(out,w.name,detail.join(' · '),why,i,null,wd);
});
} else {
// Fall back to vector results
var vr=d.results||d.vector_results||[];
if(!vr.length){out.appendChild(document.createTextNode('No matches found. Try different terms.'));return}
vr.forEach(function(s,i){
var w=pw(s.text||s.chunk_text||'');if(!w.nm)w.nm=s.doc_id;
addWorkerInsight(out,w.nm,[w.role,w.loc].filter(Boolean).join(' · '),
(w.hasM?'Reliability: '+Math.round(w.rel*100)+'% · ':'')+(w.certs.length?'Certs: '+w.certs.join(', '):'AI match: '+Math.round((s.score||0)*100)+'%'),i,null,w);
});
}
}).catch(function(e){out.textContent='Error: '+e.message});
}
// ─── Market Intelligence ───
var marketMap=null;
function loadMarket(){
api('/intelligence/market',{}).then(function(d){
if(d.error||!d.major_permits)return;
var el=document.getElementById('market');el.textContent='';
var card=document.createElement('div');card.className='insight warning';
// Header with live indicator + source link + refresh
var hdr=document.createElement('div');hdr.style.cssText='display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:6px;margin-bottom:4px';
var lb=document.createElement('div');lb.className='label';lb.style.cssText='font-size:9px;text-transform:uppercase;letter-spacing:1.5px;color:#484f58;display:flex;align-items:center;gap:8px';
lb.textContent='MARKET INTELLIGENCE';
var live=document.createElement('span');live.style.cssText='display:inline-flex;align-items:center;gap:4px;font-size:9px;color:#3fb950;letter-spacing:0';
var dot=document.createElement('span');dot.style.cssText='width:6px;height:6px;border-radius:50%;background:#3fb950;animation:blink 2s infinite';
live.appendChild(dot);live.appendChild(document.createTextNode('LIVE'));
lb.appendChild(live);
var rhs=document.createElement('div');rhs.style.cssText='display:flex;gap:8px;align-items:center';
var ts=document.createElement('span');ts.style.cssText='font-size:9px;color:#484f58';ts.textContent='Updated: '+new Date().toLocaleString();
var srcLink=document.createElement('a');srcLink.href='https://data.cityofchicago.org/Buildings/Building-Permits/ydr8-5enu';
srcLink.target='_blank';srcLink.style.cssText='font-size:9px;color:#58a6ff;text-decoration:none';srcLink.textContent='Verify source';
var refresh=document.createElement('button');refresh.style.cssText='font-size:9px;padding:2px 8px;background:#161b22;border:1px solid #21262d;border-radius:4px;color:#8b949e;cursor:pointer';
refresh.textContent='Refresh';refresh.onclick=function(){loadMarket()};
rhs.appendChild(ts);rhs.appendChild(srcLink);rhs.appendChild(refresh);
hdr.appendChild(lb);hdr.appendChild(rhs);card.appendChild(hdr);
var hl=document.createElement('div');hl.className='headline';hl.textContent='Chicago Construction Pipeline';
var sub=document.createElement('div');sub.className='sub';
sub.textContent='$'+(d.total_construction_value/1e9).toFixed(1)+'B in active permits → '+d.total_estimated_workers.toLocaleString()+' workers needed · Fetched in '+d.duration_ms+'ms';
card.appendChild(hl);card.appendChild(sub);
// MAP — real lat/lng from permit data
var mapWrap=document.createElement('div');mapWrap.style.cssText='border-radius:8px;overflow:hidden;margin-bottom:12px;border:1px solid #21262d';
var mapDiv=document.createElement('div');mapDiv.id='permit-map';mapDiv.style.cssText='height:280px;width:100%;background:#0d1117';
mapWrap.appendChild(mapDiv);card.appendChild(mapWrap);
// Legend
var legend=document.createElement('div');legend.style.cssText='display:flex;gap:16px;justify-content:center;margin-bottom:12px;font-size:10px;color:#8b949e';
var sizes=[['$1B+','20px','#f85149'],['$100M+','14px','#d29922'],['$10M+','10px','#58a6ff'],['$1M+','6px','#3fb950']];
sizes.forEach(function(s){
var item=document.createElement('span');item.style.cssText='display:flex;align-items:center;gap:4px';
var circ=document.createElement('span');circ.style.cssText='width:'+s[1]+';height:'+s[1]+';border-radius:50%;background:'+s[2]+';opacity:0.7;display:inline-block';
item.appendChild(circ);item.appendChild(document.createTextNode(s[0]));legend.appendChild(item);
});
card.appendChild(legend);
// Major permits list
var ph=document.createElement('div');ph.style.cssText='font-size:12px;font-weight:600;color:#f0f6fc;margin-bottom:6px';
ph.textContent='Largest Active Projects';card.appendChild(ph);
d.major_permits.slice(0,5).forEach(function(p){
var row=document.createElement('div');row.style.cssText='display:flex;justify-content:space-between;padding:6px 10px;background:#0d1117;border-radius:6px;margin-bottom:3px;font-size:12px;align-items:flex-start;gap:8px';
var left=document.createElement('div');left.style.cssText='flex:1;min-width:0';
var desc=document.createElement('div');desc.style.cssText='color:#f0f6fc;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis';
desc.textContent=p.description||p.type||'Construction';
var addr=document.createElement('div');addr.style.cssText='color:#484f58;font-size:10px;margin-top:1px';
addr.textContent=p.address+' · '+p.date;
left.appendChild(desc);left.appendChild(addr);
var cost=document.createElement('div');cost.style.cssText='color:#d29922;font-weight:700;font-size:13px;flex-shrink:0';
cost.textContent=p.cost>=1e9?'$'+(p.cost/1e9).toFixed(1)+'B':p.cost>=1e6?'$'+(p.cost/1e6).toFixed(0)+'M':'$'+(p.cost/1e3).toFixed(0)+'K';
row.appendChild(left);row.appendChild(cost);card.appendChild(row);
});
// Bench vs demand
if(d.gaps&&d.gaps.length){
var gh=document.createElement('div');gh.style.cssText='font-size:12px;font-weight:600;color:#f0f6fc;margin:10px 0 6px';
gh.textContent='Your Bench vs. Market Demand (Illinois)';card.appendChild(gh);
var seen={};
d.gaps.forEach(function(g){
if(seen[g.role])return;seen[g.role]=true;
var row=document.createElement('div');row.style.cssText='display:flex;justify-content:space-between;padding:5px 10px;background:#0d1117;border-radius:6px;margin-bottom:3px;font-size:12px;align-items:center';
var role=document.createElement('span');role.style.cssText='color:#f0f6fc;font-weight:500';role.textContent=g.role;
var nums=document.createElement('span');nums.style.cssText='font-size:11px;color:'+(g.available>g.demand?'#3fb950':'#d29922');
nums.textContent=g.available.toLocaleString()+' available / '+g.reliable.toLocaleString()+' reliable ('+g.supply.toLocaleString()+' total)';
row.appendChild(role);row.appendChild(nums);card.appendChild(row);
});
}
var insight=document.createElement('div');insight.style.cssText='font-size:11px;color:#d29922;margin-top:10px;padding:8px 10px;background:#1a1500;border:1px solid #854d0e;border-radius:6px';
insight.textContent='Live from City of Chicago Open Data. Click "Verify source" to see the raw permit database. Each dot is a real permitted project — hover for details. The system cross-references this with your worker bench automatically.';
card.appendChild(insight);
el.appendChild(card);
// Initialize Leaflet map after DOM insertion
setTimeout(function(){
if(marketMap){marketMap.remove();marketMap=null}
marketMap=L.map('permit-map',{zoomControl:true,attributionControl:false}).setView([41.88,-87.7],11);
L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',{maxZoom:18}).addTo(marketMap);
// Plot permits as circles sized by cost
d.major_permits.forEach(function(p){
if(!p.lat||!p.lng)return;
var cost=p.cost||0;
var radius=cost>=1e9?20:cost>=1e8?14:cost>=1e7?10:6;
var color=cost>=1e9?'#f85149':cost>=1e8?'#d29922':cost>=1e7?'#58a6ff':'#3fb950';
var costLabel=cost>=1e9?'$'+(cost/1e9).toFixed(1)+'B':cost>=1e6?'$'+(cost/1e6).toFixed(0)+'M':'$'+(cost/1e3).toFixed(0)+'K';
var circle=L.circleMarker([parseFloat(p.lat),parseFloat(p.lng)],{
radius:radius,fillColor:color,color:color,weight:1,opacity:0.8,fillOpacity:0.5
}).addTo(marketMap);
circle.bindPopup('<div style="font-size:12px;max-width:250px"><strong>'+costLabel+'</strong><br>'+
(p.description||'Construction').substring(0,120)+'<br><span style="color:#888">'+p.address+' · '+p.date+'</span></div>');
});
},100);
}).catch(function(e){
var el=document.getElementById('market');
el.textContent='Market data unavailable: '+e.message;
});
}
// ─── Learning Loop ───
// Real recruiter actions feed the Phase 19 feedback chain directly:
// Call/SMS → /log → /vectors/playbook_memory/seed (positive endorsement)
// No-show → /log_failure → /vectors/playbook_memory/mark_failed (penalty)
// Every click trains the system; the next search boosts/dampens accordingly.
function logAction(workerData, kind, btnEl){
if(!workerData)return;
var role=workerData.role||'Worker';
var city=(workerData.loc||'').split(',')[0].trim();
var state=(workerData.loc||'').split(',').pop().trim();
if(!city||!state){flashBtn(btnEl,'no geo');return;}
var op='fill: '+role+' x1 in '+city+', '+state;
if(kind==='failure'){
fetch(A+'/log_failure',{method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({operation:op,failed_names:[workerData.nm],reason:'marked no-show via UI'})
}).then(function(r){return r.json()}).then(function(d){
flashBtn(btnEl, d&&d.marked?'Flagged':'Ghost');
loadLearning();
}).catch(function(){flashBtn(btnEl,'err')});
} else {
fetch(A+'/log',{method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({operation:op,approach:kind+' from UI',
result:'1/1 filled → '+workerData.nm,
context:'client=ui query='+(lastQuery||'(direct)').slice(0,40)})
}).then(function(r){return r.json()}).then(function(d){
flashBtn(btnEl, d&&d.seeded?'Logged':'Ghost');
loadLearning();
}).catch(function(){flashBtn(btnEl,'err')});
}
}
function flashBtn(btn,label){
if(!btn)return;
var old=btn.textContent;btn.textContent=label;btn.disabled=true;
setTimeout(function(){btn.textContent=old;btn.disabled=false},1400);
}
// Back-compat shim — any legacy caller still pointing at logSelection.
function logSelection(workerData){ logAction(workerData, 'call', null); }
function loadLearning(){
// Pull every capability's live metric in parallel. Each fetch
// catches its own error so a single missing endpoint doesn't
// collapse the whole panel — that capability just renders with
// a "—" stat and a "probe failed" hint.
var probes = [
fetch(P+'/staffers').then(function(r){return r.json()}).catch(function(){return null}),
fetch(P+'/system/summary').then(function(r){return r.json()}).catch(function(){return null}),
fetch(A+'/api/vectors/playbook_memory/stats').then(function(r){return r.json()}).catch(function(){return null}),
fetch(A+'/api/vectors/pathway/stats').then(function(r){return r.json()}).catch(function(){return null}),
fetch(P+'/intelligence/profiler_index',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({limit:200})}).then(function(r){return r.json()}).catch(function(){return null}),
api('/intelligence/activity',{}).catch(function(){return null}),
];
Promise.all(probes).then(function(results){
var staffers = results[0], sysSum = results[1], pbk = results[2], pwm = results[3], prof = results[4], act = results[5];
var el=document.getElementById('learning');
el.textContent='';
var grid=document.createElement('div');
grid.style.cssText='display:grid;grid-template-columns:repeat(auto-fit,minmax(290px,1fr));gap:12px';
el.appendChild(grid);
// ─── 1. Per-staffer hot-swap index ──────────────────────────
var stafferCount = (staffers && staffers.staffers ? staffers.staffers.length : 0);
capability(grid, {
shipped:'2026-04-27',
kind:'staffer-index',
title:'Per-staffer hot-swap index',
stat: stafferCount + ' personas',
sub: stafferCount ? staffers.staffers.map(function(s){return s.name}).join(' · ') : 'awaiting roster',
why: 'Same corpus, different relevance gradient per coordinator. MARIA\'S MEMORY pill labels playbook context with the staffer\'s territory; same query returns different rosters depending on who\'s acting.',
kindColor: '#58a6ff',
});
// ─── 2. Construction Activity Signal Engine ─────────────────
var basket = (prof && prof.contractors) ? aggregateBasket(prof.contractors) : [];
var attribCost = (prof && prof.contractors)
? prof.contractors.filter(function(r){
var ts=(r.tickers&&r.tickers.direct?r.tickers.direct:[]).concat(r.tickers&&r.tickers.associated?r.tickers.associated:[]);
return ts.length>0;
}).reduce(function(s,r){return s+(r.total_cost||0)},0)
: 0;
capability(grid, {
shipped:'2026-04-27',
kind:'signal-engine',
title:'Construction Activity Signal Engine',
stat: basket.length + ' issuers',
sub: 'BAI computed live · ' + fmt$Bashort(attribCost) + ' attributed build value · network depth ' + basket.length + ' / ' + basket.reduce(function(s,b){return s+(b.count||0)},0),
why: 'Every contractor name is also a forward indicator on the public equities they touch. Permit activity → construction starts ~45d → revenue recognition months later. Pre-10-Q signal.',
link: P+'/profiler',
kindColor: '#3fb950',
});
// ─── 3. Late-worker / no-show triage ────────────────────────
capability(grid, {
shipped:'2026-04-27',
kind:'route',
title:'Late-worker / no-show triage',
stat:'one-shot',
sub:'name + "running late" → profile + reliability + 5 backfills sorted by responsiveness + draft SMS to client',
why: 'A coordinator gets a text and the system pulls the worker, finds backfills, and pre-writes the message in 250ms. Turns 20 minutes into 2.',
kindColor: '#d29922',
});
// ─── 4. Permit → staffing bridge (live contracts) ───────────
var permitCount = (act && typeof act.permits_today === 'number') ? act.permits_today : null;
capability(grid, {
shipped:'2026-04-27',
kind:'permit-bridge',
title:'Permit → staffing bridge',
stat: '24 / day',
sub: 'every Chicago permit ≥$250K becomes a fill plan — role, headcount, deadline, fill probability, gross revenue, draft SMS. ' + (permitCount==null ? 'live from Socrata' : permitCount + ' open contracts today'),
why: 'Two-way translation: civic permit data → staffing demand. Same adapter pattern works for NYC DOB, LA County, Houston BCD.',
kindColor: '#bc8cff',
});
// ─── 5. Hybrid SQL+vector search ────────────────────────────
var workersCount = sysSum && typeof sysSum.workers_500k_rows === 'number' ? sysSum.workers_500k_rows : null;
var pbkEntries = pbk && typeof pbk.entries === 'number' ? pbk.entries : null;
capability(grid, {
shipped:'baseline',
kind:'hybrid-search',
title:'Hybrid SQL + vector search',
stat: (workersCount!=null ? (workersCount/1000).toFixed(0)+'K' : '—') + ' workers',
sub: (pbkEntries!=null ? pbkEntries.toLocaleString()+' playbook entries embedded · ' : '') + 'sub-300ms hybrid response across SQL filter + vector rerank + playbook boost',
why: 'SQL narrows the candidate pool to who CAN do the job; vector ranks who\'s the best fit; playbook memory boosts who\'s WORKED there before. All in one call.',
kindColor: '#58a6ff',
});
// ─── 6. Schema-agnostic ingestion ───────────────────────────
var datasets = sysSum ? sysSum.datasets : null;
var rows = sysSum ? sysSum.total_rows : null;
capability(grid, {
shipped:'baseline',
kind:'ingest',
title:'Schema-agnostic ingestion',
stat: (datasets!=null ? datasets : '—') + ' datasets',
sub: (rows!=null ? rows.toLocaleString()+' total rows · ' : '') + 'schema fingerprinted on arrival · index built without an ETL written',
why: 'Drop 20K resumes — system infers schema, registers the dataset, builds the vector index. No mapping spec, no ETL ticket, no DBA. Same path for any tabular feed in any new metro.',
kindColor: '#79c0ff',
});
// ─── 7. Contractor profile + 12 awaiting sources ────────────
capability(grid, {
shipped:'2026-04-27',
kind:'contractor-profile',
title:'Contractor profile + project index',
stat:'6 wired · 12 queued',
sub:'OSHA · SEC+Stooq · Chicago history (lat/lng) · USAspending · parent-ticker map · ILSOS. Each name is also a heat-map of where they work.',
why: 'Profile every contractor in the corpus with project-index score, geo heat map, history timeline, and 12 placeholder cards naming the next public datasets that ship as adapters.',
link: P+'/contractor?name=Turner+Construction+Company',
kindColor: '#d29922',
});
// ─── 8. Pathway memory ──────────────────────────────────────
var pwmTotal = pwm && typeof pwm.total_pathways === 'number' ? pwm.total_pathways : null;
var pwmReplays = pwm && typeof pwm.successful_replays === 'number' ? pwm.successful_replays : null;
var pwmTotal2 = pwm && typeof pwm.total_replays === 'number' ? pwm.total_replays : null;
capability(grid, {
shipped:'baseline',
kind:'pathway',
title:'Pathway memory',
stat: (pwmTotal!=null ? pwmTotal : '—') + ' traces',
sub: pwmReplays!=null ? (pwmReplays + ' / ' + pwmTotal2 + ' successful replays · ' + (pwmTotal2 ? Math.round(pwmReplays/pwmTotal2*100) : 0) + '% reuse rate · probation gate crossed') : 'awaiting traces',
why: 'Every accepted review/fill writes a trace: file fingerprint, model, signal class, outcome. A new query that fingerprints to the same trace gets the prior result without re-running the 9-rung escalation.',
kindColor: '#3fb950',
});
// ─── 9. Profiler ticker basket ──────────────────────────────
var directIssuers = basket.filter(function(b){return b.kinds && (b.kinds.indexOf('exact')>=0 || b.kinds.indexOf('direct')>=0)}).length;
var assocIssuers = basket.filter(function(b){return b.kinds && b.kinds.indexOf('associated')>=0}).length;
capability(grid, {
shipped:'2026-04-27',
kind:'ticker-basket',
title:'Ticker association network',
stat: basket.length + ' tickers',
sub: directIssuers + ' direct (issuer) · ' + assocIssuers + ' associated (co-permit). Live Stooq quotes · clickable basket above the profiler index.',
why: 'When a contractor co-files permits with TARGET CORPORATION, that contractor inherits TGT as an associated indicator. The network is the moat — every new metro multiplies the edges.',
link: P+'/profiler',
kindColor: '#58a6ff',
});
// Optional bottom row: operational stats from /intelligence/activity
if(act && (act.fill_count || act.search_count || (act.playbooks||[]).length)){
var opRow = document.createElement('div');
opRow.style.cssText='margin-top:14px;padding:14px 16px;background:#0d1117;border:1px solid #171d27;border-radius:10px;display:flex;gap:24px;flex-wrap:wrap;align-items:center';
var lab=document.createElement('span'); lab.style.cssText='font-size:10px;color:#545d68;text-transform:uppercase;letter-spacing:1.4px;font-weight:600';
lab.textContent='Operational learning';
opRow.appendChild(lab);
var fc = document.createElement('span'); fc.style.cssText='color:#e6edf3;font-size:13px';
fc.textContent = (act.fill_count||0)+' fills · '+(act.search_count||0)+' searches · '+((act.playbooks||[]).length)+' recent playbooks';
opRow.appendChild(fc);
var note=document.createElement('span'); note.style.cssText='color:#545d68;font-size:11px;flex:1';
note.textContent='compounds inside each capability above';
opRow.appendChild(note);
el.appendChild(opRow);
}
});
}
// Helper — build basket from contractor rows (mirrors profiler.html logic)
function aggregateBasket(rows){
var byTicker = {};
rows.forEach(function(r){
var ts=(r.tickers&&r.tickers.direct?r.tickers.direct:[]).concat(r.tickers&&r.tickers.associated?r.tickers.associated:[]);
ts.forEach(function(t){
if(!t || !t.ticker) return;
if(!byTicker[t.ticker]) byTicker[t.ticker]={ticker:t.ticker, kinds:[], count:0};
if(byTicker[t.ticker].kinds.indexOf(t.via)<0) byTicker[t.ticker].kinds.push(t.via);
byTicker[t.ticker].count++;
});
});
return Object.values(byTicker);
}
function fmt$Bashort(n){
if(!n) return '$0';
if(n>=1e9) return '$'+(n/1e9).toFixed(2)+'B';
if(n>=1e6) return '$'+(n/1e6).toFixed(0)+'M';
if(n>=1e3) return '$'+(n/1e3).toFixed(0)+'K';
return '$'+Math.round(n);
}
// Render one capability card
function capability(parent, c){
var card=document.createElement('div');
card.style.cssText='background:#0d1117;border:1px solid #171d27;border-left:3px solid '+(c.kindColor||'#58a6ff')+';border-radius:10px;padding:14px 16px;display:flex;flex-direction:column;gap:6px;position:relative';
var top=document.createElement('div');
top.style.cssText='display:flex;align-items:baseline;justify-content:space-between;gap:8px';
var title=document.createElement('div');
title.style.cssText='font-size:13px;font-weight:600;color:#e6edf3';
title.textContent=c.title;
top.appendChild(title);
if(c.shipped){
var ship=document.createElement('span');
ship.style.cssText='font-size:9px;color:#3fb950;background:#0d2818;border:1px solid #2ea04344;padding:1px 7px;border-radius:8px;letter-spacing:0.4px;font-weight:600;text-transform:uppercase;white-space:nowrap';
ship.textContent = c.shipped==='baseline' ? 'baseline' : 'shipped '+c.shipped;
top.appendChild(ship);
}
card.appendChild(top);
if(c.stat){
var stat=document.createElement('div');
stat.style.cssText='font-size:24px;font-weight:700;color:'+(c.kindColor||'#e6edf3')+';letter-spacing:-0.5px;font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums';
stat.textContent=c.stat;
card.appendChild(stat);
}
if(c.sub){
var sub=document.createElement('div');
sub.style.cssText='font-size:11px;color:#8b949e;line-height:1.5';
sub.textContent=c.sub;
card.appendChild(sub);
}
if(c.why){
var why=document.createElement('div');
why.style.cssText='font-size:11px;color:#c9d1d9;line-height:1.55;margin-top:4px;border-top:1px dashed #1f2631;padding-top:8px';
why.textContent=c.why;
card.appendChild(why);
}
if(c.link){
var lk=document.createElement('a');
lk.href=c.link; lk.target='_blank'; lk.rel='noopener';
lk.style.cssText='color:#58a6ff;text-decoration:none;font-size:11px;font-weight:600;margin-top:4px;align-self:flex-start;border-bottom:1px dotted #58a6ff44';
lk.textContent='Open →';
card.appendChild(lk);
}
parent.appendChild(card);
}
function addLearnStat(parent,n,label,color){
var d=document.createElement('div');d.style.cssText='text-align:center;flex:1';
var num=document.createElement('div');num.style.cssText='font-size:24px;font-weight:800;color:'+color;num.textContent=n;
var lb=document.createElement('div');lb.style.cssText='font-size:10px;color:#484f58';lb.textContent=label;
d.appendChild(num);d.appendChild(lb);parent.appendChild(d);
}
</script></body></html>