@rasifix/orienteering-utils 1.0.6 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +10 -2
- package/src/format.ts +11 -0
- package/src/formats/index.ts +9 -0
- package/src/formats/kraemer.ts +145 -0
- package/src/formats/oware.ts +143 -0
- package/src/formats/solv.ts +169 -0
- package/src/index.ts +5 -0
- package/src/model/category.ts +9 -0
- package/src/model/competition.ts +9 -0
- package/src/model/ranking.ts +83 -0
- package/src/model/runner.ts +23 -0
- package/src/model/split.ts +4 -0
- package/{lib/time.js → src/time.ts} +11 -8
- package/{lib/anonymizer.js → src/utils/anonymizer.ts} +20 -21
- package/src/utils/ranking.ts +535 -0
- package/test/kraemer-test.ts +78 -0
- package/test/kraemer.csv +130 -0
- package/test/oware-test.ts +79 -0
- package/test/oware.csv +1 -1
- package/test/ranking-test.ts +55 -0
- package/test/solv-test.ts +65 -0
- package/test/test.js +6 -3
- package/tsconfig.json +11 -0
- package/index.d.ts +0 -4
- package/index.js +0 -11
- package/lib/butterfly.js +0 -59
- package/lib/kraemer.js +0 -125
- package/lib/oware.js +0 -105
- package/lib/ranking.js +0 -422
- package/lib/solv.js +0 -124
- package/test/oware-test.js +0 -82
- package/test/ranking-test.js +0 -102
- package/test/solv-test.js +0 -68
- /package/{lib/reorganize.js → src/utils/reorganize.ts} +0 -0
package/lib/kraemer.js
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
var { parseTime, formatTime } = require('./time');
|
|
2
|
-
|
|
3
|
-
function strip(str) {
|
|
4
|
-
if (!str) {
|
|
5
|
-
return '';
|
|
6
|
-
} else if (str.length > 0 && str[0] === '"') {
|
|
7
|
-
return str.substring(1, str.length - 1);
|
|
8
|
-
} else {
|
|
9
|
-
return str;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// OE0014;Stnr;XStnr;Chipnr;Datenbank Id;Nachname;Vorname;Jg;G;Block; AK; Start;Ziel; Zeit; Wertung;Gutschrift -;Zuschlag +;Kommentar;Club-Nr.;Abk; Ort; Nat; Sitz;Region;Katnr;Kurz; Lang; MeldeKat. Nr;MeldeKat. (kurz);MeldeKat. (lang);Rang;Ranglistenpunkte;Num1;Num2; Num3; Text1; Text2; Text3; Adr. Nachname;Adr. Vorname;Straße;Zeile2;PLZ; Adr. Ort;Tel; Mobil; Fax; EMail; Gemietet;Startgeld;Bezahlt;Mannschaft;Bahnnummer;Bahn; km; Hm; Bahn Posten;Platz; Startstempel;Zielstempel;Posten1;Stempel1;Posten2;Stempel2;Posten3; Stempel3;Posten4;Stempel4;Posten5;Stempel5;Posten6;Stempel6;Posten7;Stempel7;Posten8;Stempel8;Posten9;Stempel9;Posten10;Stempel10;Posten11;Stempel11;Posten12;Stempel12;Posten13;Stempel13;Posten14;Stempel14;Posten15;Stempel15;Posten16;Stempel16;Posten17;Stempel17;Posten18;Stempel18;Posten19;Stempel19;Posten20;Stempel20;Posten21;Stempel21;Posten22;Stempel22;Posten23;Stempel23;Posten24;Stempel24;Posten25;Stempel25;Posten26;Stempel26;Posten27;Stempel27;Posten28;Stempel28;Posten29;Stempel29;Posten30;Stempel30;Posten31;Stempel31;Posten32;Stempel32;Posten33;Stempel33;Posten34;Stempel34;Posten35;Stempel35;Posten36;Stempel36;Posten37;Stempel37;Posten38;Stempel38;Posten39;Stempel39;Posten40;Stempel40;Posten41;Stempel41;Posten42;Stempel42;Posten43;Stempel43;Posten44;Stempel44;Posten45;Stempel45;Posten46;Stempel46;Posten47;Stempel47;Posten48;Stempel48;Posten49;Stempel49;Posten50;Stempel50;Posten51;Stempel51;Posten52;Stempel52;Posten53;Stempel53;Posten54;Stempel54;Posten55;Stempel55;Posten56;Stempel56;Posten57;Stempel57;Posten58;Stempel58;Posten59;Stempel59;Posten60;Stempel60;Posten61;Stempel61;Posten62;Stempel62;Posten63;Stempel63;Posten64;Stempel64;
|
|
14
|
-
// Stnr ;Chip;Datenbank Id;Nachname;Vorname;Jg;G;Block;AK;Start;Ziel;Zeit; Wertung;Club-Nr.;Abk; Ort; Nat; Katnr; Kurz; Lang;Num1;Num2;Num3;Text1; Text2;Text3;Adr. Name;Straße; Zeile2; PLZ; Ort; Tel; Fax; EMail;Id/Verein;Gemietet;Startgeld;Bezahlt;Bahnnummer; Bahn; km; Hm; Bahn Posten;Pl; Startstempel;Zielstempel;Posten1;Stempel1;Posten2; Stempel2; Posten3;Stempel3; Posten4; Stempel4;Posten5;Stempel5;Posten6; Stempel6;Posten7; Stempel7; Posten8;Stempel8;Posten9;Stempel9;Posten10;Stempel10;(und weitere)...
|
|
15
|
-
module.exports.parse = function(text, options = {}) {
|
|
16
|
-
// split text into lines
|
|
17
|
-
var lines = text.split('[\r\n]+');
|
|
18
|
-
|
|
19
|
-
// extract header
|
|
20
|
-
var header = lines[0].split(";");
|
|
21
|
-
var firstTimeIdx;
|
|
22
|
-
|
|
23
|
-
// trying to get hold of correct column indices
|
|
24
|
-
var indices = { };
|
|
25
|
-
indices['Nachname'] = header.indexOf('Nachname');
|
|
26
|
-
indices['Vorname'] = header.indexOf('Vorname');
|
|
27
|
-
indices['Jg'] = header.indexOf('Jg');
|
|
28
|
-
indices['G'] = header.indexOf('G');
|
|
29
|
-
indices['Datenbank Id'] = header.indexOf('Datenbank Id');
|
|
30
|
-
indices['Abk'] = header.indexOf('Abk');
|
|
31
|
-
indices['Club'] = header.indexOf('Ort');
|
|
32
|
-
indices['Ort'] = header.indexOf('Adr. Ort');
|
|
33
|
-
indices['Nat'] = header.indexOf('Nat');
|
|
34
|
-
indices['Start'] = header.indexOf('Start');
|
|
35
|
-
indices['Ziel'] = header.indexOf('Ziel');
|
|
36
|
-
indices['Zeit'] = header.indexOf('Zeit');
|
|
37
|
-
indices['Katnr'] = header.indexOf('Kurz');
|
|
38
|
-
indices['Wertung'] = header.indexOf('Wertung');
|
|
39
|
-
indices['Posten'] = header.indexOf('Bahn Posten');
|
|
40
|
-
indices['km'] = header.indexOf('km');
|
|
41
|
-
indices['hm'] = header.indexOf('Hm');
|
|
42
|
-
firstTimeIdx = header.indexOf('Posten1');
|
|
43
|
-
|
|
44
|
-
if (header[0] === 'OE0014') {
|
|
45
|
-
indices['Chip'] = header.indexOf('Chipnr');
|
|
46
|
-
} else {
|
|
47
|
-
indices['Chip'] = header.indexOf('Chip');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
lines = lines.slice(1);
|
|
51
|
-
|
|
52
|
-
// the result object
|
|
53
|
-
var categories = { };
|
|
54
|
-
|
|
55
|
-
function objectify(cols) {
|
|
56
|
-
var result = { };
|
|
57
|
-
Object.keys(indices).forEach(function(key) {
|
|
58
|
-
result[key] = cols[indices[key]];
|
|
59
|
-
});
|
|
60
|
-
return result;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
lines.forEach(function(line) {
|
|
64
|
-
var cols = line.split(";");
|
|
65
|
-
var lineObj = objectify(cols);
|
|
66
|
-
|
|
67
|
-
if (lineObj['Wertung'] !== '0') {
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
let runnerName = strip(lineObj['Nachname']);
|
|
72
|
-
let runnerFirstname = strip(lineObj['Vorname']);
|
|
73
|
-
var runner = {
|
|
74
|
-
name: runnerName,
|
|
75
|
-
firstName: runnerFirstname,
|
|
76
|
-
fullName: [runnerFirstname, runnerName].join(' '),
|
|
77
|
-
yearOfBirth: lineObj['Jg'],
|
|
78
|
-
sex: strip(lineObj['G']),
|
|
79
|
-
club: (strip(lineObj['Abk']) + ' ' + strip(lineObj['Club'])).trim(),
|
|
80
|
-
city: strip(lineObj['Ort']),
|
|
81
|
-
nation: strip(lineObj['Nat']),
|
|
82
|
-
time: formatTime(parseTime(lineObj['Zeit'])),
|
|
83
|
-
startTime: formatTime(parseTime(lineObj['Start'])),
|
|
84
|
-
ecard: strip(lineObj['Chip']),
|
|
85
|
-
splits: []
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
var category = categories[strip(lineObj['Katnr'])];
|
|
89
|
-
if (typeof category === 'undefined') {
|
|
90
|
-
category = {
|
|
91
|
-
name: strip(lineObj['Katnr']),
|
|
92
|
-
distance: parseInt(strip(lineObj['km']), 10) * 1000,
|
|
93
|
-
ascent: strip(lineObj['hm']),
|
|
94
|
-
controls: strip(lineObj['Posten']),
|
|
95
|
-
runners: []
|
|
96
|
-
};
|
|
97
|
-
categories[category.name] = category;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
var times = cols.slice(firstTimeIdx);
|
|
101
|
-
for (var idx = 0; idx < parseInt(lineObj['Posten'], 10) * 2; idx += 2) {
|
|
102
|
-
if (idx === times.length - 1) {
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
runner.splits.push([times[idx], formatTime(parseTime(times[idx + 1]))]);
|
|
106
|
-
}
|
|
107
|
-
runner.course = runner.splits.map(function(split) { return split[0]; }).join(',');
|
|
108
|
-
|
|
109
|
-
category.runners.push(runner);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
name: options.event || 'Anonymous Event',
|
|
114
|
-
map: options.map || 'Unknown Map',
|
|
115
|
-
date: date || '',
|
|
116
|
-
startTime: startTime || '',
|
|
117
|
-
categories: Object.keys(categories).map(function(category) {
|
|
118
|
-
return categories[category];
|
|
119
|
-
})
|
|
120
|
-
};
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
module.exports.formatCheck = function(text) {
|
|
124
|
-
return text.substring(0, 6) === 'OE0014' || text.substring(0, 23) === 'Stnr;Chip;Datenbank Id;';
|
|
125
|
-
};
|
package/lib/oware.js
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
function parseCategory(row) {
|
|
2
|
-
return {
|
|
3
|
-
name: row[0],
|
|
4
|
-
distance: parseInt(row[1], 10),
|
|
5
|
-
ascent: parseInt(row[2], 10),
|
|
6
|
-
controls: parseInt(row[3], 10),
|
|
7
|
-
runners: []
|
|
8
|
-
};
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function parseRunner(row, category, id) {
|
|
12
|
-
var headerLength = 15;
|
|
13
|
-
var i;
|
|
14
|
-
|
|
15
|
-
var splits = [];
|
|
16
|
-
for (i = headerLength; i < row.length; i += 2) {
|
|
17
|
-
splits.push({ 'code': row[i], 'time': row[i + 1] });
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// split cleanup - detect two following splits with identical time
|
|
21
|
-
// --> control not working properly; set 's' as split time (substitute)
|
|
22
|
-
// going from back to front to catch several not working controls
|
|
23
|
-
for (i = splits.length - 1; i > 0; i--) {
|
|
24
|
-
if (splits[i].time === splits[i - 1].time && splits[i].time !== '-') {
|
|
25
|
-
splits[i].time = 's';
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
id: id,
|
|
31
|
-
category: category,
|
|
32
|
-
rank: row[0] ? parseInt(row[0]) : null,
|
|
33
|
-
name: row[1],
|
|
34
|
-
firstName: row[2],
|
|
35
|
-
fullName: [row[2], row[1]].join(' '),
|
|
36
|
-
yearOfBirth: row[3],
|
|
37
|
-
sex: row[4],
|
|
38
|
-
club: row[8],
|
|
39
|
-
city: row[7],
|
|
40
|
-
nation: row[9],
|
|
41
|
-
time: row[12],
|
|
42
|
-
startTime: row[13],
|
|
43
|
-
ecard: row[11],
|
|
44
|
-
splits: splits
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
module.exports.parse = function(text) {
|
|
49
|
-
// split text into lines
|
|
50
|
-
var lines = text.trim().split(/[\r\n]+/);
|
|
51
|
-
|
|
52
|
-
// throw away first row containing headers
|
|
53
|
-
lines = lines.splice(1);
|
|
54
|
-
|
|
55
|
-
// second row contains information about the event
|
|
56
|
-
var header = lines[0].split(';');
|
|
57
|
-
|
|
58
|
-
var event = {
|
|
59
|
-
// row starts with a double slash
|
|
60
|
-
name: header[0].substring(2, header[0].length),
|
|
61
|
-
map: header[1],
|
|
62
|
-
date: header[2],
|
|
63
|
-
startTime: header[3],
|
|
64
|
-
categories: [ ]
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
// throw a way the now parsed header
|
|
68
|
-
lines = lines.splice(1);
|
|
69
|
-
|
|
70
|
-
var category = null;
|
|
71
|
-
|
|
72
|
-
let idx = 0;
|
|
73
|
-
lines.filter(line => line.trim().length > 0).forEach(function(line) {
|
|
74
|
-
var cols = line.split(';');
|
|
75
|
-
if (cols.length === 4) {
|
|
76
|
-
category = parseCategory(cols);
|
|
77
|
-
event.categories.push(category);
|
|
78
|
-
} else {
|
|
79
|
-
category.runners.push(parseRunner(cols, category.name, ++idx));
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
return event;
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
module.exports.serialize = function(event) {
|
|
87
|
-
var result = '//Format: Rank;Name;Firstname;YearOfBirth;SexMF;FedNr;Zip;Town;Club;NationIOF;StartNr;eCardNr;RunTime;StartTime;FinishTime;CtrlCode;SplitTime; ...\n';
|
|
88
|
-
result += '//' + [event.name, event.map, event.date, event.startTime, ''].join(';') + '\n';
|
|
89
|
-
|
|
90
|
-
event.categories.forEach(function(category) {
|
|
91
|
-
result += [category.name, category.distance, category.ascent, category.controls].join(';') + '\n';
|
|
92
|
-
category.runners.forEach(function(runner) {
|
|
93
|
-
result += [runner.rank, runner.name, runner.firstName, runner.yearOfBirth, runner.sex, '', runner.zip, runner.city, runner.club, runner.nation, '', runner.ecard, runner.time, runner.startTime, ''].join(';');
|
|
94
|
-
result += ';' + runner.splits.map(function(split) {
|
|
95
|
-
return split.code + ';' + split.time;
|
|
96
|
-
}).join(';') + '\n';
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
return result;
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
module.exports.formatCheck = function(text) {
|
|
104
|
-
return text.substring(0, 8) === '//Format';
|
|
105
|
-
};
|
package/lib/ranking.js
DELETED
|
@@ -1,422 +0,0 @@
|
|
|
1
|
-
const { parseTime, formatTime } = require('./time');
|
|
2
|
-
const { reorganize } = require('./reorganize');
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
function invalidTime(time) {
|
|
6
|
-
return !time || time < 0;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function validTime(time) {
|
|
10
|
-
return !invalidTime(time);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function sum(a1, a2) {
|
|
14
|
-
return a1 + a2;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function defineCourses(runners) {
|
|
18
|
-
let courses = { };
|
|
19
|
-
runners.forEach(runner => {
|
|
20
|
-
let course = defineCourse(runner);
|
|
21
|
-
if (!courses[course]) {
|
|
22
|
-
courses[course] = {
|
|
23
|
-
code: course,
|
|
24
|
-
runners: [ runner.id ]
|
|
25
|
-
};
|
|
26
|
-
} else {
|
|
27
|
-
courses[course].runners.push(runner.id);
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
return Object.keys(courses).map(key => courses[key]);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function defineCourse(runner) {
|
|
34
|
-
return 'St,' + runner.splits.map(split => split.code).join(',');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function defineLegs(runners) {
|
|
38
|
-
let result = { };
|
|
39
|
-
|
|
40
|
-
runners.filter(runner => runner.splits.length > 0).forEach(runner => {
|
|
41
|
-
runner.splits.forEach((split, idx) => {
|
|
42
|
-
if (validTime(split.split)) {
|
|
43
|
-
let from = idx === 0 ? 'St' : runner.splits[idx - 1].code;
|
|
44
|
-
let code = from + '-' + split.code;
|
|
45
|
-
addRunnerToLeg(result, code, runner, split.split);
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
return result;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function addRunnerToLeg(legs, code, runner, split) {
|
|
54
|
-
let leg = legs[code];
|
|
55
|
-
if (!leg) {
|
|
56
|
-
leg = legs[code] = createLeg(code);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
leg.runners.push({
|
|
60
|
-
id: runner.id,
|
|
61
|
-
fullName: runner.fullName,
|
|
62
|
-
split: split
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function createLeg(code) {
|
|
67
|
-
return {
|
|
68
|
-
code: code,
|
|
69
|
-
runners: []
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function defineRunners(runners) {
|
|
74
|
-
return runners.map(function(runner) {
|
|
75
|
-
return {
|
|
76
|
-
id: runner.id,
|
|
77
|
-
fullName: runner.fullName,
|
|
78
|
-
time: runner.time,
|
|
79
|
-
yearOfBirth: runner.yearOfBirth,
|
|
80
|
-
city: runner.city,
|
|
81
|
-
club: runner.club,
|
|
82
|
-
splits: runner.splits.map(function(split, idx) {
|
|
83
|
-
var splitTime = null;
|
|
84
|
-
if (split.time === '-') {
|
|
85
|
-
splitTime = '-';
|
|
86
|
-
} else if (idx === 0) {
|
|
87
|
-
splitTime = parseTime(split.time);
|
|
88
|
-
} else {
|
|
89
|
-
if (parseTime(split.time) === null || parseTime(runner.splits[idx - 1].time) === null) {
|
|
90
|
-
splitTime = '-';
|
|
91
|
-
} else {
|
|
92
|
-
splitTime = parseTime(split.time) - parseTime(runner.splits[idx - 1].time);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
code: split.code,
|
|
98
|
-
time: split.time,
|
|
99
|
-
split: splitTime
|
|
100
|
-
};
|
|
101
|
-
})
|
|
102
|
-
};
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Define properties of each leg. After this method the leg structure will be enhanced as follows:
|
|
108
|
-
*
|
|
109
|
-
* - runners are sorted per split time per leg
|
|
110
|
-
* - each leg has a property 'idealSplit' (ideal split time of this leg)
|
|
111
|
-
* - each leg has a property 'fastestSplit'
|
|
112
|
-
* - each runner entry of a leg is enhanced with 'splitBehind' and 'splitRank'
|
|
113
|
-
*
|
|
114
|
-
* @param {*} legs leg data structre (only split is relevant)
|
|
115
|
-
*/
|
|
116
|
-
function defineLegProperties(legs) {
|
|
117
|
-
Object.keys(legs).forEach(code => {
|
|
118
|
-
let leg = legs[code];
|
|
119
|
-
leg.runners.sort(function(r1, r2) {
|
|
120
|
-
return r1.split - r2.split;
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// calculate the ideal time: take up to 5 fastest on that leg
|
|
124
|
-
let selected = leg.runners.slice(0, Math.min(leg.runners.length, 5)).map(runner => runner.split);
|
|
125
|
-
|
|
126
|
-
// only if there are valid splits for this leg
|
|
127
|
-
if (selected.length > 0) {
|
|
128
|
-
leg.idealSplit = Math.round(selected.reduce(sum) / selected.length);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// only if there are valid splits for this leg
|
|
132
|
-
if (leg.runners.length > 0) {
|
|
133
|
-
leg.fastestSplit = leg.runners[0].split;
|
|
134
|
-
leg.runners[0].splitBehind = '0:00';
|
|
135
|
-
leg.runners.slice(1).forEach(runner => {
|
|
136
|
-
runner.splitBehind = runner.split - leg.fastestSplit;
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
leg.runners[0].splitRank = 1;
|
|
140
|
-
leg.runners.forEach((runner, idx, arr) => {
|
|
141
|
-
if (idx > 0) {
|
|
142
|
-
if (runner.split === arr[idx - 1].split) {
|
|
143
|
-
runner.splitRank = arr[idx - 1].splitRank;
|
|
144
|
-
} else {
|
|
145
|
-
runner.splitRank = idx + 1;
|
|
146
|
-
}
|
|
147
|
-
runner.performanceIndex = Math.round(1.0 * leg.idealSplit / runner.split * 100);
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Calculates the weight of each leg regarding the passed in idealTime.
|
|
156
|
-
*
|
|
157
|
-
* @param {*} legs
|
|
158
|
-
* @param {*} idealTime
|
|
159
|
-
*/
|
|
160
|
-
function defineLegWeight(legs, idealTime) {
|
|
161
|
-
Object.keys(legs).forEach((code) => {
|
|
162
|
-
let leg = legs[code];
|
|
163
|
-
leg.weight = leg.idealSplit / idealTime;
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function legCode(splits, idx) {
|
|
168
|
-
if (idx === 0) {
|
|
169
|
-
return 'St-' + splits[0].code;
|
|
170
|
-
} else {
|
|
171
|
-
return splits[idx - 1].code + '-' + splits[idx].code;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Assigns the following property to every valid leg of a runner.
|
|
177
|
-
*
|
|
178
|
-
* - splitBehind: how much time behind the faster runner
|
|
179
|
-
* - splitRank: rank on corresponding leg
|
|
180
|
-
* - leg: code of leg
|
|
181
|
-
*
|
|
182
|
-
* @param {*} runners
|
|
183
|
-
* @param {*} legs
|
|
184
|
-
*/
|
|
185
|
-
function assignLegInfoToSplits(runners, legs) {
|
|
186
|
-
runners.forEach(runner => {
|
|
187
|
-
runner.splits.filter(s => validTime(s.split)).forEach((split, idx) => {
|
|
188
|
-
let code = legCode(runner.splits, idx);
|
|
189
|
-
let leg = legs[code];
|
|
190
|
-
|
|
191
|
-
if (!leg) {
|
|
192
|
-
throw 'leg with code ' + code + ' not defined!';
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
split.leg = leg.code;
|
|
196
|
-
|
|
197
|
-
let legRunner = leg.runners.find(r => r.id === runner.id);
|
|
198
|
-
|
|
199
|
-
split.idealBehind = legRunner ? legRunner.split - leg.idealSplit : '-';
|
|
200
|
-
split.supermanBehind = legRunner ? legRunner.split - leg.fastestSplit : '-';
|
|
201
|
-
split.splitBehind = legRunner ? legRunner.splitBehind : '-';
|
|
202
|
-
split.splitRank = legRunner ? legRunner.splitRank : null;
|
|
203
|
-
split.performanceIndex = legRunner ? legRunner.performanceIndex : null;
|
|
204
|
-
split.weight = leg.weight;
|
|
205
|
-
});
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function rank(runners) {
|
|
210
|
-
runners.forEach((runner, idx) => {
|
|
211
|
-
if (idx === 0) {
|
|
212
|
-
runner.rank = 1;
|
|
213
|
-
} else {
|
|
214
|
-
let prev = runners[idx - 1];
|
|
215
|
-
if (prev.time === runner.time) {
|
|
216
|
-
runner.rank = prev.rank;
|
|
217
|
-
} else if (parseTime(runner.time)) {
|
|
218
|
-
runner.rank = idx + 1;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
class RankingBuilder {
|
|
225
|
-
|
|
226
|
-
constructor(runners) {
|
|
227
|
-
runners.forEach((runner, idx) => {
|
|
228
|
-
for (let i = 0; i < runner.splits.length; i++) {
|
|
229
|
-
let split = runner.splits[i];
|
|
230
|
-
if (i === 0 && validTime(split.time)) {
|
|
231
|
-
split.split = parseTime(split.time);
|
|
232
|
-
} else if (i > 0) {
|
|
233
|
-
let previousSplit = runner.splits[i - 1];
|
|
234
|
-
if (validTime(split.time) && validTime(previousSplit.time)) {
|
|
235
|
-
split.split = parseTime(split.time) - parseTime(previousSplit.time);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
this.courses = defineCourses(runners);
|
|
242
|
-
this.legs = defineLegs(runners);
|
|
243
|
-
this.runners = defineRunners(runners);
|
|
244
|
-
|
|
245
|
-
defineLegProperties(this.legs);
|
|
246
|
-
|
|
247
|
-
// calculate the ideal time [s]
|
|
248
|
-
this.idealTime = Object.keys(this.legs).map(code => this.legs[code].idealSplit).reduce(sum);
|
|
249
|
-
|
|
250
|
-
// each leg's weight is calculated regarding as a ratio of the ideal split time to the ideal time
|
|
251
|
-
defineLegWeight(this.legs, this.idealTime);
|
|
252
|
-
|
|
253
|
-
// now assing the leg information (such as idealTime, weight, ...) to the individual splits of the runners
|
|
254
|
-
assignLegInfoToSplits(this.runners, this.legs);
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
this.runners.forEach(runner => {
|
|
258
|
-
let behind = 0;
|
|
259
|
-
let supermanBehind = 0;
|
|
260
|
-
let weightSum = 0;
|
|
261
|
-
runner.splits.forEach(split => {
|
|
262
|
-
behind += split.idealBehind;
|
|
263
|
-
supermanBehind += split.supermanBehind;
|
|
264
|
-
split.overallIdealBehind = behind;
|
|
265
|
-
split.overallSupermanBehind = supermanBehind;
|
|
266
|
-
split.position = weightSum;
|
|
267
|
-
|
|
268
|
-
weightSum += split.weight;
|
|
269
|
-
});
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
// function of function calculating the time at an arbitrary position for a given runner
|
|
273
|
-
let timeFn = (runner) => (pos) => {
|
|
274
|
-
if (pos === 0) {
|
|
275
|
-
return 0;
|
|
276
|
-
} else if (pos >= 1) {
|
|
277
|
-
return parseTime(runner.time);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
let idx = 0;
|
|
281
|
-
let weightSum = 0;
|
|
282
|
-
for (idx = 0; idx < runner.splits.length; idx++) {
|
|
283
|
-
let split = runner.splits[idx];
|
|
284
|
-
if (weightSum + split.weight > pos) {
|
|
285
|
-
break;
|
|
286
|
-
}
|
|
287
|
-
weightSum += split.weight;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
let prev = runner.splits[idx - 1];
|
|
291
|
-
let next = runner.splits[idx];
|
|
292
|
-
|
|
293
|
-
let prevTime = parseTime(prev.time);
|
|
294
|
-
let nextTime = parseTime(next.time);
|
|
295
|
-
return prevTime + (pos - prev.position) / (next.position - prev.position) * (nextTime - prevTime);
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
let timesAtPosition = (pos) => {
|
|
299
|
-
return this.runners.map(runner => { return { id: runner.id, time: timeFn(runner)(pos) }; });
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
this.runners.forEach(runner => {
|
|
303
|
-
runner.splits.forEach(split => {
|
|
304
|
-
let times = timesAtPosition(split.position + split.weight).filter(entry => entry.time !== null && entry.time > 0);
|
|
305
|
-
times.sort((t1, t2) => t1.time - t2.time);
|
|
306
|
-
|
|
307
|
-
let rank = 1;
|
|
308
|
-
let fastestTime = times[0].time;
|
|
309
|
-
|
|
310
|
-
let lastTime = parseTime(times[0].time);
|
|
311
|
-
for (let idx = 0; idx < times.length; idx++) {
|
|
312
|
-
let entry = times[idx];
|
|
313
|
-
|
|
314
|
-
if (lastTime < parseTime(entry.time)) {
|
|
315
|
-
rank = idx;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
if (runner.id === entry.id) {
|
|
319
|
-
split.overallRank = rank;
|
|
320
|
-
split.overallBehind = formatTime(entry.time - fastestTime);
|
|
321
|
-
break;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
});
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
console.log(this.runners[1]);
|
|
328
|
-
|
|
329
|
-
// calculate the overall rank
|
|
330
|
-
rank(this.runners);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
getCourses() {
|
|
334
|
-
return this.courses;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
getLeg(code) {
|
|
338
|
-
return this.legs[code];
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
getLegs() {
|
|
342
|
-
return Object.keys(this.legs).map(code => this.legs[code]);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
getRanking() {
|
|
346
|
-
let runners = this.runners.map(runner => { return { ...runner } });
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
/*
|
|
350
|
-
|
|
351
|
-
for each runner we need the following function
|
|
352
|
-
|
|
353
|
-
f(pos) => timeBehindIdeal
|
|
354
|
-
|
|
355
|
-
using this function it will be possible to calculate the following properties
|
|
356
|
-
|
|
357
|
-
f(runner, split) => {
|
|
358
|
-
overallRank // the rank the current position
|
|
359
|
-
idealBehind // time behind ideal
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
the first function
|
|
363
|
-
|
|
364
|
-
*/
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
module.exports.RankingBuilder = RankingBuilder;
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
/*
|
|
372
|
-
/* depends on ordered courses!
|
|
373
|
-
Object.keys(results.legs).forEach((code, idx) => {
|
|
374
|
-
let leg = result.legs[code];
|
|
375
|
-
if (idx === 0) {
|
|
376
|
-
leg.fastestTime = formatTime(leg.fastestSplit);
|
|
377
|
-
leg.idealTime = formatTime(leg.idealSplit);
|
|
378
|
-
} else {
|
|
379
|
-
leg.fastestTime = formatTime(parseTime(result.legs[idx - 1].fastestTime) + leg.fastestSplit);
|
|
380
|
-
leg.idealTime = formatTime(parseTime(result.legs[idx - 1].idealTime) + leg.idealSplit);
|
|
381
|
-
}
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
result.runners.forEach(function(runner) {
|
|
385
|
-
// calculate overall time behind leader
|
|
386
|
-
runner.splits.forEach(function(split, splitIdx) {
|
|
387
|
-
if (!invalidTime(split.time)) {
|
|
388
|
-
let leader = result.runners.map(function(r) {
|
|
389
|
-
return {
|
|
390
|
-
time: r.splits[splitIdx].time,
|
|
391
|
-
rank: r.splits[splitIdx].overallRank
|
|
392
|
-
};
|
|
393
|
-
}).find(function(split) {
|
|
394
|
-
return split.rank === 1;
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
// no leader for this leg?!
|
|
398
|
-
if (leader) {
|
|
399
|
-
let leaderTime = leader.time;
|
|
400
|
-
if (parseTime(split.time) !== null) {
|
|
401
|
-
split.overallBehind = formatTime(parseTime(split.time) - parseTime(leaderTime));
|
|
402
|
-
split.fastestBehind = formatTime(parseTime(split.time) - parseTime(result.legs[splitIdx].fastestTime));
|
|
403
|
-
split.idealBehind = formatTime(parseTime(split.time) - parseTime(result.legs[splitIdx].idealTime));
|
|
404
|
-
} else {
|
|
405
|
-
split.overallBehind = '-';
|
|
406
|
-
split.fastestBehind = '-';
|
|
407
|
-
split.idealBehind = '-';
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
// performance index for split
|
|
414
|
-
runner.splits.filter(split => split.split != '-' && split.split != 's' && split.split != '0:00').forEach(split => {
|
|
415
|
-
let leg = findLeg(legs, split.leg);
|
|
416
|
-
split.perfidx = Math.round(1.0 * leg.idealSplit / parseTime(split.split) * 100);
|
|
417
|
-
});
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
return result;
|
|
421
|
-
}
|
|
422
|
-
*/
|