@@ -22,6 +22,60 @@ function toNumber(value: unknown): number {
2222 return Number . isFinite ( numberValue ) ? numberValue : 0 ;
2323}
2424
25+ function normalizeMetricKey ( key : string ) : string {
26+ return key
27+ . trim ( )
28+ . toLowerCase ( )
29+ . replace ( / [ \s - ] + / g, "_" ) ;
30+ }
31+
32+ function getMetricValueForUnit (
33+ unit : string | undefined ,
34+ metrics : Record < string , unknown >
35+ ) : number | undefined {
36+ if ( ! unit ) {
37+ return undefined ;
38+ }
39+
40+ // Fast path: exact key as configured.
41+ if ( metrics [ unit ] !== undefined ) {
42+ return toNumber ( metrics [ unit ] ) ;
43+ }
44+
45+ const normalizedUnit = normalizeMetricKey ( unit ) ;
46+
47+ // Common canonical aliases used across ingestion paths.
48+ const canonicalAliases : Record < string , string [ ] > = {
49+ miles : [ "distance_miles" , "mile" , "distance_mile" ] ,
50+ kilometers : [ "distance_km" , "distance_kilometers" , "km" , "kilometres" , "kilometer" , "kilometre" ] ,
51+ minutes : [ "duration_minutes" , "moving_minutes" , "minute" ] ,
52+ count : [ "counts" , "instances" , "instance" ] ,
53+ completion : [ "completed" , "is_completed" ] ,
54+ full_days : [ "full_day" ] ,
55+ half_days : [ "half_day" ] ,
56+ } ;
57+
58+ const candidates = new Set < string > ( [ normalizedUnit ] ) ;
59+ const singular = normalizedUnit . endsWith ( "s" ) ? normalizedUnit . slice ( 0 , - 1 ) : normalizedUnit ;
60+ const plural = normalizedUnit . endsWith ( "s" ) ? normalizedUnit : `${ normalizedUnit } s` ;
61+ candidates . add ( singular ) ;
62+ candidates . add ( plural ) ;
63+
64+ const aliasKeys = canonicalAliases [ normalizedUnit ] ?? canonicalAliases [ singular ] ?? [ ] ;
65+ for ( const alias of aliasKeys ) {
66+ candidates . add ( alias ) ;
67+ }
68+
69+ for ( const [ key , value ] of Object . entries ( metrics ) ) {
70+ const normalizedKey = normalizeMetricKey ( key ) ;
71+ if ( candidates . has ( normalizedKey ) && value !== undefined ) {
72+ return toNumber ( value ) ;
73+ }
74+ }
75+
76+ return undefined ;
77+ }
78+
2579function getScoringConfig ( activityType : Doc < "activityTypes" > ) : Record < string , unknown > {
2680 return ( activityType . scoringConfig as Record < string , unknown > ) ?? { } ;
2781}
@@ -36,11 +90,11 @@ async function calculateDefaultPoints(
3690 const config = getScoringConfig ( activityType ) ;
3791 const { unit, pointsPerUnit = 1 , basePoints = 0 } = config ;
3892
39- if ( ! unit || ! context . metrics [ unit as string ] ) {
93+ const unitValue = getMetricValueForUnit ( unit as string | undefined , context . metrics ) ;
94+ if ( unitValue === undefined ) {
4095 return toNumber ( basePoints ) ;
4196 }
4297
43- const unitValue = toNumber ( context . metrics [ unit as string ] ) ;
4498 return toNumber ( basePoints ) + unitValue * toNumber ( pointsPerUnit ) ;
4599}
46100
@@ -210,11 +264,11 @@ function calculateVariantPoints(
210264 const { unit, pointsPerUnit = 1 , basePoints = 0 } = variantConfig ;
211265 const scoringUnit = unit || mainConfig [ "unit" ] ;
212266
213- if ( ! scoringUnit || ! metrics [ scoringUnit as string ] ) {
267+ const unitValue = getMetricValueForUnit ( scoringUnit as string | undefined , metrics ) ;
268+ if ( unitValue === undefined ) {
214269 return toNumber ( basePoints ) ;
215270 }
216271
217- const unitValue = toNumber ( metrics [ scoringUnit as string ] ) ;
218272 return toNumber ( basePoints ) + unitValue * toNumber ( pointsPerUnit ) ;
219273}
220274
@@ -312,18 +366,19 @@ function calculateUnitBasedPoints(
312366 const maxUnits = config [ "maxUnits" ] as number | undefined ;
313367 const basePoints = toNumber ( config [ "basePoints" ] ?? 0 ) ;
314368
315- if ( ! unit || ! context . metrics [ unit ] ) {
369+ const unitValue = getMetricValueForUnit ( unit , context . metrics ) ;
370+ if ( unitValue === undefined ) {
316371 return basePoints ;
317372 }
318373
319- let unitValue = toNumber ( context . metrics [ unit ] ) ;
374+ let boundedUnitValue = unitValue ;
320375
321376 // Apply cap if maxUnits is defined
322- if ( maxUnits !== undefined && unitValue > maxUnits ) {
323- unitValue = maxUnits ;
377+ if ( maxUnits !== undefined && boundedUnitValue > maxUnits ) {
378+ boundedUnitValue = maxUnits ;
324379 }
325380
326- return basePoints + unitValue * pointsPerUnit ;
381+ return basePoints + boundedUnitValue * pointsPerUnit ;
327382}
328383
329384/**
0 commit comments