Files
healer-man/src/app/balanceLabClient.ts
T
2026-09-16 20:00:13 -04:00

208 lines
6.4 KiB
TypeScript

import { onlineApiOrigin, requestOnlineJson } from "./onlineAccountClient";
export type BalanceLabPartySource = "wow" | "coa" | "rom" | "all";
export type BalanceLabBossCategory = "all" | "wow" | "rom";
export type BalanceLabJobStatus = "queued" | "running" | "complete" | "failed";
export interface BalanceLabRuntimeStatus {
readonly available: boolean;
readonly executable: string;
readonly version: string | null;
readonly error: string | null;
}
export interface BalanceLabJobSpec {
readonly source: BalanceLabPartySource;
readonly level: number;
readonly partyConfigurations: number;
readonly simulationsPerParty: number;
readonly durationSeconds: number;
readonly partySeed: number;
readonly bossCategory: BalanceLabBossCategory;
readonly bossKeys: readonly string[];
}
export interface BalanceLabJob {
readonly id: string;
readonly status: BalanceLabJobStatus;
readonly createdAt: string;
readonly startedAt: string | null;
readonly completedAt: string | null;
readonly requestedBy: string;
readonly spec: BalanceLabJobSpec;
readonly progress: {
readonly party: number;
readonly partiesCompleted: number;
readonly partiesTotal: number;
readonly simulationsCompleted: number;
readonly simulationsTotal: number;
};
readonly artifacts: readonly string[];
readonly error: string | null;
readonly logTail: readonly string[];
readonly summary: BalanceLabReportSummary | null;
}
export interface BalanceLabReportSummary {
readonly partyConfigurations: number;
readonly bosses: number;
readonly runs: number;
readonly classSpecs: number;
readonly classAbilities: number;
readonly bossPressureMedianDtps: number;
}
export interface BalanceLabBossResult {
readonly bossKey: string;
readonly bossName: string;
readonly category: string;
readonly dungeonId: string;
readonly dungeonTitle: string;
readonly runs: number;
readonly incomingDtps: number;
readonly averageIncomingDamage: number;
readonly partyDps: number;
readonly bossDpsTaken: number;
readonly defeatRate: number;
readonly wipeRate: number;
readonly averageDeaths: number;
readonly averageTimeToDefeat: number | null;
readonly bossMaxHealth: number;
readonly pressureVsMedian: number | null;
readonly pressureFlag: string;
}
export interface BalanceLabClassSpecResult {
readonly source: string;
readonly buildId: string;
readonly classId: string;
readonly className: string;
readonly specialization: string;
readonly role: string;
readonly partyConfigurations: number;
readonly runs: number;
readonly dps: number;
readonly bossDps: number;
readonly hps: number;
readonly dtps: number;
readonly totalDamage: number;
readonly totalDamageTaken: number;
readonly totalHealing: number;
readonly totalAbsorbs: number;
readonly deathEvents: number;
readonly damageVsSourceRoleMedian: number | null;
readonly damageFlag: string;
}
export interface BalanceLabPartyResult {
readonly partyConfigurationId: string;
readonly source: string;
readonly members: string;
readonly runs: number;
readonly partyDps: number;
readonly incomingDtps: number;
readonly defeatRate: number;
readonly wipeRate: number;
readonly averageDeaths: number;
readonly reportPath: string;
}
export interface BalanceLabClassAbilityResult {
readonly source: string;
readonly buildId: string;
readonly classId: string;
readonly className: string;
readonly specialization: string;
readonly role: string;
readonly abilityId: string;
readonly abilityName: string;
readonly icon: string;
readonly categories: string;
readonly cooldownSeconds: number | null;
readonly partyConfigurations: number;
readonly bosses: number;
readonly runs: number;
readonly availableRuns: number;
readonly activeRuns: number;
readonly usageRate: number;
readonly casts: number;
readonly castsPerMinute: number;
readonly hits: number;
readonly hitsPerRun: number;
readonly rawDamage: number;
readonly effectiveDamage: number;
readonly dps: number;
readonly damageShare: number;
readonly averageDamagePerHit: number;
readonly rawHealing: number;
readonly effectiveHealing: number;
readonly overheal: number;
readonly absorbs: number;
readonly status: "unused" | "damage" | "support" | "used";
readonly unusedAvailable: boolean;
}
export interface BalanceLabSuiteReport {
readonly schemaVersion: number;
readonly generatedAt: string;
readonly engine: string;
readonly level: number;
readonly summary: BalanceLabReportSummary;
readonly bosses: readonly BalanceLabBossResult[];
readonly classSpecs: readonly BalanceLabClassSpecResult[];
readonly classAbilities?: readonly BalanceLabClassAbilityResult[];
readonly partyConfigurations: readonly BalanceLabPartyResult[];
}
export async function listBalanceLabJobs(token: string): Promise<readonly BalanceLabJob[]> {
return (await requestOnlineJson<{ jobs: BalanceLabJob[] }>(
"/api/gm/balance-lab/jobs",
{},
token,
)).jobs;
}
export async function getBalanceLabRuntime(token: string): Promise<BalanceLabRuntimeStatus> {
return (await requestOnlineJson<{ runtime: BalanceLabRuntimeStatus }>(
"/api/gm/balance-lab/runtime",
{},
token,
)).runtime;
}
export async function startBalanceLabJob(spec: BalanceLabJobSpec, token: string): Promise<BalanceLabJob> {
return (await requestOnlineJson<{ job: BalanceLabJob }>(
"/api/gm/balance-lab/jobs",
{ method: "POST", body: JSON.stringify(spec) },
token,
)).job;
}
export async function getBalanceLabJob(id: string, token: string): Promise<BalanceLabJob> {
return (await requestOnlineJson<{ job: BalanceLabJob }>(
`/api/gm/balance-lab/jobs/${encodeURIComponent(id)}`,
{},
token,
)).job;
}
export async function getBalanceLabReport(id: string, token: string): Promise<BalanceLabSuiteReport> {
return requestOnlineJson<BalanceLabSuiteReport>(
`/api/gm/balance-lab/jobs/${encodeURIComponent(id)}/artifacts/suite-report.json`,
{},
token,
);
}
export async function fetchBalanceLabArtifact(id: string, name: string, token: string): Promise<Blob> {
const response = await fetch(
`${onlineApiOrigin()}/api/gm/balance-lab/jobs/${encodeURIComponent(id)}/artifacts/${encodeURIComponent(name)}`,
{ headers: { Authorization: `Bearer ${token}` } },
);
if (!response.ok) {
const body = await response.json().catch(() => ({})) as { error?: string };
throw new Error(body.error ?? `Could not download ${name} (${response.status}).`);
}
return response.blob();
}