demo: profiler index — ticker associations (direct, parent, co-permit)

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.
This commit is contained in:
root
2026-04-28 06:01:04 -05:00
parent f6a7621b2d
commit ba41ad2846
3 changed files with 156 additions and 4 deletions
+40
View File
@@ -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<LiteTicker[]> {
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<TickerBrief> {
const now = new Date().toISOString();
const cached = await cacheGet(normalizeEntityName(name), "ticker");