Files
codegraph/__tests__/fixtures/factory-closure-ts/src/services/metric-service.ts
T
Colby McHenry d49265043c test(explore): add the factory-closure fixture and its selection probe (CG-27)
A file whose top-level symbol spans almost all of it — createFoo() returning
an object of closures — is how Svelte 5 rune stores, React custom-hook modules,
IIFE module-pattern JS and Zustand's create((set,get)=>({…})) are all written.
probe-factory-closure.mjs measures what such a file DELIVERS from within: which
inner symbols' definitions reach the agent, not how many bytes did.
2026-08-06 13:58:36 -05:00

102 lines
3.4 KiB
TypeScript

import type { FilterSpec, MetricSample, Widget } from '../stores/types';
import { bucketByHour, meanOf, rateOfChange } from '../lib/metrics';
/**
* Stateless metric helpers — the server-shaped half of the same domain. These
* are ordinary top-level functions, not closures, so they are the control the
* factory-closure file is measured against.
*/
const STALE_AFTER_MS = 15 * 60 * 1000;
/** Refresh a cached metric map in place, returning the widgets that changed. */
export function refreshMetricCache(
cache: Map<string, MetricSample[]>,
incoming: readonly MetricSample[],
now: number,
): string[] {
const touched = new Set<string>();
for (const sample of incoming) {
if (typeof sample.value !== 'number' || Number.isNaN(sample.value)) continue;
const bucket = cache.get(sample.widgetId);
if (bucket) bucket.push(sample);
else cache.set(sample.widgetId, [sample]);
touched.add(sample.widgetId);
}
for (const [widgetId, bucket] of cache) {
const fresh = bucket.filter((s) => now - s.at <= STALE_AFTER_MS);
if (fresh.length !== bucket.length) {
cache.set(widgetId, fresh);
touched.add(widgetId);
}
}
return [...touched].sort();
}
/** Apply a filter spec set to raw samples rather than to widgets. */
export function filterMetrics(
samples: readonly MetricSample[],
specs: readonly FilterSpec[],
): MetricSample[] {
if (specs.length === 0) return samples.slice();
return samples.filter((sample) => specs.every((spec) => {
const field = spec.field === 'unit'
? sample.unit
: spec.field === 'widget'
? sample.widgetId
: String(sample.value);
switch (spec.op) {
case 'eq': return field === spec.value;
case 'contains': return field.includes(spec.value);
case 'gt': return Number(field) > Number(spec.value);
case 'lt': return Number(field) < Number(spec.value);
default: return false;
}
}));
}
/** Per-widget rollup used by the server-rendered summary card. */
export function rollupByWidget(
samples: readonly MetricSample[],
widgets: readonly Widget[],
): Array<{ widgetId: string; title: string; mean: number; slope: number; hours: number }> {
const titles = new Map(widgets.map((w) => [w.id, w.title]));
const grouped = new Map<string, MetricSample[]>();
for (const sample of samples) {
const bucket = grouped.get(sample.widgetId);
if (bucket) bucket.push(sample);
else grouped.set(sample.widgetId, [sample]);
}
const out: Array<{ widgetId: string; title: string; mean: number; slope: number; hours: number }> = [];
for (const [widgetId, bucket] of grouped) {
out.push({
widgetId,
title: titles.get(widgetId) ?? '(unknown)',
mean: meanOf(bucket),
slope: rateOfChange(bucket),
hours: bucketByHour(bucket).size,
});
}
out.sort((a, b) => b.mean - a.mean);
return out;
}
/** Which widgets have not reported inside the staleness window. */
export function staleWidgets(
samples: readonly MetricSample[],
widgets: readonly Widget[],
now: number,
): string[] {
const newest = new Map<string, number>();
for (const sample of samples) {
const seen = newest.get(sample.widgetId) ?? 0;
if (sample.at > seen) newest.set(sample.widgetId, sample.at);
}
return widgets
.filter((w) => !w.hidden)
.filter((w) => now - (newest.get(w.id) ?? 0) > STALE_AFTER_MS)
.map((w) => w.id)
.sort();
}