From 2965b68a9ddf0552757b2bed76f4af0cfefadf15 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 27 Apr 2026 22:08:24 -0500 Subject: [PATCH] =?UTF-8?q?demo:=20profiler=20index=20=E2=80=94=20ticker?= =?UTF-8?q?=20associations=20(direct,=20parent,=20co-permit)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit J's framing: "if a contractor works for Target, future Target contracts mean money flows back to the contractor — the ticker is an associated indicator." Now the profiler index attaches three flavors of ticker per contractor and renders them as colored pills: green DIRECT contractor IS the public issuer (Target Corp → TGT) amber PARENT contractor is a subsidiary of a public parent (Turner Construction → HOC.DE via Hochtief AG) blue ASSOCIATED contractor co-appears on permits with a public entity (TORNOW, KYLE F → TGT, 3 shared permits with TARGET CORPORATION) The associated flavor is the correlation signal J described — it pulls the ticker for whoever the contractor has been working *with*, not just what they are themselves. Most contractors are private; the associated link is how the moat shows up. Server-side: - entity.ts new export `lookupTickerLite(name)` — cheap in-memory resolver that does only the SEC tickers index lookup + curated KNOWN_PARENT_MAP check, no per-call SEC profile or Stooq fetch. ~10ms per name after the index is loaded once. - /intelligence/profiler_index now runs a third Socrata pull (5K permit pairs in window) to build a co-occurrence map. For each contractor in the result, attaches: .tickers.direct[] — name matches a public issuer .tickers.associated[] — top 5 co-permit partners that resolve to a ticker, with partner_name + co_permits count + partner_via reason Front-end: - mcp-server/profiler.html — new .ticker-pill styles (3 colors per attribution kind), pills render under the contractor name in the table. Hover title gives the full reason path. Verified end-to-end on the public URL: search="tornow" → blue TGT pill, hint "Associated via co-permits with TARGET CORPORATION (3 shared permits) — TARGET CORP" search="target" → green TGT × 2 (TARGET CORPORATION + CORPORATION TARGET name variants both resolve direct to the same issuer) default top 200 → 15 ticker pills surface across the page including JPM (via JPMORGAN CHASE BANK co-permits) and parent-link tickers for the construction majors. --- mcp-server/entity.ts | 40 ++++++++++++++++++ mcp-server/index.ts | 90 ++++++++++++++++++++++++++++++++++++++-- mcp-server/profiler.html | 30 ++++++++++++++ 3 files changed, 156 insertions(+), 4 deletions(-) diff --git a/mcp-server/entity.ts b/mcp-server/entity.ts index f1b45cb..26a6dd2 100644 --- a/mcp-server/entity.ts +++ b/mcp-server/entity.ts @@ -1140,6 +1140,46 @@ async function fetchStooqQuote(ticker: string): Promise<{ } } +// Cheap name-to-ticker lookup for batch use (e.g. profiler index over +// 200 contractors). Hits the in-memory SEC tickers index and the +// KNOWN_PARENT_MAP. No SEC profile fetch, no Stooq fetch. Returns +// every ticker we can attribute to a name, with a tag describing the +// link (direct = name matches a public issuer, parent = the company is +// a subsidiary with a public parent in our curated map). +export type LiteTicker = { + ticker: string; + via: "direct" | "parent" | "exact"; + matched_name?: string; + exchange?: string; + reason?: string; +}; +export async function lookupTickerLite(name: string): Promise { + const out: LiteTicker[] = []; + // Direct SEC match (in-memory) + const direct = await resolveSecTicker(name); + if (direct) { + out.push({ + ticker: direct.ticker, + via: normalizeEntityName(direct.title) === normalizeEntityName(name) ? "exact" : "direct", + matched_name: direct.title, + }); + } + // Parent map (curated) + const key = normalizeEntityName(name); + for (const [k, v] of Object.entries(KNOWN_PARENT_MAP)) { + if (key && key.includes(normalizeEntityName(k)) && v.status === "ok" && v.parent_ticker) { + out.push({ + ticker: v.parent_ticker, + via: "parent", + matched_name: v.parent_name, + exchange: v.parent_exchange, + reason: v.link_source, + }); + } + } + return out; +} + export async function fetchTickerBrief(name: string): Promise { const now = new Date().toISOString(); const cached = await cacheGet(normalizeEntityName(name), "ticker"); diff --git a/mcp-server/index.ts b/mcp-server/index.ts index d83eb16..6772b07 100644 --- a/mcp-server/index.ts +++ b/mcp-server/index.ts @@ -908,10 +908,30 @@ async function main() { + `$where=${encodeURIComponent(where.join(" AND "))}` + `&$group=${col}&$order=total_cost DESC&$limit=${limit}`; }; + // Co-occurrence query — pulls the contact_1+contact_2 pairs in + // the window so we can attribute tickers across associations + // ("Bob's Electric works for Target → show TGT"). Capped 5K + // permits, ~200ms cold; resolved tickers are in-memory after. + const buildCoQuery = () => { + const where = [ + `reported_cost>${minCost}`, + `issue_date>'${sinceDate.replace(/'/g, "")}'`, + "contact_1_name IS NOT NULL", + "contact_2_name IS NOT NULL", + ]; + if (search) { + const s = search.replace(/'/g, "''").toUpperCase(); + where.push(`(upper(contact_1_name) like '%${s}%' OR upper(contact_2_name) like '%${s}%')`); + } + return `${permitUrl}?$select=contact_1_name,contact_2_name` + + `&$where=${encodeURIComponent(where.join(" AND "))}` + + `&$limit=5000`; + }; try { - const [byC1, byC2] = await Promise.all([ + const [byC1, byC2, coRows] = await Promise.all([ fetch(buildQuery("contact_1_name")).then((r) => r.json()).catch(() => []), fetch(buildQuery("contact_2_name")).then((r) => r.json()).catch(() => []), + fetch(buildCoQuery()).then((r) => r.json()).catch(() => []), ]); const merged: Record }> = {}; const consume = (rows: any[], role: string) => { @@ -931,16 +951,78 @@ async function main() { }; consume(byC1, "applicant"); consume(byC2, "contractor"); - const rows = Object.values(merged) + + // Build co-occurrence map from the permit pairs. For each + // contractor key, count how many permits they co-appeared + // on with each other party. + const coMap: Record> = {}; + for (const r of (Array.isArray(coRows) ? coRows : []) as any[]) { + const a = String(r.contact_1_name || "").trim(); + const b = String(r.contact_2_name || "").trim(); + if (!a || !b) continue; + const ka = a.toUpperCase(); + const kb = b.toUpperCase(); + if (ka === kb) continue; + (coMap[ka] = coMap[ka] || {})[kb] = (coMap[ka][kb] || 0) + 1; + (coMap[kb] = coMap[kb] || {})[ka] = (coMap[kb][ka] || 0) + 1; + } + + // Attach tickers per contractor — direct, parent, and any + // tickers attributable to top co-occurring partners ("works + // with TARGET CORPORATION → TGT shown as associated"). Resolves + // via the in-memory SEC tickers index + curated parent map, + // so the cost is per-name index-lookup, not a network call. + const { lookupTickerLite } = await import("./entity.js"); + // Memoize per-name to skip duplicate lookups across the + // associated step. + const tickerCache: Record = {}; + const lookupCached = async (n: string) => { + const k = n.toUpperCase(); + if (tickerCache[k]) return tickerCache[k]; + tickerCache[k] = await lookupTickerLite(n); + return tickerCache[k]; + }; + + const rowsBase = Object.values(merged) .map((r) => ({ ...r, roles: Array.from(r.roles) })) .sort((a, b) => b.total_cost - a.total_cost) .slice(0, limit); + + // Resolve tickers concurrently (in-memory ops, but Promise.all + // keeps the code uniform with future remote ticker sources). + const enriched = await Promise.all(rowsBase.map(async (r) => { + const direct = await lookupCached(r.name); + const partners = Object.entries(coMap[r.name.toUpperCase()] || {}) + .sort((a, b) => b[1] - a[1]) + .slice(0, 6); + const associated: any[] = []; + const seen = new Set(direct.map((t: any) => t.ticker)); + for (const [partnerName, occurrences] of partners) { + const ts = await lookupCached(partnerName); + for (const t of ts) { + if (seen.has(t.ticker)) continue; + seen.add(t.ticker); + associated.push({ + ticker: t.ticker, + via: "associated", + partner_name: partnerName, + co_permits: occurrences, + partner_via: t.via, + matched_name: t.matched_name, + }); + if (associated.length >= 5) break; + } + if (associated.length >= 5) break; + } + return { ...r, tickers: { direct, associated } }; + })); + return ok({ - count: rows.length, + count: enriched.length, since: sinceDate, min_cost: minCost, search, - contractors: rows, + contractors: enriched, duration_ms: Date.now() - start, }); } catch (e: any) { diff --git a/mcp-server/profiler.html b/mcp-server/profiler.html index cd3c4a3..c89e124 100644 --- a/mcp-server/profiler.html +++ b/mcp-server/profiler.html @@ -30,6 +30,12 @@ td.name a:hover{text-decoration:underline} td.right{text-align:right;font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums} td.role{font-size:10px;color:#8b949e} td.role .pill{display:inline-block;padding:2px 7px;border-radius:9px;font-size:9px;font-weight:600;background:#161b22;border:1px solid #21262d;color:#8b949e;margin-right:4px;text-transform:uppercase;letter-spacing:0.5px} +.tickers{display:flex;gap:4px;flex-wrap:wrap;margin-top:3px} +.ticker-pill{display:inline-block;padding:1px 7px;border-radius:5px;font-size:10px;font-weight:700;font-family:ui-monospace,SFMono-Regular,monospace;letter-spacing:0.3px;cursor:help} +.ticker-pill.direct{background:#0d2818;border:1px solid #2ea04388;color:#3fb950} +.ticker-pill.parent{background:#1a1410;border:1px solid #d2992288;color:#d29922} +.ticker-pill.associated{background:#0d1830;border:1px solid #58a6ff66;color:#58a6ff} +.ticker-pill.exact{background:#0d2818;border:1px solid #2ea043;color:#3fb950} .cost-band-1{color:#3fb950} .cost-band-2{color:#d29922} .cost-band-3{color:#f85149} @@ -181,6 +187,30 @@ function render(){ a.target='_blank'; a.rel='noopener'; a.textContent = r.name; ntd.appendChild(a); + // Ticker association pills — direct (green) = the contractor is a + // public issuer; parent (amber) = subsidiary of a public parent; + // associated (blue) = co-appears on permits with a public entity. + // Shows the correlation indicator J described — when Bob's Electric + // works permits with Target, TGT renders here as associated. + var t = r.tickers || {direct:[], associated:[]}; + var allTk = (t.direct||[]).concat(t.associated||[]); + if(allTk.length){ + var trk = document.createElement('div'); trk.className='tickers'; + allTk.forEach(function(x){ + var p = document.createElement('span'); + p.className = 'ticker-pill ' + (x.via||'direct'); + p.textContent = x.ticker; + // Tooltip shows the full reason path + var hint = x.via === 'associated' + ? 'Associated via co-permits with '+x.partner_name+' ('+(x.co_permits||0)+' shared permits)' + (x.matched_name ? ' — '+x.matched_name : '') + : x.via === 'parent' + ? 'Subsidiary of '+(x.matched_name||x.ticker) + (x.exchange ? ' · '+x.exchange : '') + : 'Direct match: '+(x.matched_name||r.name); + p.title = hint; + trk.appendChild(p); + }); + ntd.appendChild(trk); + } tr.appendChild(ntd); var ptd=document.createElement('td'); ptd.className='right'; ptd.textContent=(r.permits||0).toLocaleString();