SPB Git forge
38commits 1branches 0releases
338.7 MBsize
maindefault branch
6 h agolast push
HTML 53.9% TypeScript 44.5% JavaScript 0.6% SQL 0.5%
3.6 KB · 48 lines typescript
Raw Blame History
1/** Admin claim reads / status changes. Changing a claim's status never rewrites an entity column: the worker's next reconciliation does. */2import type { ClaimDTO } from "@dci/core";3import { CAPACITY_COLUMN } from "@dci/core";4import { pg, claimCols, andAll, page, type Fragment } from "../../lib/sql.js";5import { int, num, reqStr, str, type Row } from "../../lib/rows.js";6import { claimDto } from "../../lib/dto.js";78export interface ClaimFilters { subjectType?: string; subjectId?: string; status?: string; predicate?: string; page?: number; perPage?: number }910export async function listClaims(f: ClaimFilters): Promise<{ items: ClaimDTO[]; total: number; page: number; perPage: number }> {11  const sql = pg();12  const pg_ = page(f.page, f.perPage, 500, 50);13  const c: Fragment[] = [];14  if (f.subjectType) c.push(sql`k.subject_type = ${f.subjectType}`);15  if (f.subjectId) c.push(sql`k.subject_id = ${f.subjectId}`);16  if (f.status && f.status !== "all") c.push(sql`k.status = ${f.status}`);17  if (f.predicate) c.push(sql`k.predicate = ${f.predicate}`);18  const rows = await sql<Row[]>`select ${claimCols(sql)}, count(*) over() as total from claims k left join sources s on s.id = k.source_id where ${andAll(sql, c)} order by k.last_observed desc, k.id limit ${pg_.perPage} offset ${pg_.offset}`;19  return { items: rows.map((r) => claimDto(r, false)), total: rows.length ? int(rows[0]!.total) : 0, page: pg_.page, perPage: pg_.perPage };20}2122const FACILITY_COL: Record<string, string> = { itCapacityMw: "it_capacity_mw", totalPowerMw: "total_power_mw", plannedPowerMw: "planned_power_mw", utilityCapacityMw: "utility_capacity_mw", gridConnectionMw: "grid_connection_mw", ultimateCampusMw: "ultimate_campus_mw" };2324/** Set a claim status; when a rejected claim was backing a displayed facility MW column, a quality flag is raised (column untouched). */25export async function setClaimStatus(id: string, status: "current" | "rejected" | "review", reason: string | null): Promise<{ claim: ClaimDTO; flagged: boolean } | null> {26  const sql = pg();27  const rows = await sql<Row[]>`update claims set status = ${status}, rejection_reason = ${status === "rejected" ? reason : null} where id = ${id} returning id`;28  if (!rows[0]) return null;29  const full = (await sql<Row[]>`select ${claimCols(sql)} from claims k left join sources s on s.id = k.source_id where k.id = ${id}`)[0]!;30  let flagged = false;31  if (status === "rejected" && (str(full.subject_type) === "facility" || str(full.subject_type) === "campus")) {32    const camel = (CAPACITY_COLUMN as Record<string, string | null>)[reqStr(full.predicate)] ?? null;33    const col = camel ? FACILITY_COL[camel] : null;34    const v = num(full.value);35    if (col && v != null) {36      const fac = (await sql<Row[]>`select ${sql(col)} as v, name from facilities where id = ${reqStr(full.subject_id)}`)[0];37      const cur = num(fac?.v);38      if (fac && cur != null && Math.abs(cur - v) < 1e-9) {39        await sql`insert into quality_flags (id, entity_type, entity_id, claim_id, code, severity, field, message, details, priority, status, dedupe_key)40          values (${"flg_" + id.replace(/^clm_/, "").slice(0, 24)}, ${reqStr(full.subject_type)}, ${reqStr(full.subject_id)}, ${id}, 'claim_rejected_backing_value', 'warn', ${camel}, ${`rejected claim (${reqStr(full.predicate)} = ${v}) is still the displayed ${camel} value — re-reconcile`}, ${JSON.stringify({ claimId: id, value: v, reason })}::jsonb, 60, 'open', ${"claim_rejected:" + id})41          on conflict (dedupe_key) do update set status = 'open', message = excluded.message, details = excluded.details, updated_at = now()`;42        flagged = true;43      }44    }45  }46  return { claim: claimDto(full, false), flagged };47}48