export type SessionSwitchSample = { observedAtMs: number destination: string[] source: string[] hasVisibleRows: boolean last: boolean bottomErrorPx?: number review?: { fileHost: boolean fileHostReplaced: boolean header: string replacedLevels: string[] } } export function classifySessionSwitch(samples: SessionSwitchSample[]) { const firstDestination = samples.findIndex((sample) => sample.destination.length > 0) const firstCorrect = samples.findIndex(isCorrectDestination) const stable = samples.findIndex((_, index) => isStableSessionSwitch(samples.slice(index, index + 3))) return { firstDestinationObservedMs: samples[firstDestination]?.observedAtMs ?? null, firstCorrectObservedMs: samples[firstCorrect]?.observedAtMs ?? null, stableObservedMs: samples[stable + 2]?.observedAtMs ?? null, wrongDestinationSamples: samples .slice(firstDestination) .filter((sample) => sample.destination.length > 0 && !sample.last).length, blankSamples: samples.filter((sample) => !sample.hasVisibleRows).length, unknownSamples: samples.filter( (sample) => sample.hasVisibleRows && sample.destination.length === 0 && sample.source.length === 0, ).length, sourceSamples: samples.filter((sample) => sample.source.length > 0).length, reviewFileHostMissingSamples: samples.filter((sample) => sample.review && !sample.review.fileHost).length, reviewFileHostReplacedSamples: samples.filter((sample) => sample.review?.fileHostReplaced).length, reviewHeaders: [...new Set(samples.flatMap((sample) => (sample.review ? [sample.review.header] : [])))], reviewReplacedLevels: [...new Set(samples.flatMap((sample) => sample.review?.replacedLevels ?? []))], } } export function isCorrectDestination(sample: SessionSwitchSample) { return ( sample.destination.length > 0 && sample.source.length === 0 && sample.last && Math.abs(sample.bottomErrorPx ?? Infinity) <= 1 ) } export function isStableSessionSwitch(samples: SessionSwitchSample[]) { return samples.length === 3 && samples.every(isCorrectDestination) } export function isStableDestination(samples: Pick[]) { return ( samples.length === 3 && samples.every((sample) => sample.last && Math.abs(sample.bottomErrorPx ?? Infinity) <= 1) ) }