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:
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user