demo: profiler index — directory of every Chicago contractor
J asked for "a profiler index that shows a history of everyone." This
is a /profiler directory page (also reachable via /contractors) that
ranks every contractor who's filed a Chicago permit, by total permit
value. Rows are clickable into the full /contractor profile.
Defaults: since 2025-06-01, min permit cost $250K, top 200 contractors
by total_cost. Server pulls two Socrata GROUP BY queries (one keyed on
contact_1_name, one on contact_2_name), merges them so contractors
listed in either applicant or contractor slot appear once with combined
counts/cost. ~300ms cold.
UI: live search box, since-date selector, min-cost selector, sortable
columns (name / permits / total_cost / last_filed). Live numbers as of
this write: 200 contractors, 1,702 permits, $14.22B aggregate. Filter
"Target" returns TARGET CORPORATION + CORPORATION TARGET (name variants
from Socrata).
Also fixes J's other complaint — "no new contracts, Target is gone":
/intelligence/permit_contracts was hard-capped at $limit=6 + only
the most recent 6 over $250K, so any day with 6 fresh permits would
push older contractors (Target) off the panel entirely. Now defaults
to 24 (caller can pass body.limit up to 100), so 2-3 days of permits
stay on the panel. Added body.contractor — passes a name into the
WHERE so the staffer can pin a specific contractor to the panel
("Target Corporation" → 3 of their permits over $250K).
Server-side:
- new POST /intelligence/profiler_index — paginated contractor index
(since, min_cost, search, limit) with merged contact_1+contact_2
aggregations
- /intelligence/permit_contracts — body.limit + body.contractor
- /profiler and /contractors routes serve profiler.html
Front-end:
- new mcp-server/profiler.html — sortable table, live filter, deep
links to /contractor?name=... (prefix-aware via P, so /lakehouse
works on devop.live)
- search.html + console.html nav: added "Profiler" link
Verified end-to-end via playwright on the public URL.
This commit is contained in:
+101
-3
@@ -876,6 +876,78 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
// Profiler index — directory of every contractor that has filed
|
||||
// a Chicago permit recently, ranked by permit count + total
|
||||
// cost. Each name in the response links to the full /contractor
|
||||
// profile page. Answers J's ask: "a profiler index that shows
|
||||
// a history of everyone." Pulled live from Socrata; the
|
||||
// count/cost aggregations let the staffer see who's actually
|
||||
// active vs one-off LLCs.
|
||||
if (url.pathname === "/intelligence/profiler_index" && req.method === "POST") {
|
||||
const start = Date.now();
|
||||
const b = await json();
|
||||
const sinceDate = String(b.since || "2025-06-01");
|
||||
const minCost = Math.max(0, Number(b.min_cost) || 100000);
|
||||
const limit = Math.max(1, Math.min(500, Number(b.limit) || 200));
|
||||
const search = String(b.search || "").trim();
|
||||
const permitUrl = "https://data.cityofchicago.org/resource/ydr8-5enu.json";
|
||||
// Group by contact_1_name AND by contact_2_name in two
|
||||
// queries, then merge — Socrata's GROUP BY only takes one
|
||||
// expression and we want both contractor slots.
|
||||
const buildQuery = (col: string) => {
|
||||
const where = [
|
||||
`reported_cost>${minCost}`,
|
||||
`issue_date>'${sinceDate.replace(/'/g, "")}'`,
|
||||
`${col} IS NOT NULL`,
|
||||
];
|
||||
if (search) {
|
||||
const s = search.replace(/'/g, "''").toUpperCase();
|
||||
where.push(`upper(${col}) like '%${s}%'`);
|
||||
}
|
||||
return `${permitUrl}?$select=${col} AS name,count(*) as cnt,sum(reported_cost) as total_cost,max(issue_date) as last_filed&`
|
||||
+ `$where=${encodeURIComponent(where.join(" AND "))}`
|
||||
+ `&$group=${col}&$order=total_cost DESC&$limit=${limit}`;
|
||||
};
|
||||
try {
|
||||
const [byC1, byC2] = await Promise.all([
|
||||
fetch(buildQuery("contact_1_name")).then((r) => r.json()).catch(() => []),
|
||||
fetch(buildQuery("contact_2_name")).then((r) => r.json()).catch(() => []),
|
||||
]);
|
||||
const merged: Record<string, { name: string; permits: number; total_cost: number; last_filed: string; roles: Set<string> }> = {};
|
||||
const consume = (rows: any[], role: string) => {
|
||||
for (const r of rows || []) {
|
||||
const n = (r.name || "").trim();
|
||||
if (!n) continue;
|
||||
const cnt = parseInt(r.cnt, 10) || 0;
|
||||
const cost = parseFloat(r.total_cost || "0") || 0;
|
||||
const last = r.last_filed || "";
|
||||
const key = n.toUpperCase();
|
||||
if (!merged[key]) merged[key] = { name: n, permits: 0, total_cost: 0, last_filed: "", roles: new Set() };
|
||||
merged[key].permits += cnt;
|
||||
merged[key].total_cost += cost;
|
||||
if (last > merged[key].last_filed) merged[key].last_filed = last;
|
||||
merged[key].roles.add(role);
|
||||
}
|
||||
};
|
||||
consume(byC1, "applicant");
|
||||
consume(byC2, "contractor");
|
||||
const rows = Object.values(merged)
|
||||
.map((r) => ({ ...r, roles: Array.from(r.roles) }))
|
||||
.sort((a, b) => b.total_cost - a.total_cost)
|
||||
.slice(0, limit);
|
||||
return ok({
|
||||
count: rows.length,
|
||||
since: sinceDate,
|
||||
min_cost: minCost,
|
||||
search,
|
||||
contractors: rows,
|
||||
duration_ms: Date.now() - start,
|
||||
});
|
||||
} catch (e: any) {
|
||||
return err(`profiler_index: ${e.message}`, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Staffer roster — read by the UI dropdown so each coordinator
|
||||
// can act under their own identity (per-staffer hot-swap index).
|
||||
if (url.pathname === "/api/staffers" || url.pathname === "/staffers") {
|
||||
@@ -1032,6 +1104,14 @@ async function main() {
|
||||
// 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.
|
||||
// Profiler index — directory page of everyone who's filed a
|
||||
// Chicago permit (clickable directory of contractors).
|
||||
if (url.pathname === "/profiler" || url.pathname === "/contractors") {
|
||||
return new Response(Bun.file(import.meta.dir + "/profiler.html"), {
|
||||
headers: { "Content-Type": "text/html; charset=utf-8" },
|
||||
});
|
||||
}
|
||||
|
||||
if (url.pathname === "/contractor") {
|
||||
return new Response(Bun.file(import.meta.dir + "/contractor.html"), {
|
||||
headers: { ...cors, "Content-Type": "text/html" },
|
||||
@@ -1351,6 +1431,7 @@ async function main() {
|
||||
if (url.pathname === "/intelligence/permit_contracts" && req.method === "POST") {
|
||||
const start = Date.now();
|
||||
try {
|
||||
const b: any = await req.json().catch(() => ({}));
|
||||
const permitUrl = "https://data.cityofchicago.org/resource/ydr8-5enu.json";
|
||||
// Recent + substantial permits only — skip tiny ones that
|
||||
// don't imply real staffing demand.
|
||||
@@ -1358,10 +1439,27 @@ async function main() {
|
||||
// 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.
|
||||
// Caller-controlled limit: J reported the live panel was
|
||||
// dropping older permits (Target) because $limit=6 only ever
|
||||
// showed today's 6 newest. Default 24 so a few days of
|
||||
// permits stay on the panel; allow up to 100 via body.
|
||||
const reqLimit = Math.max(1, Math.min(100, Number((b as any)?.limit) || 24));
|
||||
// Optional contractor-name filter — lets the panel scope to
|
||||
// a specific contact_1 or contact_2 name (e.g. "Target
|
||||
// Corporation") so the user can pin a contractor to the panel
|
||||
// without it scrolling past.
|
||||
const cFilter = String((b as any)?.contractor || "").trim().replace(/'/g, "''");
|
||||
const wherePieces: string[] = [
|
||||
"reported_cost>250000",
|
||||
"issue_date>'2025-06-01'",
|
||||
];
|
||||
if (cFilter) {
|
||||
wherePieces.push(`(upper(contact_1_name)=upper('${cFilter}') OR upper(contact_2_name)=upper('${cFilter}'))`);
|
||||
}
|
||||
const permits: any[] = await fetch(
|
||||
`${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`
|
||||
`${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,latitude,longitude&`
|
||||
+ `$where=${encodeURIComponent(wherePieces.join(" AND "))}`
|
||||
+ `&$order=issue_date DESC&$limit=${reqLimit}`
|
||||
).then(r => r.json()).catch(() => []);
|
||||
|
||||
const typeToRole: Record<string, string> = {
|
||||
|
||||
Reference in New Issue
Block a user