UNPKG

studiocms

Version:

Astro Native CMS for AstroDB. Built from the ground up by the Astro community.

90 lines (76 loc) 3.21 kB
import type { WebVitalsRating } from './schemas.js'; import type { WebVitalsResponseItem, WebVitalsSummary } from './types.js'; /** * Processes an array of web vitals response items and generates a summary. * * @param data - An array of `WebVitalsResponseItem` objects to be processed. * @returns A `WebVitalsSummary` object containing the processed summary. * * The function performs the following steps: * 1. Groups the input data by the `name` property. * 2. Processes each metric group separately. * 3. Sorts the metrics within each group by `rating` and `value`. * 4. Assigns quartiles to each metric. * 5. Identifies the end of each rating group. * 6. Computes the sample size for each metric group. * 7. Computes histogram densities for each rating. * 8. Computes the 75th percentile (P75) for each metric group. * 9. Filters the final results based on specific conditions. */ export function processWebVitalsSummary(data: WebVitalsResponseItem[]): WebVitalsSummary { // Step 1: Group by `name` const grouped: Record<string, WebVitalsResponseItem[]> = data.reduce( (acc, item) => { if (!acc[item.name]) acc[item.name] = []; acc[item.name].push(item); return acc; }, {} as Record<string, WebVitalsResponseItem[]> ); const summary: WebVitalsSummary = {}; // Step 2: Process each metric separately for (const [metricName, metrics] of Object.entries(grouped)) { if (metrics.length < 4) continue; // Ensure sample_size >= 4 // Step 3: Sort within the group metrics.sort((a, b) => a.rating.localeCompare(b.rating) || a.value - b.value); // Step 4: Assign quartiles (NTILE(4) logic) const quartileSize = Math.ceil(metrics.length / 4); for (let i = 0; i < metrics.length; i++) { metrics[i].quartile = Math.floor(i / quartileSize) + 1; // 1-based quartile index } // Step 5: Identify `rating_end` for (let i = 0; i < metrics.length; i++) { const nextItem = metrics[i + 1]; metrics[i].rating_end = !nextItem || nextItem.rating !== metrics[i].rating; } // Step 6: Compute sample size const sampleSize = metrics.length; // Step 7: Compute histogram densities const histogram: Record<WebVitalsRating, number> = { good: 0, 'needs-improvement': 0, poor: 0, }; for (let i = 0; i < metrics.length; i++) { if (metrics[i].rating_end) { const ratingCount = metrics.filter((m) => m.rating === metrics[i].rating).length; histogram[metrics[i].rating] = ratingCount / sampleSize; } } // Step 8: Compute percentiles (P75) const p75Index = Math.floor(metrics.length * 0.75); const p75Metric = metrics[p75Index] || null; const percentiles: Partial< Record<'p75', { value: number; rating: WebVitalsRating }> | undefined > = p75Metric ? { p75: { value: p75Metric.value, rating: p75Metric.rating } } : {}; // Step 9: Filter the final results based on SQL conditions const finalMetrics = metrics.filter( // biome-ignore lint/style/noNonNullAssertion: This is a valid use case for non-null assertion (metric) => metric.rating_end || metric.quartile! * 25 === 75 ); if (finalMetrics.length > 0) { summary[metricName] = { histogram, percentiles, sampleSize }; } } return summary; }