/** Facility tenants: carriers / cloud providers present at a facility → facility_tenants + operator flags. */ import { sql } from "@dci/db"; import type { IngestContext, Tx } from "./common.js"; import { resolveOperator } from "./operators.js"; export interface TenantInput { names: string[]; role: "carrier" | "cloud" | "network" | "tenant"; } /** Attach tenants; returns the distinct operator count attached for this role. */ export async function attachTenants(tx: Tx, ctx: IngestContext, facilityId: string, input: TenantInput): Promise { const seen = new Set(); for (const raw of input.names) { const name = raw?.trim(); if (!name || name.length < 2 || name.length > 120) continue; try { const asnMatch = name.match(/\bAS(\d{2,7})\b/i); const clean = name.replace(/\s*\(?AS\d{2,7}\)?\s*/i, "").trim() || name; const op = await resolveOperator(tx, ctx, { name: clean, roleHint: input.role === "cloud" ? "cloud" : input.role === "carrier" ? "carrier" : null }); if (seen.has(op.id)) continue; seen.add(op.id); await tx.execute(sql`insert into facility_tenants (facility_id, operator_id, role, asn, source_id) values (${facilityId}, ${op.id}, ${input.role}, ${asnMatch ? Number(asnMatch[1]) : null}, ${ctx.run.sourceId}) on conflict (facility_id, operator_id, role) do update set asn = coalesce(excluded.asn, facility_tenants.asn), source_id = excluded.source_id`); } catch (e) { console.warn(`[ingest] tenant "${name}" skipped: ${(e as Error).message}`); } } return seen.size; } /** Recompute carriers_count / networks_count from facility_tenants. */ export async function refreshTenantCounts(tx: Tx, facilityId: string): Promise { await tx.execute(sql`update facilities f set carriers_count = (select count(distinct operator_id) from facility_tenants t where t.facility_id = f.id and t.role = 'carrier'), networks_count = (select count(distinct operator_id) from facility_tenants t where t.facility_id = f.id and t.role in ('carrier','network','cloud','tenant')) where f.id = ${facilityId}`); }