summaryrefslogtreecommitdiff
path: root/src/ReportService.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/ReportService.js')
-rw-r--r--src/ReportService.js111
1 files changed, 83 insertions, 28 deletions
diff --git a/src/ReportService.js b/src/ReportService.js
index e0bd299..8bd9514 100644
--- a/src/ReportService.js
+++ b/src/ReportService.js
@@ -1,9 +1,10 @@
import * as maps from './maps/maps';
-import * as metadata from '../data/78f0edf0/metadata.json';
+import * as metadata from '../data/2d73896c/metadata.json';
const POPULAR_THRESHOLD = 1.0; // percent
+const PICKS_THRESHOLD = 200; // picks
-const report = require('../data/78f0edf0/report.json')
+const report = require('../data/2d73896c/report.json')
.filter((entry) => entry.Actor != undefined); // bad data from API downtime
const reports = new Map();
@@ -14,45 +15,95 @@ const topWins = new Map();
const topRareWins = new Map();
const topEpicWins = new Map();
const topLegendaryWins = new Map();
-const topLeveledUp = new Map();
-const topLeveledDown = new Map();
+const topLowLevel = new Map();
+const topScaling = new Map();
const topUnpopularWins = new Map();
let totalMatches = 0;
let actors = [];
const modes = metadata.config.api.modes;
for(let mode of modes) {
- const relevancy = (entry) => (entry.Count / totalPicks.get(mode)) * entry.Winner;
+ const relevancy = (entry) => (entry.TotalPicks / totalPicks.get(mode)) * entry.TotalWinner;
const playersPerMatch = maps.playersPerMatch(mode);
- reports.set(mode, report.filter((entry) => entry.Mode == mode));
- totalPicks.set(mode, report.map((entry) => entry.Count).reduce((agg, cur) => agg + cur, 0));
+ const reportFilterMode = report.filter((entry) => entry.Mode == mode);
+
+ const baseLevelForEntry = (entry) => reportFilterMode
+ .filter((entry2) => entry2.Actor == entry.Actor &&
+ entry2.Talent == entry.Talent)
+ .map((entry2) => entry2.LevelBucket)
+ .reduce((agg, cur) => cur < agg? cur : agg);
+
+ const reportFilterModeBaseLevel = reportFilterMode.filter((entry) => entry.LevelBucket == baseLevelForEntry(entry));
+
+ // using the "base level" (bucket 0) report, add information about diff to 'NoTalent' and scaling
+ const reportFilterModeRegressed = reportFilterModeBaseLevel.map((entry) => {
+ const sum = (agg, cur) => agg + cur;
+ const range = (n) => [...Array(n).keys()];
+
+ // perform a weighted linear regression. x: level bucket (scaled 0 - 1) y: win rate weights: picks
+ const actorReport = reportFilterMode
+ .filter((entry2) =>
+ entry2.Actor == entry.Actor &&
+ entry2.Talent == entry.Talent &&
+ entry2.LevelBucket >= 0);
+ const buckets = actorReport.map((entry) => entry.LevelBucket).sort();
+ const findActorWithBucket = (bucket) => actorReport.find((entry) => entry.LevelBucket == bucket);
+ const weights = buckets.map((bucket) => findActorWithBucket(bucket).Count);
+ const sum_weights = weights.reduce(sum, 0);
+ const xs = [].concat(...buckets.map((bucket, index) => range(weights[index]).map((_) => bucket / metadata.config.self.levelBuckets)));
+ const ys = [].concat(...buckets.map((bucket, index) => range(weights[index]).map((_) => findActorWithBucket(bucket).Winner)));
+ const n = xs.length;
+ const sum_x = xs.reduce(sum, 0);
+ const sum_y = ys.reduce(sum, 0);
+ const sum_xy = xs.map((x, index) => x * ys[index]).reduce(sum, 0);
+ const sum_xx = xs.map((x) => x * x).reduce(sum, 0);
+ const sum_yy = ys.map((y) => y * y).reduce(sum, 0);
+
+ const slope = (n * sum_xy - sum_x * sum_y) / (n * sum_xx - sum_x * sum_x);
+ const intercept = (sum_y - slope * sum_x) / n;
+
+ return Object.assign({}, entry, {
+ TalentWinrateBase: intercept,
+ TalentWinrateScaling: slope, // to 1 = max level
+ TalentWinrateLevelScaling: slope / maps.getMaxLevel(entry),
+ TotalPicks: sum_weights || entry.Count, // NoTalent has no weights
+ TotalWinner: sum_y / sum_weights || entry.Winner,
+ SampleTooSmall: sum_weights < PICKS_THRESHOLD,
+ });
+ });
+ reports.set(mode, reportFilterModeRegressed);
+
+ totalPicks.set(mode, reports.get(mode)
+ .map((entry) => entry.TotalPicks)
+ .reduce((agg, cur) => agg + cur, 0));
totalMatches += totalPicks.get(mode) / maps.playersPerMatch(mode);
top10Relevancy.set(mode, reports.get(mode)
.sort((entry1, entry2) => relevancy(entry2) - relevancy(entry1))
.slice(0, 10));
- topPicks.set(mode, reports.get(mode).sort((entry1, entry2) => entry2.Count - entry1.Count)[0]);
+ topPicks.set(mode, reports.get(mode).sort((entry1, entry2) => entry2.TotalPicks - entry1.TotalPicks)[0]);
topWins.set(mode, reports.get(mode)
- .filter((entry) => 100 * playersPerMatch * entry.Count / totalPicks.get(mode) > POPULAR_THRESHOLD)
- .sort((entry1, entry2) => entry2.Winner - entry1.Winner)[0]);
+ .filter((entry) => 100 * playersPerMatch * entry.TotalPicks / totalPicks.get(mode) > POPULAR_THRESHOLD)
+ .sort((entry1, entry2) => entry2.TotalWinner - entry1.TotalWinner)[0]);
topUnpopularWins.set(mode, reports.get(mode)
- .filter((entry) => 100 * playersPerMatch * entry.Count / totalPicks.get(mode) <= POPULAR_THRESHOLD)
- .sort((entry1, entry2) => entry2.Winner - entry1.Winner)[0]);
+ .filter((entry) => 100 * playersPerMatch * entry.TotalPicks / totalPicks.get(mode) <= POPULAR_THRESHOLD &&
+ entry.TotalPicks > PICKS_THRESHOLD)
+ .sort((entry1, entry2) => entry2.TotalWinner - entry1.TotalWinner)[0]);
topRareWins.set(mode, reports.get(mode)
- .filter((entry) => maps.getTalentRarity(entry.Talent) == 'Rare' && 100 * playersPerMatch * entry.Count / totalPicks.get(mode))
- .sort((entry1, entry2) => entry2.Winner - entry1.Winner)[0]);
+ .filter((entry) => maps.getTalentRarity(entry.Talent) == 'Rare' && 100 * playersPerMatch * entry.TotalPicks / totalPicks.get(mode))
+ .sort((entry1, entry2) => entry2.TotalWinner - entry1.TotalWinner)[0]);
topEpicWins.set(mode, reports.get(mode)
- .filter((entry) => maps.getTalentRarity(entry.Talent) == 'Epic' && 100 * playersPerMatch * entry.Count / totalPicks.get(mode))
- .sort((entry1, entry2) => entry2.Winner - entry1.Winner)[0]);
+ .filter((entry) => maps.getTalentRarity(entry.Talent) == 'Epic' && 100 * playersPerMatch * entry.TotalPicks / totalPicks.get(mode))
+ .sort((entry1, entry2) => entry2.TotalWinner - entry1.TotalWinner)[0]);
topLegendaryWins.set(mode, reports.get(mode)
- .filter((entry) => maps.getTalentRarity(entry.Talent) == 'Legendary' && 100 * playersPerMatch * entry.Count / totalPicks.get(mode))
- .sort((entry1, entry2) => entry2.Winner - entry1.Winner)[0]);
- topLeveledUp.set(mode, reports.get(mode)
- .filter((entry) => maps.getTalentRarity(entry.Talent) != 'None')
- .sort((entry1, entry2) => maps.getScaledLevel(entry2) - maps.getScaledLevel(entry1))[0]);
- topLeveledDown.set(mode, reports.get(mode)
- .filter((entry) => maps.getTalentRarity(entry.Talent) != 'None')
- .sort((entry1, entry2) => maps.getScaledLevel(entry1) - maps.getScaledLevel(entry2))[0]);
+ .filter((entry) => maps.getTalentRarity(entry.Talent) == 'Legendary' && 100 * playersPerMatch * entry.TotalPicks / totalPicks.get(mode))
+ .sort((entry1, entry2) => entry2.TotalWinner - entry1.TotalWinner)[0]);
+ topLowLevel.set(mode, reports.get(mode)
+ .filter((entry) => !entry.SampleTooSmall)
+ .sort((entry1, entry2) => entry2.TalentWinrateBase - entry1.TalentWinrateBase)[0]);
+ topScaling.set(mode, reports.get(mode)
+ .filter((entry) => !entry.SampleTooSmall)
+ .sort((entry1, entry2) => entry2.TalentWinrateLevelScaling - entry1.TalentWinrateLevelScaling)[0]);
if (actors.length == 0) {
actors = [...new Set(reports.get(mode).map((entry) => entry.Actor))];
@@ -76,12 +127,12 @@ export default {
return top10Relevancy.get(mode);
},
- getHighestLevelAvg(mode) {
- return topLeveledUp.get(mode);
+ getTopLowLevel(mode) {
+ return topLowLevel.get(mode);
},
- getLowestLevelAvg(mode) {
- return topLeveledDown.get(mode);
+ getTopScaling(mode) {
+ return topScaling.get(mode);
},
getTopWin(mode) {
@@ -123,4 +174,8 @@ export default {
getLastUpdate() {
return metadata.lastUpdate;
},
+
+ getLevelBuckets() {
+ return metadata.config.self.levelBuckets;
+ },
};