Skip to content

Commit 27132f3

Browse files
author
Tom Pearson
authored
Indicator value override (#15)
* tighten indicator identification * add testing workflow * allow the user to hardcode calculated indicator values
1 parent 7bf40de commit 27132f3

File tree

6 files changed

+50
-31
lines changed

6 files changed

+50
-31
lines changed
File renamed without changes.

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ There will often be ancillary imformation associated with each entity, things li
3939
---
4040
## API
4141

42-
### indexCore(_indicators:Array_, _entities:Array_, [_indexMax:Number_])
42+
### indexCore(_indicators:Array_, _entities:Array_, [_indexMax:Number=100_],[_allowOverwrite:Boolean=true_])
4343

4444
indexCore constructs and calculates an index it returns an `index` object allowing access to results of the calculation for all the entities at a give indicator level, the structure of the index as a whole and methods for adjusting indicator values and weightings.
4545

@@ -68,7 +68,10 @@ __entities__ is an array of enetity objects
6868

6969
Take a look at the data source descriptions for a fuller description of the properties these objects might take: [indicators](#data-indicators) [entities](#data-entities)
7070

71-
__indexMax__, optional. is the maximum value for the index score (minimum is always 0) by thefault this is 100, other typical values are 1 and 10.
71+
__indexMax__, optional. is the maximum value for the index score (minimum is always 0) by default this is 100, other typical values are 1 and 10.
72+
73+
__allowOverwrite__, optional. By default this is set to true ensuring that all calculated values _will_ be calculated by the module. If set to false, calculated values that are supplied in the `entities` sheet will take precedence. Most of the time you don't want to do this but it might be a handy escape hatch.
74+
7275

7376
### indexCore.__adjustValue(_entityName:String_, _indicatorID:String_, _value:Number_)__
7477
A function that allows the user to adjust an entity's indicator score within the index the index is recalculated using the new value. The index is recalculated after calling this function.

data/simple-index-set/entities.csv

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
name,1.1,1.2,1.3,1.4,1.5,1.6,2.1,2.2,2.3.1,2.3.2,3.1,3.2
2-
Catan,5,5,5,8,1.5,7,6,4,1,0,35,70
3-
Monopoly,4,4,9,6,2,2,4,4,1,0,19,5
4-
Brass: Birmingham,8,8,3,7,3,6,7,7,4,10,50,100
5-
Treasure Island,7,5,3,7,1.5,8,8,8,6,10,40,20
6-
Tigris and Eurphrates,8,5,4,6,1,7,3,6,1,5,35,100
7-
The Crew,8,4,7,6,.5,7,3,6,0,0,13,30
8-
Twilight Imperium,6,7,4,4,7,8,8,6,1,-5,90,10
1+
name,1,1.1,1.2,1.3,1.4,1.5,1.6,2.1,2.2,2.3.1,2.3.2,3.1,3.2
2+
Catan,10,5,5,5,8,1.5,7,6,4,1,0,35,70
3+
Monopoly,,4,4,9,6,2,2,4,4,1,0,19,5
4+
Brass: Birmingham,,8,8,3,7,3,6,7,7,4,10,50,100
5+
Treasure Island,,7,5,3,7,1.5,8,8,8,6,10,40,20
6+
Tigris and Eurphrates,,8,5,4,6,1,7,3,6,1,5,35,100
7+
The Crew,,8,4,7,6,.5,7,3,6,0,0,13,30
8+
Twilight Imperium,,6,7,4,4,7,8,8,6,1,-5,90,10

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@economist/index-core",
3-
"version": "1.3.3",
3+
"version": "1.4.0",
44
"description": "",
55
"main": "src/index-core.js",
66
"type": "module",

src/index-core.js

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { calculateWeightedMean, clone, normalise } from './utils.js';
22

33
const indicatorIdTest = /^([\w]\.)*\w{1}$/;
44

5-
function indexCore(indicatorsData = [], entitiesData = [], indexMax = 100) {
5+
function indexCore(indicatorsData = [], entitiesData = [], indexMax = 100, allowOverwrite = true) {
66
if (indicatorsData.length === 0 || entitiesData.length === 0) return {};
77
const indicatorLookup = Object.fromEntries(
88
indicatorsData
@@ -92,24 +92,30 @@ function indexCore(indicatorsData = [], entitiesData = [], indexMax = 100) {
9292
};
9393
}
9494

95-
function indexEntity(entity, calculationList) {
95+
function indexEntity(entity, calculationList, overwrite = allowOverwrite) {
9696
const newEntity = clone(entity);
9797
calculationList.forEach((indicatorID) => {
98-
if (newEntity[indicatorID]) { console.log('overwriting', indicatorID); }
99-
// get the required component indicators to calculate the parent value
100-
// this is a bit brittle maybe?
101-
const componentIndicators = indicatorsData
102-
.filter((indicator) => (indicator.id.indexOf(indicatorID) === 0
103-
&& indicator.id.length === indicatorID.length + 2))
104-
.filter((indicator) => !excludeIndicator(indicator))
105-
.map((indicator) => formatIndicator(indicator, newEntity, indexMax));
106-
// calculate the weighted mean of the component indicators on the newEntity
107-
// assign that value to the newEntity
108-
newEntity[indicatorID] = calculateWeightedMean(componentIndicators, indexMax);
98+
if (newEntity[indicatorID] && overwrite === true
99+
|| !newEntity[indicatorID])
100+
{
101+
// get the required component indicators to calculate the parent value
102+
// this is a bit brittle maybe?
103+
const componentIndicators = indicatorsData
104+
.filter((indicator) => (indicator.id.indexOf(indicatorID) === 0
105+
&& indicator.id.length === indicatorID.length + 2))
106+
.filter((indicator) => !excludeIndicator(indicator))
107+
.map((indicator) => formatIndicator(indicator, newEntity, indexMax));
108+
// calculate the weighted mean of the component indicators on the newEntity
109+
// assign that value to the newEntity
110+
newEntity[indicatorID] = calculateWeightedMean(componentIndicators, indexMax);
111+
}else{
112+
console.log(`retaining existing value for ${newEntity.name} - ${indicatorID} : ${Number(entity[indicatorID])}`)
113+
newEntity[indicatorID] = Number(entity[indicatorID]);
114+
}
109115
});
110116

111117
const pillarIndicators = indicatorsData
112-
.filter((indicator) => indicator.id.match(indicatorIdTest) && indicator.id.split('.').length === 1)
118+
.filter((indicator) => String(indicator.id).match(indicatorIdTest) && indicator.id.split('.').length === 1)
113119
.map((indicator) => formatIndicator(indicator, newEntity, indexMax));
114120

115121
newEntity.value = calculateWeightedMean(pillarIndicators, indexMax);
@@ -156,10 +162,10 @@ function indexCore(indicatorsData = [], entitiesData = [], indexMax = 100) {
156162
return tree;
157163
}
158164

159-
function calculateIndex() {
165+
function calculateIndex(overwrite = allowOverwrite) {
160166
const onlyIdIndicators = indicatorsData
161167
.filter((i) =>{
162-
const isIndicator = i.id.match(indicatorIdTest)
168+
const isIndicator = String(i.id).match(indicatorIdTest)
163169
const isExcluded = excludeIndicator(i);
164170
return isIndicator && !isExcluded;
165171
});
@@ -173,7 +179,7 @@ function indexCore(indicatorsData = [], entitiesData = [], indexMax = 100) {
173179
indexStructure = createStructure(onlyIdIndicators.map((i) => i.id));
174180

175181
entitiesData.forEach((entity) => {
176-
const indexedEntity = indexEntity(entity, calculationList);
182+
const indexedEntity = indexEntity(entity, calculationList, overwrite);
177183
indexedEntity.data = entity;
178184
indexedData[entity.name] = indexedEntity;
179185
});
@@ -186,12 +192,12 @@ function indexCore(indicatorsData = [], entitiesData = [], indexMax = 100) {
186192
calculateIndex();
187193
}
188194

189-
function filterIndicators(exclude = ()=>false){
195+
function filterIndicators(exclude = ()=>false, overwrite=allowOverwrite){
190196
excludeIndicator = exclude;
191-
calculateIndex();
197+
calculateIndex(overwrite);
192198
}
193199

194-
calculateIndex();
200+
calculateIndex(allowOverwrite);
195201

196202
return {
197203
adjustValue,

test/index-core.test.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,14 @@ test('diverging indicator index-core', ()=>{
4747
const simpleIndex = indexCore(simpleIndicators, simpleEntities);
4848
expect(simpleIndex.indexedData['Tigris and Eurphrates']['2.3']).toBe(70);
4949
expect(simpleIndex.indexedData['Twilight Imperium']['2.3']).toBe(70);
50+
})
51+
52+
test('indicator overide index-core', ()=>{
53+
const simpleIndexOverwrite = indexCore(simpleIndicators, simpleEntities, undefined, true);
54+
expect(simpleIndexOverwrite.indexedData['Catan']['1']).toBe(46.66666666666667);
55+
expect(simpleIndexOverwrite.indexedData['Twilight Imperium']['2.3']).toBe(70);
56+
57+
const simpleIndex = indexCore(simpleIndicators, simpleEntities, undefined, false);
58+
expect(simpleIndex.indexedData['Catan']['1']).toBe(10);
59+
expect(simpleIndex.indexedData['Twilight Imperium']['2.3']).toBe(70);
5060
})

0 commit comments

Comments
 (0)