<commit_before>import { IClientMetricsEnv } from '../types/stores/client-metrics-store-v2';
import { startOfHour } from 'date-fns';

const groupBy = <T>(list: T[], createKey: (item: T) => string): T[][] => {
    const groups = list.reduce((acc, item) => {
        const key = createKey(item);
        acc[key] = acc[key] ?? [];
        acc[key].push(item);
        return acc;
    }, {} as Record<string, T[]>);

    return Object.values(groups);
};

const createMetricKey = (metric: IClientMetricsEnv): string => {
    return [
        metric.featureName,
        metric.appName,
        metric.environment,
        metric.timestamp.getTime(),
    ].join();
};

const sumYesNo = (
    metrics: IClientMetricsEnv[],
): Pick<IClientMetricsEnv, 'yes' | 'no'> => {
    return metrics.reduce(
        (acc, metric) => ({
            yes: acc.yes + metric.yes,
            no: acc.no + metric.no,
        }),
        {
            yes: 0,
            no: 0,
        },
    );
};

export const collapseHourlyMetrics = (
    metrics: IClientMetricsEnv[],
): IClientMetricsEnv[] => {
    const hourlyMetrics = metrics.map((metric) => ({
        ...metric,
        timestamp: startOfHour(metric.timestamp),
    }));

    return groupBy(hourlyMetrics, createMetricKey).flatMap((group) => ({
        ...group[0],
        ...sumYesNo(group),
    }));
};
<commit_msg>refactor: improve performance of collapseHourlyMetrics (#1945)<commit_after>import { IClientMetricsEnv } from '../types/stores/client-metrics-store-v2';
import { startOfHour } from 'date-fns';

const createMetricKey = (metric: IClientMetricsEnv): string => {
    return [
        metric.featureName,
        metric.appName,
        metric.environment,
        metric.timestamp.getTime(),
    ].join();
};

export const collapseHourlyMetrics = (
    metrics: IClientMetricsEnv[],
): IClientMetricsEnv[] => {
    const grouped = new Map<string, IClientMetricsEnv>();
    metrics.forEach((metric) => {
        const hourlyMetric = {
            ...metric,
            timestamp: startOfHour(metric.timestamp),
        };
        const key = createMetricKey(hourlyMetric);
        if (!grouped[key]) {
            grouped[key] = hourlyMetric;
        } else {
            grouped[key].yes = metric.yes + (grouped[key].yes || 0);
            grouped[key].no = metric.no + (grouped[key].no || 0);
        }
    });
    return Object.values(grouped);
};

{'fraction_non_alphanumeric': 0.1591003748438151, 'fraction_numerical': 0.004581424406497293, 'mean_word_length': 2.4610951008645534, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6872313', 'n_tokens_mistral': 736, 'n_tokens_neox': 727, 'n_words': 142}