@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.
Files changed (61) hide show
  1. package/README.md +27 -6
  2. package/dist/bgs-global-stats.d.ts +2 -0
  3. package/dist/bgs-global-stats.js.map +1 -1
  4. package/dist/build-battlegrounds-hero-stats-new.d.ts +5 -2
  5. package/dist/build-battlegrounds-hero-stats-new.js +261 -122
  6. package/dist/build-battlegrounds-hero-stats-new.js.map +1 -1
  7. package/dist/common.d.ts +14 -0
  8. package/dist/common.js +48 -0
  9. package/dist/common.js.map +1 -0
  10. package/dist/internal-model.d.ts +71 -0
  11. package/dist/internal-model.js +3 -0
  12. package/dist/internal-model.js.map +1 -0
  13. package/dist/quests-v2/bgs-quest-stat.d.ts +40 -0
  14. package/dist/quests-v2/bgs-quest-stat.js +3 -0
  15. package/dist/quests-v2/bgs-quest-stat.js.map +1 -0
  16. package/dist/quests-v2/charged-stat.d.ts +4 -0
  17. package/dist/quests-v2/charged-stat.js +3 -0
  18. package/dist/quests-v2/charged-stat.js.map +1 -0
  19. package/dist/quests-v2/data-filter.d.ts +8 -0
  20. package/dist/quests-v2/data-filter.js +21 -0
  21. package/dist/quests-v2/data-filter.js.map +1 -0
  22. package/dist/quests-v2/quests-v2.d.ts +4 -0
  23. package/dist/quests-v2/quests-v2.js +28 -0
  24. package/dist/quests-v2/quests-v2.js.map +1 -0
  25. package/dist/quests-v2/stats-buikder.d.ts +3 -0
  26. package/dist/quests-v2/stats-buikder.js +80 -0
  27. package/dist/quests-v2/stats-buikder.js.map +1 -0
  28. package/dist/quests.d.ts +3 -0
  29. package/dist/quests.js +60 -0
  30. package/dist/quests.js.map +1 -0
  31. package/dist/retrieve-quest-stats.d.ts +8 -0
  32. package/dist/retrieve-quest-stats.js +98 -0
  33. package/dist/retrieve-quest-stats.js.map +1 -0
  34. package/dist/stats-v2/bgs-hero-stat.d.ts +21 -0
  35. package/dist/stats-v2/bgs-hero-stat.js +3 -0
  36. package/dist/stats-v2/bgs-hero-stat.js.map +1 -0
  37. package/dist/stats-v2/stats-buikder.d.ts +4 -0
  38. package/dist/stats-v2/stats-buikder.js +40 -0
  39. package/dist/stats-v2/stats-buikder.js.map +1 -0
  40. package/dist/stats-v2/stats-v2.d.ts +5 -0
  41. package/dist/stats-v2/stats-v2.js +25 -0
  42. package/dist/stats-v2/stats-v2.js.map +1 -0
  43. package/dist/utils/util-functions.d.ts +3 -7
  44. package/dist/utils/util-functions.js +43 -59
  45. package/dist/utils/util-functions.js.map +1 -1
  46. package/package.json +6 -4
  47. package/dist/db/rds-bgs.d.ts +0 -3
  48. package/dist/db/rds-bgs.js +0 -53
  49. package/dist/db/rds-bgs.js.map +0 -1
  50. package/dist/db/rds.d.ts +0 -3
  51. package/dist/db/rds.js +0 -53
  52. package/dist/db/rds.js.map +0 -1
  53. package/dist/db/s3.d.ts +0 -12
  54. package/dist/db/s3.js +0 -142
  55. package/dist/db/s3.js.map +0 -1
  56. package/dist/retrieve-bgs-global-stats.d.ts +0 -3
  57. package/dist/retrieve-bgs-global-stats.js +0 -173
  58. package/dist/retrieve-bgs-global-stats.js.map +0 -1
  59. package/dist/test.d.ts +0 -2
  60. package/dist/test.js +0 -27
  61. 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;CAK3B;AALD,0CAKC;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}\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"]}
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
- declare const _default: (event: any) => Promise<any>;
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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
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 rds_1 = require("./db/rds");
15
- const s3_1 = require("./db/s3");
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
- const s3 = new s3_1.S3();
15
+ exports.s3 = new aws_lambda_utils_1.S3();
18
16
  const allCards = new reference_data_1.AllCardsService();
19
- exports.default = (event) => __awaiter(void 0, void 0, void 0, function* () {
20
- yield exports.handleNewStats();
21
- });
22
- exports.handleNewStats = () => __awaiter(void 0, void 0, void 0, function* () {
23
- var _a, _b;
24
- yield allCards.initializeCardsDb('20211104');
25
- const lastPatch = yield getLastBattlegroundsPatch();
26
- const mysql = yield rds_1.getConnection();
27
- const rows = yield loadRows(mysql, lastPatch);
28
- const mmrPercentiles = buildMmrPercentiles(rows);
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
- console.log('all tribes', allTribes);
31
- const tribePermutations = [null, ...combine(allTribes, 5)];
32
- console.log('tribe permutations, should be 56, because 8 tribes', tribePermutations.length);
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
- console.log('handling tribes', tribes);
36
- const tribesStr = !!((_a = tribes) === null || _a === void 0 ? void 0 : _a.length) ? tribes.join(',') : null;
37
- const statsForTribes = {
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 stringResults = JSON.stringify(statsForTribes);
44
- const gzippedResults = zlib_1.gzipSync(stringResults);
45
- yield s3.writeFile(gzippedResults, 'static.zerotoheroes.com', `api/bgs/bgs-global-stats-${!!((_b = tribes) === null || _b === void 0 ? void 0 : _b.length) ? tribes.join('-') : 'all-tribes'}.gz.json`, 'application/json', 'gzip');
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
- yield mysql.end();
48
- return { statusCode: 200, body: null };
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, lastPatch, mmrPercentiles, tribesStr) => {
77
- return mmrPercentiles
78
- .map(mmrPercentile => [mmrPercentile, rows.filter(row => row.rating >= mmrPercentile.mmr)])
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
- return buildHeroesForMmr(rows, lastPatch, tribesStr).map(stat => (Object.assign(Object.assign({}, stat), { mmrPercentile: mmr.percentile })));
81
- })
82
- .map(info => {
83
- return info;
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 buildHeroesForMmr = (rows, lastPatch, tribesStr) => {
88
- const rowsWithTribes = !!tribesStr
89
- ? rows.filter(row => !!row.tribes).filter(row => row.tribes === tribesStr)
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
- date: period,
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
- for (const row of rows) {
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
- console.error('Could not parse combat winrate', row.id, e);
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 buildPlacementDistribution = (rows) => {
197
- const placementDistribution = [];
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 * FROM bgs_run_stats
207
- WHERE creationDate > DATE_SUB(NOW(), INTERVAL 30 DAY)
363
+ SELECT *
364
+ FROM bgs_run_stats
365
+ WHERE creationDate > DATE_SUB(NOW(), INTERVAL 30 DAY);
208
366
  `;
209
- console.log('running query', query);
210
- const rows = yield mysql.query(query);
211
- console.log('rows', (_c = rows) === null || _c === void 0 ? void 0 : _c.length, rows[0]);
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
- const getLastBattlegroundsPatch = () => __awaiter(void 0, void 0, void 0, function* () {
217
- const patchInfo = yield util_functions_1.http(`https://static.zerotoheroes.com/hearthstone/data/patches.json`);
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