mcp: contractor entity-brief drill-down + mobile UX pass

Adds /contractor page route plus /intelligence/contractor_profile
endpoint that fans out across OSHA, ticker, history, parent_link,
federal contracts, debarment, NLRB, ILSOS, news, diversity certs,
BLS macro — single per-contractor portfolio view across every
wired source.

search.html: mobile responsive layout, fixed bottom dock with
horizontal scroll-snap, legacy bridge row stacking, viewport
overflow guards.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
root 2026-04-25 17:07:23 -05:00
parent a6c83b03e5
commit b843a23433
2 changed files with 1256 additions and 76 deletions

View File

@ -18,6 +18,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { z } from "zod";
import { startTrace, logSpan, logGeneration, scoreTrace, flush as flushTraces } from "./tracing.js";
import { buildPermitBrief } from "./entity.js";
const BASE = process.env.LAKEHOUSE_URL || "http://localhost:3100";
const PORT = parseInt(process.env.MCP_PORT || "3700");
@ -960,6 +961,61 @@ async function main() {
return new Response(Bun.file(import.meta.dir + "/console.html"));
}
// ─── Contractor / entity drill-down page ───
// Single-contractor portfolio view across every wired source:
// OSHA national, Chicago history, ticker chart, parent link,
// federal contracts, debarment, unions, training. Click any
// contractor name in a permit Entity Brief to land here.
if (url.pathname === "/contractor") {
return new Response(Bun.file(import.meta.dir + "/contractor.html"), {
headers: { ...cors, "Content-Type": "text/html" },
});
}
if (url.pathname === "/intelligence/contractor_profile" && req.method === "POST") {
const start = Date.now();
try {
const b = (await req.json().catch(() => ({}))) as { name?: string };
if (!b.name) return err("missing name", 400);
// Use the entity-brief library directly — single entity, all sources.
const { fetchOshaBrief, fetchTickerBrief, fetchContractorHistory, fetchParentLink, fetchFederalContracts, fetchDebarmentBrief, fetchNlrbBriefReal, fetchIlsosBrief, fetchNewsMentions, fetchDiversityCerts, scoreNewsSentiment, fetchBlsConstructionTrend, normalizeEntityName, entityTicker } = await import("./entity.js");
const [osha, stock, history, parent_link, federal, debarment, nlrb, ilsos, news, diversity, macro] = await Promise.all([
fetchOshaBrief(b.name),
fetchTickerBrief(b.name),
fetchContractorHistory(b.name),
fetchParentLink(b.name),
fetchFederalContracts(b.name),
fetchDebarmentBrief(b.name),
fetchNlrbBriefReal(b.name),
fetchIlsosBrief(b.name),
fetchNewsMentions(b.name),
fetchDiversityCerts(b.name),
fetchBlsConstructionTrend(),
]);
const news_sentiment = news ? scoreNewsSentiment(news) : null;
return ok({
key: normalizeEntityName(b.name),
display_name: b.name,
ticker: entityTicker(b.name),
osha,
stock,
history,
parent_link,
federal,
debarment,
nlrb,
ilsos,
news,
news_sentiment,
diversity,
macro,
generated_at: new Date().toISOString(),
duration_ms: Date.now() - start,
});
} catch (e: any) {
return err(`contractor_profile: ${e.message}`, 500);
}
}
// Intelligence: Market data — public building permits → staffing demand forecast
if (url.pathname === "/intelligence/market" && req.method === "POST") {
const start = Date.now();
@ -1232,8 +1288,12 @@ async function main() {
const permitUrl = "https://data.cityofchicago.org/resource/ydr8-5enu.json";
// Recent + substantial permits only — skip tiny ones that
// don't imply real staffing demand.
// Include contact_1 + contact_2 fields so the Entity Brief
// panel on each card can populate without a second fetch.
// Contacts identify the applicant / contractor by name —
// those are the keys we pass to OSHA/ILSOS enrichment.
const permits: any[] = await fetch(
`${permitUrl}?$select=permit_type,work_type,work_description,reported_cost,street_number,street_direction,street_name,community_area,issue_date&`
`${permitUrl}?$select=id,permit_type,work_type,work_description,reported_cost,street_number,street_direction,street_name,community_area,issue_date,contact_1_name,contact_1_type,contact_2_name,contact_2_type&`
+ `$where=reported_cost>250000 AND issue_date>'2025-06-01'`
+ `&$order=issue_date DESC&$limit=6`
).then(r => r.json()).catch(() => []);
@ -1367,12 +1427,19 @@ async function main() {
contracts.push({
permit: {
id: p.id,
cost,
work_type: p.work_type || "General construction",
description: (p.work_description || "").substring(0, 140),
address: `${p.street_number || ""} ${p.street_direction || ""} ${p.street_name || ""}`.trim(),
community_area: p.community_area,
issue_date: (p.issue_date || "").substring(0, 10),
// Contacts — used by /intelligence/permit_entities to
// enrich each card with OSHA + ILSOS on expand.
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 || "",
},
implied_bill_rate: contractBillRate,
timeline: {
@ -1426,6 +1493,58 @@ async function main() {
}
}
// Intelligence: per-permit entity brief — OSHA + ILSOS + property
// Takes a permit identifier (we look it up from Chicago Socrata) or
// raw contact fields directly from the client. Returns an "ETF
// basket" shape: property + entities + per-entity risk factors.
// OSHA is live-scraped (cached 30d). ILSOS returns a structured
// placeholder because apps.ilsos.gov blocks our ASN.
if (url.pathname === "/intelligence/permit_entities" && req.method === "POST") {
const start = Date.now();
try {
const b = await req.json().catch(() => ({})) as {
permit_id?: string;
address?: string;
work_type?: string;
contact_1_name?: string;
contact_1_type?: string;
contact_2_name?: string;
contact_2_type?: string;
fetch_osha?: boolean;
fetch_ilsos?: boolean;
};
// If the caller didn't pass contact fields but did pass a
// permit_id, go pull the record from Chicago Socrata.
let permit = b;
if (b.permit_id && !b.contact_1_name) {
const u = `https://data.cityofchicago.org/resource/ydr8-5enu.json?$where=id='${encodeURIComponent(b.permit_id)}'`;
const rows = (await fetch(u).then((r) => r.json())) as any[];
const p = rows?.[0];
if (p) {
const addr = [p.street_number, p.street_direction, p.street_name]
.filter(Boolean)
.join(" ");
permit = {
permit_id: b.permit_id,
address: addr,
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,
};
}
}
const brief = await buildPermitBrief(permit, {
fetchOsha: b.fetch_osha !== false,
fetchIlsos: b.fetch_ilsos !== false,
});
return ok({ ...brief, duration_ms: Date.now() - start });
} catch (e: any) {
return err(`permit_entities: ${e.message}`, 500);
}
}
// Removed 2026-04-20: /intelligence/learn was a legacy CSV writer
// that destructively re-wrote successful_playbooks. /log and
// /log_failure replace it cleanly via /vectors/playbook_memory/seed

File diff suppressed because it is too large Load Diff