web/src/lib/comparison.ts 2.3 K raw
1
import type { DigraphAggregation, ComparisonResult, PerDigraphComparison } from './types';
2
3
const METRIC_KEYS = [
4
  'holdTime1',
5
  'holdTime2',
6
  'pressPress',
7
  'releaseRelease',
8
  'pressRelease',
9
  'releasePress',
10
] as const;
11
12
const MAX_METRIC_DISTANCE = 3.0;
13
const MIN_STD_FALLBACK = 15.0;
14
15
export function compareSession(
16
  sessionAggs: DigraphAggregation[],
17
  profileAggs: DigraphAggregation[],
18
): ComparisonResult {
19
  const profileMap = new Map<string, DigraphAggregation>();
20
  for (const a of profileAggs) {
21
    profileMap.set(a.normalizedKeys, a);
22
  }
23
24
  const perDigraph: PerDigraphComparison[] = [];
25
  let totalDistance = 0;
26
  let metricCount = 0;
27
28
  for (const sessionAgg of sessionAggs) {
29
    const profileAgg = profileMap.get(sessionAgg.normalizedKeys);
30
    if (!profileAgg) continue;
31
32
    let digraphDistance = 0;
33
    let digraphMetrics = 0;
34
35
    for (const key of METRIC_KEYS) {
36
      const sessionMean = sessionAgg[key].mean;
37
      const profileMean = profileAgg[key].mean;
38
      const profileStd = profileAgg[key].std;
39
40
      const divisor = Math.max(profileStd, MIN_STD_FALLBACK);
41
      const distance = Math.min(
42
        Math.abs(sessionMean - profileMean) / divisor,
43
        MAX_METRIC_DISTANCE,
44
      );
45
46
      digraphDistance += distance;
47
      digraphMetrics++;
48
    }
49
50
    const avgDistance = digraphMetrics > 0 ? digraphDistance / digraphMetrics : 0;
51
    const matchPercent = Math.max(0, Math.round(100 * (1 - avgDistance / 3.0)));
52
53
    perDigraph.push({
54
      normalizedKeys: sessionAgg.normalizedKeys,
55
      distance: Math.round(avgDistance * 100) / 100,
56
      matchPercent,
57
    });
58
59
    totalDistance += digraphDistance;
60
    metricCount += digraphMetrics;
61
  }
62
63
  const sharedCount = perDigraph.length;
64
  const overallDistance = metricCount > 0
65
    ? Math.round((totalDistance / metricCount) * 100) / 100
66
    : 0;
67
  const similarityPercent = Math.max(0, Math.round(100 * (1 - overallDistance / 3.0)));
68
69
  let confidence: ComparisonResult['confidence'];
70
  if (sharedCount >= 15) confidence = 'high';
71
  else if (sharedCount >= 10) confidence = 'medium';
72
  else if (sharedCount >= 5) confidence = 'low';
73
  else confidence = 'insufficient';
74
75
  // Sort by distance descending (worst matches first)
76
  perDigraph.sort((a, b) => b.distance - a.distance);
77
78
  return {
79
    overallDistance,
80
    similarityPercent,
81
    confidence,
82
    sharedCount,
83
    perDigraph,
84
  };
85
}