-
Notifications
You must be signed in to change notification settings - Fork 9.4k
/
scoring.js
91 lines (77 loc) · 2.81 KB
/
scoring.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
/**
* @license
* Copyright 2018 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {Audit} from './audits/audit.js';
/**
* Clamp figure to 2 decimal places
* @param {number} val
* @return {number}
*/
const clampTo2Decimals = val => Math.round(val * 100) / 100;
class ReportScoring {
/**
* Computes the weighted-average of the score of the list of items.
* @param {Array<{score: number|null, weight: number}>} items
* @return {number|null}
*/
static arithmeticMean(items) {
// Filter down to just the items with a weight as they have no effect on score
items = items.filter(item => item.weight > 0);
// If there is 1 null score, return a null average
if (items.some(item => item.score === null)) return null;
const results = items.reduce(
(result, item) => {
const score = item.score;
const weight = item.weight;
return {
weight: result.weight + weight,
sum: result.sum + /** @type {number} */ (score) * weight,
};
},
{weight: 0, sum: 0}
);
return clampTo2Decimals(results.sum / results.weight || 0);
}
/**
* Returns the report JSON object with computed scores.
* @param {Object<string, LH.Config.Category>} configCategories
* @param {Object<string, LH.RawIcu<LH.Audit.Result>>} resultsByAuditId
* @return {Object<string, LH.RawIcu<LH.Result.Category>>}
*/
static scoreAllCategories(configCategories, resultsByAuditId) {
/** @type {Record<string, LH.RawIcu<LH.Result.Category>>} */
const scoredCategories = {};
for (const [categoryId, configCategory] of Object.entries(configCategories)) {
// Copy category audit members
const auditRefs = configCategory.auditRefs.map(configMember => {
const member = {...configMember};
// If a result was not applicable, meaning its checks did not run against anything on
// the page, force it's weight to 0. It will not count during the arithmeticMean() but
// will still be included in the final report json and displayed in the report as
// "Not Applicable".
const result = resultsByAuditId[member.id];
if (result.scoreDisplayMode === Audit.SCORING_MODES.NOT_APPLICABLE ||
result.scoreDisplayMode === Audit.SCORING_MODES.INFORMATIVE ||
result.scoreDisplayMode === Audit.SCORING_MODES.MANUAL) {
member.weight = 0;
}
return member;
});
const scores = auditRefs.map(auditRef => ({
score: resultsByAuditId[auditRef.id].score,
weight: auditRef.weight,
}));
const score = ReportScoring.arithmeticMean(scores);
scoredCategories[categoryId] = {
...configCategory,
auditRefs,
id: categoryId,
score,
};
}
return scoredCategories;
}
}
export {ReportScoring};