mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution] Change risk scoring sum max and simplify risk score calculations (#184638)
## Summary * Update the `RISK_SCORING_SUM_MAX` to the appropriate value based 10.000 alerts (read more on the original issue) * The following risk scoring engine lines can be simplified by no longer multiplying by 100, and instead using the value above directly. I also renamed the constants to improve reliability, I rounded `2.592375848672986` up to `2.5924` so the calculated score won't go above `100`. For `10.000` alerts with a risk score of `100` each the calculated risk score is `99.99906837960884` Risk score calculation for 10_00 alerts with 100 risk score  Risk score calculation for 1_000 alerts with 100 risk score  ### User Impact The entity's calculated risk score will slightly increase because we update the normalisation divisor from 261.2 to 2.5924. ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
This commit is contained in:
parent
22155aefdc
commit
b4561e7c3e
10 changed files with 79 additions and 123 deletions
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { applyCriticalityToScore, normalize } from './helpers';
|
||||
import { applyCriticalityToScore } from './helpers';
|
||||
|
||||
describe('applyCriticalityToScore', () => {
|
||||
describe('integer scores', () => {
|
||||
|
@ -61,42 +61,3 @@ describe('applyCriticalityToScore', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalize', () => {
|
||||
it('returns 0 if the number is equal to the min', () => {
|
||||
const result = normalize({ number: 0, min: 0, max: 100 });
|
||||
expect(result).toEqual(0);
|
||||
});
|
||||
|
||||
it('returns 100 if the number is equal to the max', () => {
|
||||
const result = normalize({ number: 100, min: 0, max: 100 });
|
||||
expect(result).toEqual(100);
|
||||
});
|
||||
|
||||
it('returns 50 if the number is halfway between the min and max', () => {
|
||||
const result = normalize({ number: 50, min: 0, max: 100 });
|
||||
expect(result).toEqual(50);
|
||||
});
|
||||
|
||||
it('defaults to a min of 0', () => {
|
||||
const result = normalize({ number: 50, max: 100 });
|
||||
expect(result).toEqual(50);
|
||||
});
|
||||
|
||||
describe('when the domain is diffrent than the range', () => {
|
||||
it('returns 0 if the number is equal to the min', () => {
|
||||
const result = normalize({ number: 20, min: 20, max: 200 });
|
||||
expect(result).toEqual(0);
|
||||
});
|
||||
|
||||
it('returns 100 if the number is equal to the max', () => {
|
||||
const result = normalize({ number: 40, min: 30, max: 40 });
|
||||
expect(result).toEqual(100);
|
||||
});
|
||||
|
||||
it('returns 50 if the number is halfway between the min and max', () => {
|
||||
const result = normalize({ number: 20, min: 0, max: 40 });
|
||||
expect(result).toEqual(50);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -65,22 +65,3 @@ export const bayesianUpdate = ({
|
|||
const newProbability = priorProbability * modifier;
|
||||
return (max * newProbability) / (1 + newProbability);
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalizes a number to the range [0, 100]
|
||||
*
|
||||
* @param number - The number to be normalized
|
||||
* @param min - The minimum possible value of the number. Defaults to 0.
|
||||
* @param max - The maximum possible value of the number
|
||||
*
|
||||
* @returns The updated score with modifiers applied
|
||||
*/
|
||||
export const normalize = ({
|
||||
number,
|
||||
min = 0,
|
||||
max,
|
||||
}: {
|
||||
number: number;
|
||||
min?: number;
|
||||
max: number;
|
||||
}) => ((number - min) / (max - min)) * 100;
|
||||
|
|
|
@ -30,18 +30,14 @@ import {
|
|||
import { withSecuritySpan } from '../../../utils/with_security_span';
|
||||
import type { AssetCriticalityRecord } from '../../../../common/api/entity_analytics';
|
||||
import type { AssetCriticalityService } from '../asset_criticality/asset_criticality_service';
|
||||
import {
|
||||
applyCriticalityToScore,
|
||||
getCriticalityModifier,
|
||||
normalize,
|
||||
} from '../asset_criticality/helpers';
|
||||
import { applyCriticalityToScore, getCriticalityModifier } from '../asset_criticality/helpers';
|
||||
import { getAfterKeyForIdentifierType, getFieldForIdentifier } from './helpers';
|
||||
import type {
|
||||
CalculateRiskScoreAggregations,
|
||||
CalculateScoresParams,
|
||||
RiskScoreBucket,
|
||||
} from '../types';
|
||||
import { RISK_SCORING_SUM_MAX, RISK_SCORING_SUM_VALUE } from './constants';
|
||||
import { RIEMANN_ZETA_VALUE, RIEMANN_ZETA_S_VALUE } from './constants';
|
||||
import { getPainlessScripts, type PainlessScripts } from './painless';
|
||||
|
||||
const formatForResponse = ({
|
||||
|
@ -82,10 +78,7 @@ const formatForResponse = ({
|
|||
calculated_level: calculatedLevel,
|
||||
calculated_score: riskDetails.value.score,
|
||||
calculated_score_norm: normalizedScoreWithCriticality,
|
||||
category_1_score: normalize({
|
||||
number: riskDetails.value.category_1_score,
|
||||
max: RISK_SCORING_SUM_MAX,
|
||||
}),
|
||||
category_1_score: riskDetails.value.category_1_score / RIEMANN_ZETA_VALUE, // normalize value to be between 0-100
|
||||
category_1_count: riskDetails.value.category_1_count,
|
||||
notes: riskDetails.value.notes,
|
||||
inputs: riskDetails.value.risk_inputs.map((riskInput) => ({
|
||||
|
@ -150,8 +143,8 @@ const buildIdentifierTypeAggregation = ({
|
|||
map_script: scriptedMetricPainless.map,
|
||||
combine_script: scriptedMetricPainless.combine,
|
||||
params: {
|
||||
p: RISK_SCORING_SUM_VALUE,
|
||||
risk_cap: RISK_SCORING_SUM_MAX,
|
||||
p: RIEMANN_ZETA_S_VALUE,
|
||||
risk_cap: RIEMANN_ZETA_VALUE,
|
||||
global_identifier_type_weight: globalIdentifierTypeWeight || 1,
|
||||
},
|
||||
reduce_script: scriptedMetricPainless.reduce,
|
||||
|
|
|
@ -6,15 +6,36 @@
|
|||
*/
|
||||
|
||||
/**
|
||||
* The risk scoring algorithm uses a Riemann zeta function to sum an entity's risk inputs to a known, finite value (@see RISK_SCORING_SUM_MAX). It does so by assigning each input a weight based on its position in the list (ordered by score) of inputs. This value represents the complex variable s of Re(s) in traditional Riemann zeta function notation.
|
||||
* The risk scoring algorithm uses a Riemann zeta function to sum an entity's risk inputs to a known, finite value (@see RIEMANN_ZETA_VALUE).
|
||||
* It does so by assigning each input a weight based on its position in the list (ordered by score) of inputs.
|
||||
* This value represents the complex variable s of Re(s) in traditional Riemann zeta function notation.
|
||||
*
|
||||
* Read more: https://en.wikipedia.org/wiki/Riemann_zeta_function
|
||||
*/
|
||||
export const RISK_SCORING_SUM_VALUE = 1.5;
|
||||
export const RIEMANN_ZETA_S_VALUE = 1.5;
|
||||
|
||||
/**
|
||||
* Represents the maximum possible risk score sum. This value is derived from RISK_SCORING_SUM_VALUE, but we store the precomputed value here to be used more conveniently in normalization.
|
||||
* @see RISK_SCORING_SUM_VALUE
|
||||
* Represents the value calculated by Riemann Zeta function for RIEMANN_ZETA_S_VALUE with 10.000 iterations (inputs) which is the default alertSampleSizePerShard.
|
||||
* The maximum unnormalized risk score value is calculated by multiplying RIEMANN_ZETA_S_VALUE by the maximum alert risk_score (100).
|
||||
*
|
||||
* This value is derived from RIEMANN_ZETA_S_VALUE, but we store the precomputed value here to be used more conveniently in normalization. @see RIEMANN_ZETA_S_VALUE
|
||||
*
|
||||
* The Riemann Zeta value for different number of inputs is:
|
||||
* | 𝑍(s,inputs) |
|
||||
* | :---------------------------------------|
|
||||
* | 𝑍(1.5,10)≈1.9953364933456017 |
|
||||
* | 𝑍(1.5,100)≈2.412874098703719 |
|
||||
* | 𝑍(1.5,1000)≈2.5491456029175756 |
|
||||
* | 𝑍(1.5,10_000)≈2.5923758486729866 |
|
||||
* | 𝑍(1.5,100_000)≈2.6060508091764736 |
|
||||
* | 𝑍(1.5,1_000_000)≈2.6103753491852295 |
|
||||
* | 𝑍(1.5,10_000_000)≈2.611742893169012 |
|
||||
* | 𝑍(1.5,100_000_000)≈2.6121753486854478 |
|
||||
* | 𝑍(1.5,1_000_000_000)≈2.6123121030481857 |
|
||||
*
|
||||
* Read more: https://en.wikipedia.org/wiki/Riemann_zeta_function
|
||||
*/
|
||||
export const RISK_SCORING_SUM_MAX = 261.2;
|
||||
export const RIEMANN_ZETA_VALUE = 2.5924;
|
||||
|
||||
/**
|
||||
* This value represents the maximum possible risk score after normalization.
|
||||
|
|
|
@ -17,7 +17,7 @@ describe('getPainlessScripts', () => {
|
|||
"combine": "return state;",
|
||||
"init": "state.inputs = []",
|
||||
"map": "Map fields = new HashMap();fields.put('id', doc['kibana.alert.uuid'].value);fields.put('index', doc['_index'].value);fields.put('time', doc['@timestamp'].value);fields.put('rule_name', doc['kibana.alert.rule.name'].value);fields.put('category', doc['event.kind'].value);fields.put('score', doc['kibana.alert.risk_score'].value);state.inputs.add(fields); ",
|
||||
"reduce": "Map results = new HashMap();results['notes'] = [];results['category_1_score'] = 0.0;results['category_1_count'] = 0;results['risk_inputs'] = [];results['score'] = 0.0;def inputs = states[0].inputs;Collections.sort(inputs, (a, b) -> b.get('score').compareTo(a.get('score')));for (int i = 0; i < inputs.length; i++) { double current_score = inputs[i].score / Math.pow(i + 1, params.p); if (i < 10) { inputs[i][\\"contribution\\"] = 100 * current_score / params.risk_cap; results['risk_inputs'].add(inputs[i]); } results['category_1_score'] += current_score; results['category_1_count'] += 1; results['score'] += current_score;}results['score'] *= params.global_identifier_type_weight;results['normalized_score'] = 100 * results['score'] / params.risk_cap;return results;",
|
||||
"reduce": "Map results = new HashMap();results['notes'] = [];results['category_1_score'] = 0.0;results['category_1_count'] = 0;results['risk_inputs'] = [];results['score'] = 0.0;def inputs = states[0].inputs;Collections.sort(inputs, (a, b) -> b.get('score').compareTo(a.get('score')));for (int i = 0; i < inputs.length; i++) { double current_score = inputs[i].score / Math.pow(i + 1, params.p); if (i < 10) { inputs[i]['contribution'] = current_score / params.risk_cap; results['risk_inputs'].add(inputs[i]); } results['category_1_score'] += current_score; results['category_1_count'] += 1; results['score'] += current_score;}results['score'] *= params.global_identifier_type_weight;results['normalized_score'] = results['score'] / params.risk_cap;return results;",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
|
|
@ -22,7 +22,7 @@ for (int i = 0; i < inputs.length; i++) {
|
|||
double current_score = inputs[i].score / Math.pow(i + 1, params.p);
|
||||
|
||||
if (i < 10) {
|
||||
inputs[i]["contribution"] = 100 * current_score / params.risk_cap;
|
||||
inputs[i]['contribution'] = current_score / params.risk_cap;
|
||||
results['risk_inputs'].add(inputs[i]);
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,6 @@ for (int i = 0; i < inputs.length; i++) {
|
|||
}
|
||||
|
||||
results['score'] *= params.global_identifier_type_weight;
|
||||
results['normalized_score'] = 100 * results['score'] / params.risk_cap;
|
||||
results['normalized_score'] = results['score'] / params.risk_cap;
|
||||
|
||||
return results;
|
||||
|
|
|
@ -138,8 +138,8 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
expect(score).to.eql({
|
||||
calculated_level: 'Unknown',
|
||||
calculated_score: 21,
|
||||
calculated_score_norm: 8.039816232771823,
|
||||
category_1_score: 8.039816232771821,
|
||||
calculated_score_norm: 8.10060175898781,
|
||||
category_1_score: 8.10060175898781,
|
||||
category_1_count: 1,
|
||||
id_field: 'host.name',
|
||||
id_value: 'host-1',
|
||||
|
@ -353,8 +353,8 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
criticality_modifier: 1.5,
|
||||
calculated_level: 'Unknown',
|
||||
calculated_score: 21,
|
||||
calculated_score_norm: 11.59366948840633,
|
||||
category_1_score: 8.039816232771821,
|
||||
calculated_score_norm: 11.677912063468526,
|
||||
category_1_score: 8.10060175898781,
|
||||
category_1_count: 1,
|
||||
id_field: 'host.name',
|
||||
id_value: 'host-1',
|
||||
|
|
|
@ -126,8 +126,8 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const expectedScore = {
|
||||
calculated_level: 'Unknown',
|
||||
calculated_score: 21,
|
||||
calculated_score_norm: 8.039816232771823,
|
||||
category_1_score: 8.039816232771821,
|
||||
calculated_score_norm: 8.10060175898781,
|
||||
category_1_score: 8.10060175898781,
|
||||
category_1_count: 1,
|
||||
id_field: 'host.name',
|
||||
id_value: 'host-1',
|
||||
|
@ -176,8 +176,8 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
criticality_modifier: 1.5,
|
||||
calculated_level: 'Unknown',
|
||||
calculated_score: 21,
|
||||
calculated_score_norm: 11.59366948840633,
|
||||
category_1_score: 8.039816232771821,
|
||||
calculated_score_norm: 11.677912063468526,
|
||||
category_1_score: 8.10060175898781,
|
||||
category_1_count: 1,
|
||||
id_field: 'host.name',
|
||||
id_value: 'host-1',
|
||||
|
|
|
@ -116,9 +116,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
expect(score).to.eql({
|
||||
calculated_level: 'Unknown',
|
||||
calculated_score: 21,
|
||||
calculated_score_norm: 8.039816232771823,
|
||||
calculated_score_norm: 8.10060175898781,
|
||||
category_1_count: 1,
|
||||
category_1_score: 8.039816232771821,
|
||||
category_1_score: 8.10060175898781,
|
||||
id_field: 'host.name',
|
||||
id_value: 'host-1',
|
||||
});
|
||||
|
@ -144,18 +144,18 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
{
|
||||
calculated_level: 'Unknown',
|
||||
calculated_score: 21,
|
||||
calculated_score_norm: 8.039816232771823,
|
||||
calculated_score_norm: 8.10060175898781,
|
||||
category_1_count: 1,
|
||||
category_1_score: 8.039816232771821,
|
||||
category_1_score: 8.10060175898781,
|
||||
id_field: 'host.name',
|
||||
id_value: 'host-1',
|
||||
},
|
||||
{
|
||||
calculated_level: 'Unknown',
|
||||
calculated_score: 21,
|
||||
calculated_score_norm: 8.039816232771823,
|
||||
calculated_score_norm: 8.10060175898781,
|
||||
category_1_count: 1,
|
||||
category_1_score: 8.039816232771821,
|
||||
category_1_score: 8.10060175898781,
|
||||
id_field: 'host.name',
|
||||
id_value: 'host-2',
|
||||
},
|
||||
|
@ -177,9 +177,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
{
|
||||
calculated_level: 'Unknown',
|
||||
calculated_score: 28.42462120245875,
|
||||
calculated_score_norm: 10.88232052161514,
|
||||
calculated_score_norm: 10.964596976723788,
|
||||
category_1_count: 2,
|
||||
category_1_score: 10.882320521615142,
|
||||
category_1_score: 10.964596976723788,
|
||||
id_field: 'host.name',
|
||||
id_value: 'host-1',
|
||||
},
|
||||
|
@ -199,9 +199,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
{
|
||||
calculated_level: 'Unknown',
|
||||
calculated_score: 47.25513506055279,
|
||||
calculated_score_norm: 18.091552473412246,
|
||||
calculated_score_norm: 18.228334771081926,
|
||||
category_1_count: 30,
|
||||
category_1_score: 18.091552473412246,
|
||||
category_1_score: 18.228334771081926,
|
||||
id_field: 'host.name',
|
||||
id_value: 'host-1',
|
||||
},
|
||||
|
@ -224,18 +224,18 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
{
|
||||
calculated_level: 'Unknown',
|
||||
calculated_score: 47.25513506055279,
|
||||
calculated_score_norm: 18.091552473412246,
|
||||
calculated_score_norm: 18.228334771081926,
|
||||
category_1_count: 30,
|
||||
category_1_score: 18.091552473412246,
|
||||
category_1_score: 18.228334771081926,
|
||||
id_field: 'host.name',
|
||||
id_value: 'host-1',
|
||||
},
|
||||
{
|
||||
calculated_level: 'Unknown',
|
||||
calculated_score: 21,
|
||||
calculated_score_norm: 8.039816232771823,
|
||||
calculated_score_norm: 8.10060175898781,
|
||||
category_1_count: 1,
|
||||
category_1_score: 8.039816232771821,
|
||||
category_1_score: 8.10060175898781,
|
||||
id_field: 'host.name',
|
||||
id_value: 'host-2',
|
||||
},
|
||||
|
@ -255,9 +255,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
{
|
||||
calculated_level: 'Unknown',
|
||||
calculated_score: 50.67035607277805,
|
||||
calculated_score_norm: 19.399064346392823,
|
||||
calculated_score_norm: 19.545732168175455,
|
||||
category_1_count: 100,
|
||||
category_1_score: 19.399064346392823,
|
||||
category_1_score: 19.545732168175455,
|
||||
id_field: 'host.name',
|
||||
id_value: 'host-1',
|
||||
},
|
||||
|
@ -280,9 +280,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
{
|
||||
calculated_level: 'Critical',
|
||||
calculated_score: 241.2874098703716,
|
||||
calculated_score_norm: 92.37649688758484,
|
||||
calculated_score_norm: 93.07491508654975,
|
||||
category_1_count: 100,
|
||||
category_1_score: 92.37649688758484,
|
||||
category_1_score: 93.07491508654975,
|
||||
id_field: 'host.name',
|
||||
id_value: 'host-1',
|
||||
},
|
||||
|
@ -311,9 +311,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
{
|
||||
calculated_level: 'Critical',
|
||||
calculated_score: 254.91456029175757,
|
||||
calculated_score_norm: 97.59362951445543,
|
||||
calculated_score_norm: 98.33149216623883,
|
||||
category_1_count: 1000,
|
||||
category_1_score: 97.59362951445543,
|
||||
category_1_score: 98.33149216623883,
|
||||
id_field: 'host.name',
|
||||
id_value: 'host-1',
|
||||
},
|
||||
|
@ -407,9 +407,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
{
|
||||
calculated_level: 'High',
|
||||
calculated_score: 225.1106801442913,
|
||||
calculated_score_norm: 86.18326192354185,
|
||||
calculated_score_norm: 86.83485578779946,
|
||||
category_1_count: 100,
|
||||
category_1_score: 86.18326192354185,
|
||||
category_1_score: 86.83485578779946,
|
||||
id_field: 'host.name',
|
||||
id_value: 'host-1',
|
||||
},
|
||||
|
@ -436,9 +436,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
{
|
||||
calculated_level: 'Moderate',
|
||||
calculated_score: 120.6437049351858,
|
||||
calculated_score_norm: 46.18824844379242,
|
||||
calculated_score_norm: 46.537457543274876,
|
||||
category_1_count: 100,
|
||||
category_1_score: 92.37649688758484,
|
||||
category_1_score: 93.07491508654975,
|
||||
id_field: 'host.name',
|
||||
id_value: 'host-1',
|
||||
},
|
||||
|
@ -463,9 +463,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
{
|
||||
calculated_level: 'Moderate',
|
||||
calculated_score: 168.9011869092601,
|
||||
calculated_score_norm: 64.66354782130938,
|
||||
calculated_score_norm: 65.15244056058482,
|
||||
category_1_count: 100,
|
||||
category_1_score: 92.37649688758484,
|
||||
category_1_score: 93.07491508654975,
|
||||
id_field: 'user.name',
|
||||
id_value: 'user-1',
|
||||
},
|
||||
|
@ -492,9 +492,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
{
|
||||
calculated_level: 'Low',
|
||||
calculated_score: 93.23759116471251,
|
||||
calculated_score_norm: 35.695861854790394,
|
||||
calculated_score_norm: 35.96574261869793,
|
||||
category_1_count: 50,
|
||||
category_1_score: 89.23965463697598,
|
||||
category_1_score: 89.91435654674481,
|
||||
id_field: 'host.name',
|
||||
id_value: 'host-1',
|
||||
},
|
||||
|
@ -504,9 +504,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
{
|
||||
calculated_level: 'High',
|
||||
calculated_score: 186.47518232942502,
|
||||
calculated_score_norm: 71.39172370958079,
|
||||
calculated_score_norm: 71.93148523739586,
|
||||
category_1_count: 50,
|
||||
category_1_score: 89.23965463697598,
|
||||
category_1_score: 89.91435654674481,
|
||||
id_field: 'user.name',
|
||||
id_value: 'user-1',
|
||||
},
|
||||
|
@ -547,18 +547,18 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
criticality_modifier: 2.0,
|
||||
calculated_level: 'Unknown',
|
||||
calculated_score: 21,
|
||||
calculated_score_norm: 14.8830616583983,
|
||||
calculated_score_norm: 14.987153868113044,
|
||||
category_1_count: 1,
|
||||
category_1_score: 8.039816232771821,
|
||||
category_1_score: 8.10060175898781,
|
||||
id_field: 'host.name',
|
||||
id_value: 'host-1',
|
||||
},
|
||||
{
|
||||
calculated_level: 'Unknown',
|
||||
calculated_score: 21,
|
||||
calculated_score_norm: 8.039816232771823,
|
||||
calculated_score_norm: 8.10060175898781,
|
||||
category_1_count: 1,
|
||||
category_1_score: 8.039816232771821,
|
||||
category_1_score: 8.10060175898781,
|
||||
id_field: 'host.name',
|
||||
id_value: 'host-2',
|
||||
},
|
||||
|
|
|
@ -274,9 +274,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
criticality_modifier: 2,
|
||||
calculated_level: 'Moderate',
|
||||
calculated_score: 79.81345973382406,
|
||||
calculated_score_norm: 46.809565696393314,
|
||||
calculated_score_norm: 47.08016240063269,
|
||||
category_1_count: 10,
|
||||
category_1_score: 30.55645472198471,
|
||||
category_1_score: 30.787478681462762,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue