/** Side-by-side operator comparison (2–5 operators): summary, pipeline, velocity, new markets, recent projects. */ import type { OperatorComparison } from "@dci/core"; import { pg, projectLive } from "../lib/sql.js"; import { reqStr, type Row } from "../lib/rows.js"; import { notFound } from "../lib/http.js"; import { expansionVelocity, pipelineBreakdown, scopeFor } from "../lib/pipeline.js"; import { newMarkets, operatorSummaryById } from "./operators.js"; import { projectsWhere } from "./projects.js"; export const COMPARE_METHODOLOGY = "Each column is computed the same way as the operator page: containment-aware facility counts, known MW = published operational figures only (coverage given per operator), pipeline from facility statuses and live project records (hidden / merged projects excluded), velocity windows from opened_on (else first_seen) and announced_on (else first indexed)."; export async function compareOperators(slugs: string[]): Promise { const sql = pg(); const ids: string[] = []; for (const slug of slugs) { const r = (await sql`select id from operators where slug = ${slug} or id = ${slug} limit 1`)[0]; if (!r) throw notFound(`operator ${slug}`); ids.push(reqStr(r.id)); } const operators = await Promise.all(ids.map(async (id) => { const scope = scopeFor(sql, { operatorId: id }); const [summary, pipeline, velocity, markets, recentProjects, countries] = await Promise.all([ operatorSummaryById(id), pipelineBreakdown(scope), expansionVelocity(scope), newMarkets(id, 12), projectsWhere(sql`${projectLive(sql)} and p.operator_id = ${id}`, 5), sql`select distinct country_iso2 from facilities where operator_id = ${id} and merged_into is null and country_iso2 is not null order by 1`, ]); if (!summary) throw notFound(`operator ${id}`); return { ...summary, pipeline, velocity, aiCount: summary.aiCount ?? 0, newMarkets12m: markets, recentProjects, countriesList: countries.map((c) => reqStr(c.country_iso2)), coverage: summary.mwCoverage ?? 0 }; })); return { operators, generatedAt: new Date().toISOString() }; }