SPB Git forge
38commits 1branches 0releases
338.7 MBsize
maindefault branch
yesterdaylast push
HTML 53.9% TypeScript 44.5% JavaScript 0.6% SQL 0.5%
2.1 KB · 35 lines typescript
Raw Blame History
1/** Side-by-side operator comparison (2–5 operators): summary, pipeline, velocity, new markets, recent projects. */2import type { OperatorComparison } from "@dci/core";3import { pg, projectLive } from "../lib/sql.js";4import { reqStr, type Row } from "../lib/rows.js";5import { notFound } from "../lib/http.js";6import { expansionVelocity, pipelineBreakdown, scopeFor } from "../lib/pipeline.js";7import { newMarkets, operatorSummaryById } from "./operators.js";8import { projectsWhere } from "./projects.js";910export 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).";1112export async function compareOperators(slugs: string[]): Promise<OperatorComparison> {13  const sql = pg();14  const ids: string[] = [];15  for (const slug of slugs) {16    const r = (await sql<Row[]>`select id from operators where slug = ${slug} or id = ${slug} limit 1`)[0];17    if (!r) throw notFound(`operator ${slug}`);18    ids.push(reqStr(r.id));19  }20  const operators = await Promise.all(ids.map(async (id) => {21    const scope = scopeFor(sql, { operatorId: id });22    const [summary, pipeline, velocity, markets, recentProjects, countries] = await Promise.all([23      operatorSummaryById(id),24      pipelineBreakdown(scope),25      expansionVelocity(scope),26      newMarkets(id, 12),27      projectsWhere(sql`${projectLive(sql)} and p.operator_id = ${id}`, 5),28      sql<Row[]>`select distinct country_iso2 from facilities where operator_id = ${id} and merged_into is null and country_iso2 is not null order by 1`,29    ]);30    if (!summary) throw notFound(`operator ${id}`);31    return { ...summary, pipeline, velocity, aiCount: summary.aiCount ?? 0, newMarkets12m: markets, recentProjects, countriesList: countries.map((c) => reqStr(c.country_iso2)), coverage: summary.mwCoverage ?? 0 };32  }));33  return { operators, generatedAt: new Date().toISOString() };34}35