SPB Git forge
38commits 1branches 0releases
338.7 MBsize
maindefault branch
5 h agolast push
HTML 53.9% TypeScript 44.5% JavaScript 0.6% SQL 0.5%
10.3 KB · 141 lines typescript
Raw Blame History
1/**2 * Admin data-quality reads: QualityOverview (largest values, review queue, counters) and the quality_flags list.3 * Every "largest" query is a plain ORDER BY on published figures — the point is to surface outliers for review.4 */5import type { QualityFlagDTO, QualityOverview } from "@dci/core";6import { pg, facilityView, pipelineMwAgg, projectLive, andAll, page, PLANNED_SET, CONSTRUCTION_SET, type Fragment } from "../../lib/sql.js";7import { int, iso, num, reqStr, str, type Row } from "../../lib/rows.js";8import { qualityFlagDto } from "../../lib/dto.js";9import { entityKey, resolveEntityRefs } from "../../lib/resolve.js";1011const CODE_LABELS: Record<string, string> = {12  mw_single_site_gt_1000: "Single site > 1 000 MW",13  mw_building_gt_500: "One building > 500 MW",14  mw_market_statistic: "MW figure is a market statistic",15  mw_change_5x: "MW figure changed > 5×",16  mw_money_collision: "MW / money figure collision",17  mw_semantics_default: "MW semantics defaulted",18  mw_utility_not_it: "Utility / grid MW (not IT load)",19  mw_project_gt_2000: "Project > 2 000 MW",20  mw_density_implausible: "Implausible power density",21  mw_invalid: "Invalid MW figure",22  inv_single_site_gt_50b: "Single site investment > $50B",23  inv_gt_500b: "Investment > $500B (industry statistic)",24  inv_change_5x: "Investment changed > 5×",25  inv_money_mw_collision: "Investment / MW figure collision",26  inv_invalid: "Invalid investment figure",27  project_false_positive_candidate: "Project false-positive candidate",28  project_title_like_name: "Project named after a headline",29  project_no_location: "Project without a location",30  project_unknown_scope: "Project figure with unknown scope",31  facility_no_country: "Facility without a country",32  operator_bad_slug: "Operator with a broken slug",33  claim_rejected_backing_value: "Rejected claim was backing the displayed value",34};3536export function flagLabel(code: string): string {37  if (CODE_LABELS[code]) return CODE_LABELS[code]!;38  if (code.startsWith("scope_")) return `Figure scope: ${code.slice(6).replace(/_/g, " ")}`;39  if (code.startsWith("inv_scope_")) return `Investment scope: ${code.slice(10).replace(/_/g, " ")}`;40  if (code.startsWith("inv_")) return `Investment: ${code.slice(4).replace(/_/g, " ")}`;41  if (code.startsWith("duplicate")) return "Possible duplicate";42  return code.replace(/_/g, " ").replace(/^\w/, (c) => c.toUpperCase());43}4445function named(r: Row): { id: string; slug: string; name: string } {46  return { id: reqStr(r.id), slug: reqStr(r.slug), name: reqStr(r.name) };47}4849/** Attach {slug,name} refs to quality flag rows (one lookup per entity type). */50export async function flagsWithRefs(rows: Row[]): Promise<QualityFlagDTO[]> {51  const refs = await resolveEntityRefs(rows.map((r) => ({ type: reqStr(r.entity_type), id: str(r.entity_id) })));52  return rows.map((r) => qualityFlagDto(r, refs.get(entityKey(r.entity_type, r.entity_id)) ?? null));53}5455export async function qualityOverview(): Promise<QualityOverview> {56  const sql = pg();57  const live = projectLive(sql);58  const [counts, sev, codes, largeOps, largeCon, largePrj, largeInv, opPipe, metPipe, queue, alerts] = await Promise.all([59    sql<Row[]>`select60        (select count(*) from quality_flags where status = 'open')::int as open_flags,61        (select count(*) from entity_matches where status = 'pending')::int + (select count(*) from quality_flags where status = 'open' and code like 'duplicate%')::int as possible_duplicates,62        (select count(*) from quality_flags where status = 'open' and code like 'mw\\_%')::int as suspicious_mw,63        (select count(*) from quality_flags where status = 'open' and code like 'inv\\_%')::int as suspicious_inv,64        (select count(*) from quality_flags where status = 'open' and code = 'project_false_positive_candidate')::int as project_fp,65        (select count(*) from claims where status = 'unscoped')::int as unscoped_claims,66        (select count(*) from claims where status = 'review')::int as claims_review,67        (select count(*) from projects p where ${live} and p.planned_mw >= 500 and (coalesce(p.evidence_level, 'none') <> 'strong' or p.confidence in ('moderate', 'estimated', 'unverified')))::int as unverified_large,68        (select count(*) from facilities where merged_into is null and country_iso2 is null)::int as fac_no_country,69        (select count(*) from projects p where ${live} and p.facility_id is null)::int as prj_no_facility,70        (select count(*) from operators o where not exists (select 1 from facilities f where (f.operator_id = o.id or f.owner_id = o.id) and f.merged_into is null)71            and not exists (select 1 from projects p where p.operator_id = o.id and ${live})72            and not exists (select 1 from cloud_regions r where r.provider_id = o.id)73            and not exists (select 1 from facility_tenants t where t.operator_id = o.id))::int as orphan_operators`,74    sql<Row[]>`select severity, count(*)::int as n from quality_flags where status = 'open' group by 1`,75    sql<Row[]>`select code, count(*)::int as n from quality_flags where status = 'open' group by 1 order by n desc`,76    sql<Row[]>`select id, slug, name, coalesce(it_capacity_mw, total_power_mw) as mw, capacity_scope as scope, capacity_semantics as semantics from facilities where merged_into is null and status in ('operational', 'partially_operational', 'expansion') and coalesce(it_capacity_mw, total_power_mw) is not null order by 4 desc limit 15`,77    sql<Row[]>`select id, slug, name, coalesce(planned_power_mw, it_capacity_mw, total_power_mw) as mw from facilities where merged_into is null and status = 'under_construction' and coalesce(planned_power_mw, it_capacity_mw, total_power_mw) is not null order by 4 desc limit 15`,78    sql<Row[]>`select p.id, p.slug, p.name, p.planned_mw as mw, p.capacity_scope as scope from projects p where ${live} and p.planned_mw is not null order by p.planned_mw desc limit 15`,79    sql<Row[]>`select p.id, p.slug, p.name, p.investment_usd as inv, p.investment_scope as scope from projects p where ${live} and p.investment_usd is not null order by p.investment_usd desc limit 15`,80    sql<Row[]>`select o.id, o.slug, o.name,81        coalesce((select sum(${pipelineMwAgg(sql)}) from ${facilityView(sql)} f where f.operator_id = o.id and f.status = any(${[...PLANNED_SET, ...CONSTRUCTION_SET]})), 0)82        + coalesce((select sum(p.planned_mw) from projects p where p.operator_id = o.id and ${live} and p.status = any(${[...PLANNED_SET, ...CONSTRUCTION_SET]})), 0) as mw83      from operators o order by mw desc nulls last limit 15`,84    sql<Row[]>`select m.id, m.slug, m.name,85        coalesce((select sum(${pipelineMwAgg(sql)}) from ${facilityView(sql)} f where f.metro_id = m.id and f.status = any(${[...PLANNED_SET, ...CONSTRUCTION_SET]})), 0)86        + coalesce((select sum(p.planned_mw) from projects p where p.metro_id = m.id and ${live} and p.status = any(${[...PLANNED_SET, ...CONSTRUCTION_SET]})), 0) as mw87      from metros m order by mw desc nulls last limit 15`,88    sql<Row[]>`select * from quality_flags where status = 'open' order by priority desc, created_at desc limit 50`,89    sql<Row[]>`select id, level, component, message, created_at from system_alerts where resolved_at is null order by created_at desc limit 20`,90  ]);91  const c = counts[0] ?? {};92  const bySeverity: Record<string, number> = {};93  for (const r of sev) bySeverity[reqStr(r.severity)] = int(r.n);94  return {95    openFlags: int(c.open_flags),96    bySeverity,97    byCode: codes.map((r) => ({ code: reqStr(r.code), count: int(r.n), label: flagLabel(reqStr(r.code)) })),98    possibleDuplicates: int(c.possible_duplicates),99    suspiciousMw: int(c.suspicious_mw),100    suspiciousInvestment: int(c.suspicious_inv),101    projectFalsePositiveCandidates: int(c.project_fp),102    unscopedClaims: int(c.unscoped_claims),103    claimsInReview: int(c.claims_review),104    unverifiedLargeProjects: int(c.unverified_large),105    facilitiesMissingCountry: int(c.fac_no_country),106    projectsWithoutFacility: int(c.prj_no_facility),107    orphanOperators: int(c.orphan_operators),108    largest: {109      operationalFacilityMw: largeOps.map((r) => ({ ...named(r), mw: num(r.mw) ?? 0, scope: str(r.scope), semantics: str(r.semantics) })),110      constructionFacilityMw: largeCon.map((r) => ({ ...named(r), mw: num(r.mw) ?? 0 })),111      projectMw: largePrj.map((r) => ({ ...named(r), mw: num(r.mw) ?? 0, scope: str(r.scope) })),112      investment: largeInv.map((r) => ({ ...named(r), investmentUsd: num(r.inv) ?? 0, scope: str(r.scope) })),113      operatorPipelineMw: opPipe.filter((r) => (num(r.mw) ?? 0) > 0).map((r) => ({ ...named(r), mw: Math.round((num(r.mw) ?? 0) * 100) / 100 })),114      metroPipelineMw: metPipe.filter((r) => (num(r.mw) ?? 0) > 0).map((r) => ({ ...named(r), mw: Math.round((num(r.mw) ?? 0) * 100) / 100 })),115    },116    reviewQueue: await flagsWithRefs(queue),117    alerts: alerts.map((a) => ({ id: reqStr(a.id), level: reqStr(a.level), component: reqStr(a.component), message: reqStr(a.message), createdAt: iso(a.created_at) ?? "" })),118  };119}120121export interface FlagFilters { status?: string; code?: string; entityType?: string; minPriority?: number; page?: number; perPage?: number }122123export async function listFlags(f: FlagFilters): Promise<{ items: QualityFlagDTO[]; total: number; page: number; perPage: number }> {124  const sql = pg();125  const pg_ = page(f.page, f.perPage, 200, 50);126  const c: Fragment[] = [];127  if (f.status && f.status !== "all") c.push(sql`q.status = ${f.status}`);128  if (f.code) c.push(sql`q.code = ${f.code}`);129  if (f.entityType) c.push(sql`q.entity_type = ${f.entityType}`);130  if (f.minPriority != null) c.push(sql`q.priority >= ${f.minPriority}`);131  const rows = await sql<Row[]>`select q.*, count(*) over() as total from quality_flags q where ${andAll(sql, c)} order by q.priority desc, q.created_at desc limit ${pg_.perPage} offset ${pg_.offset}`;132  return { items: await flagsWithRefs(rows), total: rows.length ? int(rows[0]!.total) : 0, page: pg_.page, perPage: pg_.perPage };133}134135export async function setFlagStatus(id: string, status: "resolved" | "dismissed", resolution: string | null): Promise<QualityFlagDTO | null> {136  const sql = pg();137  const rows = await sql<Row[]>`update quality_flags set status = ${status}, resolution = ${resolution}, resolved_by = 'admin', resolved_at = now(), updated_at = now() where id = ${id} returning *`;138  if (!rows[0]) return null;139  return (await flagsWithRefs(rows))[0] ?? null;140}141