@firestone-hs/bgs-global-stats 1.0.25 → 1.0.31
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/README.md +27 -6
- package/dist/bgs-global-stats.d.ts +2 -0
- package/dist/bgs-global-stats.js.map +1 -1
- package/dist/build-battlegrounds-hero-stats-new.d.ts +5 -2
- package/dist/build-battlegrounds-hero-stats-new.js +261 -122
- package/dist/build-battlegrounds-hero-stats-new.js.map +1 -1
- package/dist/common.d.ts +14 -0
- package/dist/common.js +48 -0
- package/dist/common.js.map +1 -0
- package/dist/internal-model.d.ts +71 -0
- package/dist/internal-model.js +3 -0
- package/dist/internal-model.js.map +1 -0
- package/dist/quests-v2/bgs-quest-stat.d.ts +40 -0
- package/dist/quests-v2/bgs-quest-stat.js +3 -0
- package/dist/quests-v2/bgs-quest-stat.js.map +1 -0
- package/dist/quests-v2/charged-stat.d.ts +4 -0
- package/dist/quests-v2/charged-stat.js +3 -0
- package/dist/quests-v2/charged-stat.js.map +1 -0
- package/dist/quests-v2/data-filter.d.ts +8 -0
- package/dist/quests-v2/data-filter.js +21 -0
- package/dist/quests-v2/data-filter.js.map +1 -0
- package/dist/quests-v2/quests-v2.d.ts +4 -0
- package/dist/quests-v2/quests-v2.js +28 -0
- package/dist/quests-v2/quests-v2.js.map +1 -0
- package/dist/quests-v2/stats-buikder.d.ts +3 -0
- package/dist/quests-v2/stats-buikder.js +80 -0
- package/dist/quests-v2/stats-buikder.js.map +1 -0
- package/dist/quests.d.ts +3 -0
- package/dist/quests.js +60 -0
- package/dist/quests.js.map +1 -0
- package/dist/retrieve-quest-stats.d.ts +8 -0
- package/dist/retrieve-quest-stats.js +98 -0
- package/dist/retrieve-quest-stats.js.map +1 -0
- package/dist/stats-v2/bgs-hero-stat.d.ts +21 -0
- package/dist/stats-v2/bgs-hero-stat.js +3 -0
- package/dist/stats-v2/bgs-hero-stat.js.map +1 -0
- package/dist/stats-v2/stats-buikder.d.ts +4 -0
- package/dist/stats-v2/stats-buikder.js +40 -0
- package/dist/stats-v2/stats-buikder.js.map +1 -0
- package/dist/stats-v2/stats-v2.d.ts +5 -0
- package/dist/stats-v2/stats-v2.js +25 -0
- package/dist/stats-v2/stats-v2.js.map +1 -0
- package/dist/utils/util-functions.d.ts +3 -7
- package/dist/utils/util-functions.js +43 -59
- package/dist/utils/util-functions.js.map +1 -1
- package/package.json +6 -4
- package/dist/db/rds-bgs.d.ts +0 -3
- package/dist/db/rds-bgs.js +0 -53
- package/dist/db/rds-bgs.js.map +0 -1
- package/dist/db/rds.d.ts +0 -3
- package/dist/db/rds.js +0 -53
- package/dist/db/rds.js.map +0 -1
- package/dist/db/s3.d.ts +0 -12
- package/dist/db/s3.js +0 -142
- package/dist/db/s3.js.map +0 -1
- package/dist/retrieve-bgs-global-stats.d.ts +0 -3
- package/dist/retrieve-bgs-global-stats.js +0 -173
- package/dist/retrieve-bgs-global-stats.js.map +0 -1
- package/dist/test.d.ts +0 -2
- package/dist/test.js +0 -27
- package/dist/test.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
# Test it
|
|
2
|
-
|
|
3
|
-
```
|
|
4
|
-
npm run build && sam local invoke -t template.yaml -e event.json BuildBgsHeroStatsFunction
|
|
5
|
-
```
|
|
6
|
-
|
|
7
1
|
# Deploy
|
|
8
2
|
|
|
9
3
|
```
|
|
@@ -12,6 +6,33 @@ npm run build && npm run package && npm run deploy
|
|
|
12
6
|
rm -rf dist && tsc && rm -rf dist/node_modules && npm publish --access=public
|
|
13
7
|
```
|
|
14
8
|
|
|
9
|
+
```
|
|
10
|
+
$ curl https://static-api.firestoneapp.com/bgs-quests?questCardId=BG24_Quest_313\&heroCardId=BG20_HERO_101\&difficulty=6\&tribes=14,18,20,43,92\&mmrPercentile=100
|
|
11
|
+
```
|
|
12
|
+
|
|
15
13
|
# Reference
|
|
16
14
|
|
|
17
15
|
Used this project as template: https://github.com/alukach/aws-sam-typescript-boilerplate
|
|
16
|
+
|
|
17
|
+
# Random notes for Quests
|
|
18
|
+
|
|
19
|
+
what do we want to get?
|
|
20
|
+
|
|
21
|
+
- average turns to complete for given hero / tribes (and current MMR bracket)
|
|
22
|
+
- how much the current hero influences the turns to complete (i.e. delta vs average of heroes)
|
|
23
|
+
- how much the current tribes influence the turns to complete (i.e. delta vs all tribes). Maybe also highlight the tribes that have the best influence, as these are probably tribes you should focus on? Maybe this is too obvious?
|
|
24
|
+
|
|
25
|
+
stat: {
|
|
26
|
+
globalData;
|
|
27
|
+
difficulty: <difficult, globalDataForDifficulty, difficultyImpact>
|
|
28
|
+
heroes: <hero, globalDataForHero>
|
|
29
|
+
tribes: <tribe, globalDataForTribe, raceAverageTurnToCompleteImpact, raceCompletionRateImpact>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
in game: - for each quest - extract quest + difficulty - get turnsToComplete + completionRate for hero - get raceImpact for turnsToComplete + completionRate for each tribe - get difficultyImpact for tTC + cR for the difficulty. - this will tell me that "on average, difficulty 4 is + 0.5 ttc vs average difficulty) - build average turn to complete for hero + tribes (using the race impacts) + difficulty (using the average delta for difficulty. That way we can use the global average for the hero, and use the difficulty delta to update this data. The assumption behind this is that the difficulty impact all heroes the same way, which a priori seems fair, since the difficulty is just a quota to get)
|
|
33
|
+
|
|
34
|
+
globalData: averageTurnToComplete (when completed), completionRate
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```
|
|
@@ -30,6 +30,7 @@ export declare class BgsGlobalStats2 {
|
|
|
30
30
|
readonly mmrPercentiles: readonly MmrPercentile[];
|
|
31
31
|
readonly heroStats: readonly BgsGlobalHeroStat2[];
|
|
32
32
|
readonly allTribes: readonly Race[];
|
|
33
|
+
readonly totalMatches: number;
|
|
33
34
|
}
|
|
34
35
|
export declare class BgsGlobalHeroStat2 {
|
|
35
36
|
readonly date: 'all-time' | 'past-three' | 'past-seven' | 'last-patch';
|
|
@@ -56,3 +57,4 @@ export interface MmrPercentile {
|
|
|
56
57
|
readonly mmr: number;
|
|
57
58
|
readonly percentile: 100 | 50 | 25 | 10 | 1;
|
|
58
59
|
}
|
|
60
|
+
export * from './stats-v2/bgs-hero-stat';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bgs-global-stats.js","sourceRoot":"/","sources":["bgs-global-stats.ts"],"names":[],"mappings":";;AAEA,MAAa,cAAc;CAG1B;AAHD,wCAGC;AAED,MAAa,iBAAiB;CAW7B;AAXD,8CAWC;AAOD,MAAa,eAAe;
|
|
1
|
+
{"version":3,"file":"bgs-global-stats.js","sourceRoot":"/","sources":["bgs-global-stats.ts"],"names":[],"mappings":";;AAEA,MAAa,cAAc;CAG1B;AAHD,wCAGC;AAED,MAAa,iBAAiB;CAW7B;AAXD,8CAWC;AAOD,MAAa,eAAe;CAM3B;AAND,0CAMC;AAED,MAAa,kBAAkB;CAc9B;AAdD,gDAcC","sourcesContent":["import { Race } from '@firestone-hs/reference-data';\r\n\r\nexport class BgsGlobalStats {\r\n\tlastUpdateDate: string;\r\n\theroStats: readonly BgsGlobalHeroStat[];\r\n}\r\n\r\nexport class BgsGlobalHeroStat {\r\n\tid: string;\r\n\tpopularity: number;\r\n\taveragePosition: number;\r\n\ttop4: number;\r\n\ttop1: number;\r\n\ttier: BgsHeroTier;\r\n\ttotalGames: number;\r\n\ttribesStat: readonly { tribe: string; percent: number }[];\r\n\twarbandStats: readonly { turn: number; totalStats: number }[];\r\n\tcombatWinrate: readonly { turn: number; winrate: number }[];\r\n}\r\n\r\nexport type BgsHeroTier = 'S' | 'A' | 'B' | 'C' | 'D';\r\n\r\n// ===============================\r\n// New stats\r\n// =================================\r\nexport class BgsGlobalStats2 {\r\n\treadonly lastUpdateDate: string;\r\n\treadonly mmrPercentiles: readonly MmrPercentile[];\r\n\treadonly heroStats: readonly BgsGlobalHeroStat2[];\r\n\treadonly allTribes: readonly Race[];\r\n\treadonly totalMatches: number;\r\n}\r\n\r\nexport class BgsGlobalHeroStat2 {\r\n\t// The filters\r\n\treadonly date: 'all-time' | 'past-three' | 'past-seven' | 'last-patch';\r\n\treadonly mmrPercentile: 100 | 50 | 25 | 10 | 1;\r\n\treadonly cardId: string;\r\n\treadonly tribes: readonly Race[];\r\n\r\n\t// The values\r\n\treadonly totalMatches: number;\r\n\treadonly placementDistribution: readonly { rank: number; totalMatches: number }[];\r\n\t// To get the actual winrate, you will have to divide the totalWinrate by the dataPoints\r\n\treadonly combatWinrate: readonly { turn: number; dataPoints: number; totalWinrate: number }[];\r\n\t// Same\r\n\treadonly warbandStats: readonly { turn: number; dataPoints: number; totalStats: number }[];\r\n}\r\n\r\nexport interface MmrPercentile {\r\n\treadonly mmr: number;\r\n\treadonly percentile: 100 | 50 | 25 | 10 | 1;\r\n}\r\n\r\nexport * from './stats-v2/bgs-hero-stat';\r\n"]}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import { S3 } from '@firestone-hs/aws-lambda-utils';
|
|
2
|
+
import { Context } from 'aws-lambda';
|
|
3
|
+
export declare const s3: S3;
|
|
4
|
+
declare const _default: (event: any, context: Context) => Promise<any>;
|
|
2
5
|
export default _default;
|
|
3
|
-
export declare const handleNewStats: () => Promise<{
|
|
6
|
+
export declare const handleNewStats: (event: any, context: Context) => Promise<{
|
|
4
7
|
statusCode: number;
|
|
5
8
|
body: any;
|
|
6
9
|
}>;
|
|
@@ -1,52 +1,235 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
4
|
};
|
|
11
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const aws_lambda_utils_1 = require("@firestone-hs/aws-lambda-utils");
|
|
12
7
|
const reference_data_1 = require("@firestone-hs/reference-data");
|
|
8
|
+
const aws_sdk_1 = __importDefault(require("aws-sdk"));
|
|
13
9
|
const zlib_1 = require("zlib");
|
|
14
|
-
const
|
|
15
|
-
const
|
|
10
|
+
const common_1 = require("./common");
|
|
11
|
+
const quests_1 = require("./quests");
|
|
12
|
+
const quests_v2_1 = require("./quests-v2/quests-v2");
|
|
13
|
+
const stats_v2_1 = require("./stats-v2/stats-v2");
|
|
16
14
|
const util_functions_1 = require("./utils/util-functions");
|
|
17
|
-
|
|
15
|
+
exports.s3 = new aws_lambda_utils_1.S3();
|
|
18
16
|
const allCards = new reference_data_1.AllCardsService();
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
17
|
+
const lambda = new aws_sdk_1.default.Lambda();
|
|
18
|
+
const allTimePeriods = [
|
|
19
|
+
'all-time',
|
|
20
|
+
'past-three',
|
|
21
|
+
'past-seven',
|
|
22
|
+
'last-patch',
|
|
23
|
+
];
|
|
24
|
+
exports.default = async (event, context) => {
|
|
25
|
+
await exports.handleNewStats(event, context);
|
|
26
|
+
};
|
|
27
|
+
exports.handleNewStats = async (event, context) => {
|
|
28
|
+
var _a, _b, _c, _d;
|
|
29
|
+
const cleanup = aws_lambda_utils_1.logBeforeTimeout(context);
|
|
30
|
+
aws_lambda_utils_1.logger.log('event', event);
|
|
31
|
+
await allCards.initializeCardsDb();
|
|
32
|
+
const lastPatch = await getLastBattlegroundsPatch();
|
|
33
|
+
if (event.quests) {
|
|
34
|
+
const rows = await readRowsFromS3();
|
|
35
|
+
aws_lambda_utils_1.logger.log('read rows', (_a = rows) === null || _a === void 0 ? void 0 : _a.length);
|
|
36
|
+
for (const timePeriod of allTimePeriods) {
|
|
37
|
+
await quests_1.handleQuests(timePeriod, rows, lastPatch);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else if (event.questsV2) {
|
|
41
|
+
const rows = await readRowsFromS3();
|
|
42
|
+
aws_lambda_utils_1.logger.log('read rows', (_b = rows) === null || _b === void 0 ? void 0 : _b.length);
|
|
43
|
+
for (const timePeriod of allTimePeriods) {
|
|
44
|
+
await quests_v2_1.handleQuestsV2(timePeriod, rows, lastPatch);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else if (event.statsV2) {
|
|
48
|
+
const rows = await readRowsFromS3();
|
|
49
|
+
aws_lambda_utils_1.logger.log('read rows', (_c = rows) === null || _c === void 0 ? void 0 : _c.length);
|
|
50
|
+
for (const timePeriod of allTimePeriods) {
|
|
51
|
+
await stats_v2_1.handleStatsV2(timePeriod, rows, lastPatch, allCards);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else if (event.permutation) {
|
|
55
|
+
const rows = await readRowsFromS3();
|
|
56
|
+
aws_lambda_utils_1.logger.log('read rows', (_d = rows) === null || _d === void 0 ? void 0 : _d.length);
|
|
57
|
+
for (const timePeriod of allTimePeriods) {
|
|
58
|
+
await handlePermutation(event.permutation, timePeriod, event.allTribes, rows, lastPatch);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
const mysql = await aws_lambda_utils_1.getConnection();
|
|
63
|
+
const rows = await loadRows(mysql);
|
|
64
|
+
await mysql.end();
|
|
65
|
+
await saveRowsOnS3(rows);
|
|
66
|
+
await dispatchNewLambdas(rows, context);
|
|
67
|
+
await dispatchQuestsLambda(rows, context);
|
|
68
|
+
await dispatchQuestsV2Lambda(rows, context);
|
|
69
|
+
await dispatchStatsV2Lambda(rows, context);
|
|
70
|
+
}
|
|
71
|
+
cleanup();
|
|
72
|
+
return { statusCode: 200, body: null };
|
|
73
|
+
};
|
|
74
|
+
const dispatchQuestsLambda = async (rows, context) => {
|
|
75
|
+
const newEvent = {
|
|
76
|
+
quests: true,
|
|
77
|
+
};
|
|
78
|
+
const params = {
|
|
79
|
+
FunctionName: context.functionName,
|
|
80
|
+
InvocationType: 'Event',
|
|
81
|
+
LogType: 'Tail',
|
|
82
|
+
Payload: JSON.stringify(newEvent),
|
|
83
|
+
};
|
|
84
|
+
aws_lambda_utils_1.logger.log('\tinvoking lambda', params);
|
|
85
|
+
const result = await lambda
|
|
86
|
+
.invoke({
|
|
87
|
+
FunctionName: context.functionName,
|
|
88
|
+
InvocationType: 'Event',
|
|
89
|
+
LogType: 'Tail',
|
|
90
|
+
Payload: JSON.stringify(newEvent),
|
|
91
|
+
})
|
|
92
|
+
.promise();
|
|
93
|
+
aws_lambda_utils_1.logger.log('\tinvocation result', result);
|
|
94
|
+
};
|
|
95
|
+
const dispatchQuestsV2Lambda = async (rows, context) => {
|
|
96
|
+
const newEvent = {
|
|
97
|
+
questsV2: true,
|
|
98
|
+
};
|
|
99
|
+
const params = {
|
|
100
|
+
FunctionName: context.functionName,
|
|
101
|
+
InvocationType: 'Event',
|
|
102
|
+
LogType: 'Tail',
|
|
103
|
+
Payload: JSON.stringify(newEvent),
|
|
104
|
+
};
|
|
105
|
+
aws_lambda_utils_1.logger.log('\tinvoking lambda', params);
|
|
106
|
+
const result = await lambda
|
|
107
|
+
.invoke({
|
|
108
|
+
FunctionName: context.functionName,
|
|
109
|
+
InvocationType: 'Event',
|
|
110
|
+
LogType: 'Tail',
|
|
111
|
+
Payload: JSON.stringify(newEvent),
|
|
112
|
+
})
|
|
113
|
+
.promise();
|
|
114
|
+
aws_lambda_utils_1.logger.log('\tinvocation result', result);
|
|
115
|
+
};
|
|
116
|
+
const dispatchStatsV2Lambda = async (rows, context) => {
|
|
117
|
+
const newEvent = {
|
|
118
|
+
statsV2: true,
|
|
119
|
+
};
|
|
120
|
+
const params = {
|
|
121
|
+
FunctionName: context.functionName,
|
|
122
|
+
InvocationType: 'Event',
|
|
123
|
+
LogType: 'Tail',
|
|
124
|
+
Payload: JSON.stringify(newEvent),
|
|
125
|
+
};
|
|
126
|
+
aws_lambda_utils_1.logger.log('\tinvoking lambda', params);
|
|
127
|
+
const result = await lambda
|
|
128
|
+
.invoke({
|
|
129
|
+
FunctionName: context.functionName,
|
|
130
|
+
InvocationType: 'Event',
|
|
131
|
+
LogType: 'Tail',
|
|
132
|
+
Payload: JSON.stringify(newEvent),
|
|
133
|
+
})
|
|
134
|
+
.promise();
|
|
135
|
+
aws_lambda_utils_1.logger.log('\tinvocation result', result);
|
|
136
|
+
};
|
|
137
|
+
const dispatchNewLambdas = async (rows, context) => {
|
|
29
138
|
const allTribes = extractAllTribes(rows);
|
|
30
|
-
|
|
31
|
-
const tribePermutations = [
|
|
32
|
-
|
|
33
|
-
console.log('tribe permutations', tribePermutations);
|
|
139
|
+
aws_lambda_utils_1.logger.log('all tribes', allTribes);
|
|
140
|
+
const tribePermutations = ['all', ...combine(allTribes, 5)];
|
|
141
|
+
aws_lambda_utils_1.logger.log('tribe permutations, should be 127 (126 + 1), because 9 tribes', tribePermutations.length);
|
|
34
142
|
for (const tribes of tribePermutations) {
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
lastUpdateDate: util_functions_1.formatDate(new Date()),
|
|
39
|
-
mmrPercentiles: mmrPercentiles,
|
|
40
|
-
heroStats: buildHeroes(rows, lastPatch, mmrPercentiles, tribesStr),
|
|
143
|
+
aws_lambda_utils_1.logger.log('handling tribes', tribes, tribes !== 'all' && tribes.join('-'));
|
|
144
|
+
const newEvent = {
|
|
145
|
+
permutation: tribes,
|
|
41
146
|
allTribes: allTribes,
|
|
42
147
|
};
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
148
|
+
const params = {
|
|
149
|
+
FunctionName: context.functionName,
|
|
150
|
+
InvocationType: 'Event',
|
|
151
|
+
LogType: 'Tail',
|
|
152
|
+
Payload: JSON.stringify(newEvent),
|
|
153
|
+
};
|
|
154
|
+
aws_lambda_utils_1.logger.log('\tinvoking lambda', params);
|
|
155
|
+
const result = await lambda
|
|
156
|
+
.invoke({
|
|
157
|
+
FunctionName: context.functionName,
|
|
158
|
+
InvocationType: 'Event',
|
|
159
|
+
LogType: 'Tail',
|
|
160
|
+
Payload: JSON.stringify(newEvent),
|
|
161
|
+
})
|
|
162
|
+
.promise();
|
|
163
|
+
aws_lambda_utils_1.logger.log('\tinvocation result', result);
|
|
46
164
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
165
|
+
};
|
|
166
|
+
const handlePermutation = async (tribes, timePeriod, allTribes, rows, lastPatch) => {
|
|
167
|
+
var _a, _b, _c;
|
|
168
|
+
console.log('total rows', rows.length);
|
|
169
|
+
const rowsForTimePeriod = common_1.filterRowsForTimePeriod(rows, timePeriod, lastPatch);
|
|
170
|
+
console.log('rows for time period', rowsForTimePeriod.length);
|
|
171
|
+
const tribesStr = tribes === 'all' ? null : tribes.join(',');
|
|
172
|
+
const rowsWithTribes = !!tribesStr
|
|
173
|
+
? rowsForTimePeriod.filter(row => !!row.tribes).filter(row => row.tribes === tribesStr)
|
|
174
|
+
: rowsForTimePeriod;
|
|
175
|
+
console.log('rowsWithTribes', rowsWithTribes.length);
|
|
176
|
+
const mmrPercentiles = common_1.buildMmrPercentiles(rowsWithTribes);
|
|
177
|
+
aws_lambda_utils_1.logger.log('handling permutation', tribes, timePeriod, (_a = rows) === null || _a === void 0 ? void 0 : _a.length, (_b = rowsWithTribes) === null || _b === void 0 ? void 0 : _b.length);
|
|
178
|
+
const stats = buildHeroes(rowsWithTribes, mmrPercentiles).map(stat => ({
|
|
179
|
+
...stat,
|
|
180
|
+
tribes: tribes === 'all' ? allTribes : tribes,
|
|
181
|
+
date: timePeriod,
|
|
182
|
+
}));
|
|
183
|
+
const statsForTribes = {
|
|
184
|
+
lastUpdateDate: util_functions_1.formatDate(new Date()),
|
|
185
|
+
mmrPercentiles: mmrPercentiles,
|
|
186
|
+
heroStats: stats,
|
|
187
|
+
allTribes: allTribes,
|
|
188
|
+
totalMatches: stats.map(s => s.totalMatches).reduce((a, b) => a + b, 0),
|
|
189
|
+
};
|
|
190
|
+
aws_lambda_utils_1.logger.log('\tbuilt stats', statsForTribes.totalMatches, (_c = statsForTribes.heroStats) === null || _c === void 0 ? void 0 : _c.length);
|
|
191
|
+
const tribesSuffix = tribes === 'all' ? 'all-tribes' : tribes.join('-');
|
|
192
|
+
const timeSuffix = timePeriod;
|
|
193
|
+
await exports.s3.writeFile(zlib_1.gzipSync(JSON.stringify(statsForTribes)), 'static.zerotoheroes.com', `api/bgs/heroes/bgs-global-stats-${tribesSuffix}-${timeSuffix}.gz.json`, 'application/json', 'gzip');
|
|
194
|
+
};
|
|
195
|
+
const saveRowsOnS3 = async (rows) => {
|
|
196
|
+
aws_lambda_utils_1.logger.log('saving rows on s3', rows.length);
|
|
197
|
+
await exports.s3.writeArrayAsMultipart(rows, 'static.zerotoheroes.com', `api/bgs/working-rows.json`, 'application/json');
|
|
198
|
+
aws_lambda_utils_1.logger.log('file saved');
|
|
199
|
+
};
|
|
200
|
+
const readRowsFromS3 = async () => {
|
|
201
|
+
return new Promise((resolve, reject) => {
|
|
202
|
+
let parseErrors = 0;
|
|
203
|
+
let totalParsed = 0;
|
|
204
|
+
const stream = exports.s3.readStream('static.zerotoheroes.com', `api/bgs/working-rows.json`);
|
|
205
|
+
const result = [];
|
|
206
|
+
let previousString = '';
|
|
207
|
+
stream
|
|
208
|
+
.on('data', chunk => {
|
|
209
|
+
const str = Buffer.from(chunk).toString('utf-8');
|
|
210
|
+
const newStr = previousString + str;
|
|
211
|
+
const split = newStr.split('\n');
|
|
212
|
+
const rows = split.slice(0, split.length - 1).map(row => {
|
|
213
|
+
try {
|
|
214
|
+
const result = JSON.parse(row);
|
|
215
|
+
totalParsed++;
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
catch (e) {
|
|
219
|
+
parseErrors++;
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
previousString = split[split.length - 1];
|
|
223
|
+
result.push(...rows);
|
|
224
|
+
})
|
|
225
|
+
.on('end', () => {
|
|
226
|
+
const finalResult = result.filter(row => !!row);
|
|
227
|
+
aws_lambda_utils_1.logger.log('stream end', result.length, finalResult.length);
|
|
228
|
+
aws_lambda_utils_1.logger.log('parsing errors', parseErrors, 'and successes', totalParsed);
|
|
229
|
+
resolve(finalResult);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
};
|
|
50
233
|
const combine = (input, chooseN) => {
|
|
51
234
|
const finalResult = [];
|
|
52
235
|
const intermediateResult = [];
|
|
@@ -73,51 +256,43 @@ const extractAllTribes = (rows) => {
|
|
|
73
256
|
.reduce((a, b) => [...new Set(a.concat(b))], [])),
|
|
74
257
|
];
|
|
75
258
|
};
|
|
76
|
-
const buildHeroes = (rows,
|
|
77
|
-
|
|
78
|
-
|
|
259
|
+
const buildHeroes = (rows, mmrPercentiles) => {
|
|
260
|
+
const mappedByMmr = mmrPercentiles.map(mmrPercentile => [
|
|
261
|
+
mmrPercentile,
|
|
262
|
+
rows.filter(row => mmrPercentile.percentile === 100 || row.rating >= mmrPercentile.mmr),
|
|
263
|
+
]);
|
|
264
|
+
return mappedByMmr
|
|
79
265
|
.map(([mmr, rows]) => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
266
|
+
aws_lambda_utils_1.logger.log('building heroes for mmr', mmr.percentile, rows.length);
|
|
267
|
+
return buildHeroStats(rows).map(stat => ({
|
|
268
|
+
...stat,
|
|
269
|
+
mmrPercentile: mmr.percentile,
|
|
270
|
+
}));
|
|
84
271
|
})
|
|
85
272
|
.reduce((a, b) => [...a, ...b], []);
|
|
86
273
|
};
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
: rows;
|
|
91
|
-
const allTimeHeroes = buildHeroStats(rowsWithTribes, 'all-time', tribesStr);
|
|
92
|
-
const lastPatchHeroes = buildHeroStats(rowsWithTribes.filter(row => row.buildNumber >= lastPatch.number ||
|
|
93
|
-
row.creationDate > new Date(new Date(lastPatch.date).getTime() + 24 * 60 * 60 * 1000)), 'last-patch', tribesStr);
|
|
94
|
-
const threeDaysHeroes = buildHeroStats(rowsWithTribes.filter(row => row.creationDate >= new Date(new Date().getTime() - 3 * 24 * 60 * 60 * 1000)), 'past-three', tribesStr);
|
|
95
|
-
const sevenDaysHeroes = buildHeroStats(rowsWithTribes.filter(row => row.creationDate >= new Date(new Date().getTime() - 7 * 24 * 60 * 60 * 1000)), 'past-seven', tribesStr);
|
|
96
|
-
return [...allTimeHeroes, ...lastPatchHeroes, ...threeDaysHeroes, ...sevenDaysHeroes];
|
|
97
|
-
};
|
|
98
|
-
const buildHeroStats = (rows, period, tribesStr) => {
|
|
99
|
-
const grouped = !!tribesStr
|
|
100
|
-
? util_functions_1.groupByFunction((row) => `${row.heroCardId}-${row.tribes}-${row.darkmoonPrizes}`)(rows)
|
|
101
|
-
: util_functions_1.groupByFunction((row) => `${row.heroCardId}-${row.darkmoonPrizes}`)(rows);
|
|
102
|
-
return Object.values(grouped).map(groupedRows => {
|
|
274
|
+
const buildHeroStats = (rows) => {
|
|
275
|
+
const grouped = aws_lambda_utils_1.groupByFunction((row) => util_functions_1.normalizeHeroCardId(row.heroCardId, allCards))(rows);
|
|
276
|
+
const result = Object.values(grouped).map(groupedRows => {
|
|
103
277
|
const ref = groupedRows[0];
|
|
104
|
-
const placementDistribution = buildPlacementDistribution(groupedRows);
|
|
278
|
+
const placementDistribution = common_1.buildPlacementDistribution(groupedRows);
|
|
105
279
|
const combatWinrate = buildCombatWinrate(groupedRows);
|
|
106
280
|
const warbandStats = buildWarbandStats(groupedRows);
|
|
107
281
|
return {
|
|
108
|
-
|
|
109
|
-
cardId: ref.heroCardId,
|
|
282
|
+
cardId: util_functions_1.normalizeHeroCardId(ref.heroCardId, allCards),
|
|
110
283
|
totalMatches: groupedRows.length,
|
|
111
284
|
placementDistribution: placementDistribution,
|
|
112
285
|
combatWinrate: combatWinrate,
|
|
113
286
|
warbandStats: warbandStats,
|
|
114
287
|
};
|
|
115
288
|
});
|
|
289
|
+
return result;
|
|
116
290
|
};
|
|
117
291
|
const buildWarbandStats = (rows) => {
|
|
118
292
|
var _a, _b, _c;
|
|
119
293
|
const data = {};
|
|
120
|
-
|
|
294
|
+
const validRows = rows.filter(row => row.id > 5348374);
|
|
295
|
+
for (const row of validRows) {
|
|
121
296
|
if (!((_a = row.warbandStats) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
122
297
|
continue;
|
|
123
298
|
}
|
|
@@ -126,7 +301,10 @@ const buildWarbandStats = (rows) => {
|
|
|
126
301
|
continue;
|
|
127
302
|
}
|
|
128
303
|
for (const turnInfo of parsed) {
|
|
129
|
-
if (turnInfo.turn === 0 || turnInfo.totalStats == null) {
|
|
304
|
+
if (turnInfo.turn === 0 || turnInfo.totalStats == null || isNaN(turnInfo.totalStats)) {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
if (turnInfo.totalStats > 20000) {
|
|
130
308
|
continue;
|
|
131
309
|
}
|
|
132
310
|
const existingInfo = (_c = data['' + turnInfo.turn], (_c !== null && _c !== void 0 ? _c : { dataPoints: 0, totalStats: 0 }));
|
|
@@ -159,88 +337,49 @@ const buildCombatWinrate = (rows) => {
|
|
|
159
337
|
}
|
|
160
338
|
}
|
|
161
339
|
catch (e) {
|
|
162
|
-
|
|
340
|
+
aws_lambda_utils_1.logger.error('Could not parse combat winrate', row.id, e);
|
|
163
341
|
continue;
|
|
164
342
|
}
|
|
165
|
-
if (debug) {
|
|
166
|
-
}
|
|
167
343
|
for (const turnInfo of parsed) {
|
|
168
344
|
if (turnInfo.turn === 0 || turnInfo.winrate == null) {
|
|
169
345
|
continue;
|
|
170
346
|
}
|
|
171
|
-
if (debug) {
|
|
172
|
-
}
|
|
173
347
|
const existingInfo = (_c = data['' + turnInfo.turn], (_c !== null && _c !== void 0 ? _c : { dataPoints: 0, totalWinrate: 0 }));
|
|
174
|
-
if (debug) {
|
|
175
|
-
}
|
|
176
348
|
existingInfo.dataPoints = existingInfo.dataPoints + 1;
|
|
177
349
|
existingInfo.totalWinrate = existingInfo.totalWinrate + Math.round(turnInfo.winrate);
|
|
178
|
-
if (debug) {
|
|
179
|
-
}
|
|
180
350
|
data['' + turnInfo.turn] = existingInfo;
|
|
181
|
-
if (debug) {
|
|
182
|
-
}
|
|
183
351
|
}
|
|
184
352
|
}
|
|
185
|
-
if (debug) {
|
|
186
|
-
}
|
|
187
353
|
const result = Object.keys(data).map(turn => ({
|
|
188
354
|
turn: +turn,
|
|
189
355
|
dataPoints: data[turn].dataPoints,
|
|
190
356
|
totalWinrate: data[turn].totalWinrate,
|
|
191
357
|
}));
|
|
192
|
-
if (debug) {
|
|
193
|
-
}
|
|
194
358
|
return result;
|
|
195
359
|
};
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
const groupedByPlacement = util_functions_1.groupByFunction((res) => '' + res.rank)(rows);
|
|
199
|
-
Object.keys(groupedByPlacement).forEach(placement => placementDistribution.push({ rank: +placement, totalMatches: groupedByPlacement[placement].length }));
|
|
200
|
-
return placementDistribution;
|
|
201
|
-
};
|
|
202
|
-
const loadRows = (mysql, patch) => __awaiter(void 0, void 0, void 0, function* () {
|
|
203
|
-
var _c;
|
|
204
|
-
console.log('loading rows', patch);
|
|
360
|
+
const loadRows = async (mysql) => {
|
|
361
|
+
var _a;
|
|
205
362
|
const query = `
|
|
206
|
-
SELECT *
|
|
207
|
-
|
|
363
|
+
SELECT *
|
|
364
|
+
FROM bgs_run_stats
|
|
365
|
+
WHERE creationDate > DATE_SUB(NOW(), INTERVAL 30 DAY);
|
|
208
366
|
`;
|
|
209
|
-
|
|
210
|
-
const rows =
|
|
211
|
-
|
|
367
|
+
aws_lambda_utils_1.logger.log('running query', query);
|
|
368
|
+
const rows = await mysql.query(query);
|
|
369
|
+
aws_lambda_utils_1.logger.log('rows', (_a = rows) === null || _a === void 0 ? void 0 : _a.length, rows[0]);
|
|
212
370
|
return rows
|
|
213
371
|
.filter(row => row.heroCardId.startsWith('TB_BaconShop_') || row.heroCardId.startsWith('BG'))
|
|
214
|
-
.filter(row => row.heroCardId !== "TB_BaconShop_HERO_59t"
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
372
|
+
.filter(row => row.heroCardId !== "TB_BaconShop_HERO_59t" &&
|
|
373
|
+
row.heroCardId !== "BG22_HERO_007t")
|
|
374
|
+
.map(row => ({
|
|
375
|
+
...row,
|
|
376
|
+
heroCardId: util_functions_1.normalizeHeroCardId(row.heroCardId, allCards),
|
|
377
|
+
}));
|
|
378
|
+
};
|
|
379
|
+
const getLastBattlegroundsPatch = async () => {
|
|
380
|
+
const patchInfo = await aws_lambda_utils_1.http(`https://static.zerotoheroes.com/hearthstone/data/patches.json`);
|
|
218
381
|
const structuredPatch = JSON.parse(patchInfo);
|
|
219
382
|
const patchNumber = structuredPatch.currentBattlegroundsMetaPatch;
|
|
220
383
|
return structuredPatch.patches.find(patch => patch.number === patchNumber);
|
|
221
|
-
});
|
|
222
|
-
const buildMmrPercentiles = (rows) => {
|
|
223
|
-
const sortedMmrs = rows.map(row => row.rating).sort((a, b) => a - b);
|
|
224
|
-
const median = sortedMmrs[Math.floor(sortedMmrs.length / 2)];
|
|
225
|
-
const top25 = sortedMmrs[Math.floor((sortedMmrs.length / 4) * 3)];
|
|
226
|
-
const top10 = sortedMmrs[Math.floor((sortedMmrs.length / 10) * 9)];
|
|
227
|
-
return [
|
|
228
|
-
{
|
|
229
|
-
percentile: 100,
|
|
230
|
-
mmr: 0,
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
percentile: 50,
|
|
234
|
-
mmr: median,
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
percentile: 25,
|
|
238
|
-
mmr: top25,
|
|
239
|
-
},
|
|
240
|
-
{
|
|
241
|
-
percentile: 10,
|
|
242
|
-
mmr: top10,
|
|
243
|
-
},
|
|
244
|
-
];
|
|
245
384
|
};
|
|
246
385
|
//# sourceMappingURL=build-battlegrounds-hero-stats-new.js.map
|