@firestone-hs/bgs-global-stats 1.0.39 → 1.0.41

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 (106) hide show
  1. package/dist/aggregate-hourly/config.d.ts +2 -0
  2. package/dist/aggregate-hourly/config.js +7 -0
  3. package/dist/aggregate-hourly/config.js.map +1 -0
  4. package/dist/aggregate-hourly/heroes/_build-aggregated-stats.d.ts +5 -0
  5. package/dist/aggregate-hourly/heroes/_build-aggregated-stats.js +69 -0
  6. package/dist/aggregate-hourly/heroes/_build-aggregated-stats.js.map +1 -0
  7. package/dist/aggregate-hourly/heroes/s3-saver.d.ts +2 -0
  8. package/dist/aggregate-hourly/heroes/s3-saver.js +22 -0
  9. package/dist/aggregate-hourly/heroes/s3-saver.js.map +1 -0
  10. package/dist/aggregate-hourly/heroes/stats-merger.d.ts +3 -0
  11. package/dist/aggregate-hourly/heroes/stats-merger.js +132 -0
  12. package/dist/aggregate-hourly/heroes/stats-merger.js.map +1 -0
  13. package/dist/aggregate-hourly/hourly-utils.d.ts +4 -0
  14. package/dist/aggregate-hourly/hourly-utils.js +33 -0
  15. package/dist/aggregate-hourly/hourly-utils.js.map +1 -0
  16. package/dist/aggregate-hourly/percentiles.d.ts +3 -0
  17. package/dist/aggregate-hourly/percentiles.js +8 -0
  18. package/dist/aggregate-hourly/percentiles.js.map +1 -0
  19. package/dist/aggregate-hourly/quests/_build-aggregated-stats.d.ts +5 -0
  20. package/dist/aggregate-hourly/quests/_build-aggregated-stats.js +70 -0
  21. package/dist/aggregate-hourly/quests/_build-aggregated-stats.js.map +1 -0
  22. package/dist/aggregate-hourly/quests/quest-stats-merger.d.ts +4 -0
  23. package/dist/aggregate-hourly/quests/quest-stats-merger.js +99 -0
  24. package/dist/aggregate-hourly/quests/quest-stats-merger.js.map +1 -0
  25. package/dist/aggregate-hourly/quests/reward-stats-merger.d.ts +4 -0
  26. package/dist/aggregate-hourly/quests/reward-stats-merger.js +65 -0
  27. package/dist/aggregate-hourly/quests/reward-stats-merger.js.map +1 -0
  28. package/dist/aggregate-hourly/quests/s3-saver.d.ts +3 -0
  29. package/dist/aggregate-hourly/quests/s3-saver.js +27 -0
  30. package/dist/aggregate-hourly/quests/s3-saver.js.map +1 -0
  31. package/dist/aggregate-hourly/s3-loader.d.ts +6 -0
  32. package/dist/aggregate-hourly/s3-loader.js +21 -0
  33. package/dist/aggregate-hourly/s3-loader.js.map +1 -0
  34. package/dist/{utils → common}/util-functions.d.ts +2 -0
  35. package/dist/{utils → common}/util-functions.js +13 -10
  36. package/dist/common/util-functions.js.map +1 -0
  37. package/dist/hourly/_build-battlegrounds-hero-stats.d.ts +14 -0
  38. package/dist/hourly/_build-battlegrounds-hero-stats.js +98 -0
  39. package/dist/hourly/_build-battlegrounds-hero-stats.js.map +1 -0
  40. package/dist/hourly/builders.d.ts +11 -0
  41. package/dist/hourly/builders.js +81 -0
  42. package/dist/hourly/builders.js.map +1 -0
  43. package/dist/hourly/hero-stats-buikder.d.ts +4 -0
  44. package/dist/hourly/hero-stats-buikder.js +61 -0
  45. package/dist/hourly/hero-stats-buikder.js.map +1 -0
  46. package/dist/hourly/hero-stats.d.ts +3 -0
  47. package/dist/hourly/hero-stats.js +27 -0
  48. package/dist/hourly/hero-stats.js.map +1 -0
  49. package/dist/hourly/quest-stats.d.ts +4 -0
  50. package/dist/hourly/quest-stats.js +40 -0
  51. package/dist/hourly/quest-stats.js.map +1 -0
  52. package/dist/hourly/quests/quest-stats-buikder.d.ts +4 -0
  53. package/dist/{quests-v2/quests-stats-buikder.js → hourly/quests/quest-stats-buikder.js} +8 -12
  54. package/dist/hourly/quests/quest-stats-buikder.js.map +1 -0
  55. package/dist/hourly/quests/reward-stats-builder.d.ts +4 -0
  56. package/dist/{quests-v2/rewards-stats-buikder.js → hourly/quests/reward-stats-builder.js} +7 -9
  57. package/dist/hourly/quests/reward-stats-builder.js.map +1 -0
  58. package/dist/hourly/rows.d.ts +4 -0
  59. package/dist/hourly/rows.js +175 -0
  60. package/dist/hourly/rows.js.map +1 -0
  61. package/dist/{common.d.ts → hourly/utils.d.ts} +3 -12
  62. package/dist/hourly/utils.js +56 -0
  63. package/dist/hourly/utils.js.map +1 -0
  64. package/dist/internal-model.d.ts +2 -52
  65. package/dist/internal-model.js.map +1 -1
  66. package/dist/{quests-v2/bgs-quest-stat.d.ts → model-quests.d.ts} +1 -2
  67. package/dist/{quests-v2/charged-stat.js → model-quests.js} +1 -1
  68. package/dist/{quests-v2/bgs-quest-stat.js.map → model-quests.js.map} +1 -1
  69. package/dist/{stats-v2/bgs-hero-stat.d.ts → models.d.ts} +38 -5
  70. package/dist/{stats-v2/bgs-hero-stat.js → models.js} +1 -1
  71. package/dist/models.js.map +1 -0
  72. package/dist/public-api.d.ts +2 -0
  73. package/dist/{bgs-global-stats.js → public-api.js} +3 -16
  74. package/dist/public-api.js.map +1 -0
  75. package/package.json +7 -7
  76. package/dist/bgs-global-stats.d.ts +0 -61
  77. package/dist/bgs-global-stats.js.map +0 -1
  78. package/dist/build-battlegrounds-hero-stats-new.d.ts +0 -20
  79. package/dist/build-battlegrounds-hero-stats-new.js +0 -360
  80. package/dist/build-battlegrounds-hero-stats-new.js.map +0 -1
  81. package/dist/common.js +0 -62
  82. package/dist/common.js.map +0 -1
  83. package/dist/quests-v2/bgs-quest-stat.js +0 -3
  84. package/dist/quests-v2/charged-stat.d.ts +0 -4
  85. package/dist/quests-v2/charged-stat.js.map +0 -1
  86. package/dist/quests-v2/data-filter.d.ts +0 -8
  87. package/dist/quests-v2/data-filter.js +0 -23
  88. package/dist/quests-v2/data-filter.js.map +0 -1
  89. package/dist/quests-v2/quests-stats-buikder.d.ts +0 -3
  90. package/dist/quests-v2/quests-stats-buikder.js.map +0 -1
  91. package/dist/quests-v2/quests-v2.d.ts +0 -4
  92. package/dist/quests-v2/quests-v2.js +0 -33
  93. package/dist/quests-v2/quests-v2.js.map +0 -1
  94. package/dist/quests-v2/rewards-stats-buikder.d.ts +0 -3
  95. package/dist/quests-v2/rewards-stats-buikder.js.map +0 -1
  96. package/dist/quests.d.ts +0 -3
  97. package/dist/quests.js +0 -62
  98. package/dist/quests.js.map +0 -1
  99. package/dist/stats-v2/bgs-hero-stat.js.map +0 -1
  100. package/dist/stats-v2/stats-buikder.d.ts +0 -4
  101. package/dist/stats-v2/stats-buikder.js +0 -110
  102. package/dist/stats-v2/stats-buikder.js.map +0 -1
  103. package/dist/stats-v2/stats-v2.d.ts +0 -5
  104. package/dist/stats-v2/stats-v2.js +0 -27
  105. package/dist/stats-v2/stats-v2.js.map +0 -1
  106. package/dist/utils/util-functions.js.map +0 -1
@@ -0,0 +1,2 @@
1
+ export declare const STAT_KEY_HERO: string;
2
+ export declare const STAT_KEY_QUEST: string;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.STAT_KEY_QUEST = exports.STAT_KEY_HERO = void 0;
4
+ const _build_battlegrounds_hero_stats_1 = require("../hourly/_build-battlegrounds-hero-stats");
5
+ exports.STAT_KEY_HERO = `${_build_battlegrounds_hero_stats_1.STATS_KEY_PREFIX}/hero-stats/mmr-%mmrPercentile%/%timePeriod%/overview-from-hourly.gz.json`;
6
+ exports.STAT_KEY_QUEST = `${_build_battlegrounds_hero_stats_1.STATS_KEY_PREFIX}/quest-stats/mmr-%mmrPercentile%/%timePeriod%/overview-from-hourly.gz.json`;
7
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/aggregate-hourly/config.ts"],"names":[],"mappings":";;;AAAA,+FAA6E;AAEhE,QAAA,aAAa,GAAG,GAAG,kDAAgB,2EAA2E,CAAC;AAC/G,QAAA,cAAc,GAAG,GAAG,kDAAgB,4EAA4E,CAAC","sourcesContent":["import { STATS_KEY_PREFIX } from '../hourly/_build-battlegrounds-hero-stats';\r\n\r\nexport const STAT_KEY_HERO = `${STATS_KEY_PREFIX}/hero-stats/mmr-%mmrPercentile%/%timePeriod%/overview-from-hourly.gz.json`;\r\nexport const STAT_KEY_QUEST = `${STATS_KEY_PREFIX}/quest-stats/mmr-%mmrPercentile%/%timePeriod%/overview-from-hourly.gz.json`;\r\n"]}
@@ -0,0 +1,5 @@
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>;
5
+ export default _default;
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.s3 = void 0;
7
+ const aws_lambda_utils_1 = require("@firestone-hs/aws-lambda-utils");
8
+ const reference_data_1 = require("@firestone-hs/reference-data");
9
+ const aws_sdk_1 = __importDefault(require("aws-sdk"));
10
+ const percentiles_1 = require("../percentiles");
11
+ const s3_loader_1 = require("../s3-loader");
12
+ const s3_saver_1 = require("./s3-saver");
13
+ const stats_merger_1 = require("./stats-merger");
14
+ const allCards = new reference_data_1.AllCardsService();
15
+ exports.s3 = new aws_lambda_utils_1.S3();
16
+ const lambda = new aws_sdk_1.default.Lambda();
17
+ exports.default = async (event, context) => {
18
+ await allCards.initializeCardsDb();
19
+ if (!event.timePeriod) {
20
+ await dispatchEvents(context);
21
+ return;
22
+ }
23
+ const cleanup = (0, aws_lambda_utils_1.logBeforeTimeout)(context);
24
+ const timePeriod = event.timePeriod;
25
+ const mmrPercentile = event.mmrPercentile;
26
+ const patchInfo = await (0, aws_lambda_utils_1.getLastBattlegroundsPatch)();
27
+ const hourlyData = await (0, s3_loader_1.loadHourlyDataFromS3)('hero', timePeriod, mmrPercentile, patchInfo);
28
+ const lastUpdate = hourlyData
29
+ .map((d) => ({
30
+ date: new Date(d.lastUpdateDate),
31
+ dateStr: d.lastUpdateDate,
32
+ time: new Date(d.lastUpdateDate).getTime(),
33
+ }))
34
+ .sort((a, b) => b.time - a.time)[0].date;
35
+ const mergedStats = (0, stats_merger_1.mergeStats)(hourlyData, mmrPercentile, allCards);
36
+ const mmrPercentiles = (0, percentiles_1.buildMmrPercentiles)(hourlyData);
37
+ const testHeroStat = mergedStats.find((stat) => stat.heroCardId === 'BG21_HERO_010');
38
+ await (0, s3_saver_1.persistData)(mergedStats, mmrPercentiles, lastUpdate, timePeriod, mmrPercentile);
39
+ cleanup();
40
+ };
41
+ const dispatchEvents = async (context) => {
42
+ console.log('dispatching events');
43
+ const allTimePeriod = ['all-time', 'past-three', 'past-seven', 'last-patch'];
44
+ const mmrPercentiles = [100, 50, 25, 10, 1];
45
+ for (const timePeriod of allTimePeriod) {
46
+ for (const percentile of mmrPercentiles) {
47
+ const newEvent = {
48
+ timePeriod: timePeriod,
49
+ mmrPercentile: percentile,
50
+ };
51
+ const params = {
52
+ FunctionName: context.functionName,
53
+ InvocationType: 'Event',
54
+ LogType: 'Tail',
55
+ Payload: JSON.stringify(newEvent),
56
+ };
57
+ const result = await lambda
58
+ .invoke({
59
+ FunctionName: context.functionName,
60
+ InvocationType: 'Event',
61
+ LogType: 'Tail',
62
+ Payload: JSON.stringify(newEvent),
63
+ })
64
+ .promise();
65
+ await (0, aws_lambda_utils_1.sleep)(50);
66
+ }
67
+ }
68
+ };
69
+ //# sourceMappingURL=_build-aggregated-stats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_build-aggregated-stats.js","sourceRoot":"","sources":["../../../src/aggregate-hourly/heroes/_build-aggregated-stats.ts"],"names":[],"mappings":"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","sourcesContent":["import { S3, getLastBattlegroundsPatch, logBeforeTimeout, sleep } from '@firestone-hs/aws-lambda-utils';\r\nimport { AllCardsService } from '@firestone-hs/reference-data';\r\nimport { Context } from 'aws-lambda';\r\nimport AWS from 'aws-sdk';\r\nimport { BgsGlobalHeroStat, BgsHeroStatsV2, MmrPercentile, MmrPercentileFilter, TimePeriod } from '../../models';\r\nimport { buildMmrPercentiles } from '../percentiles';\r\nimport { loadHourlyDataFromS3 } from '../s3-loader';\r\nimport { persistData } from './s3-saver';\r\nimport { mergeStats } from './stats-merger';\r\n\r\nconst allCards = new AllCardsService();\r\nexport const s3 = new S3();\r\nconst lambda = new AWS.Lambda();\r\n\r\nexport default async (event, context: Context): Promise<any> => {\r\n\tawait allCards.initializeCardsDb();\r\n\r\n\tif (!event.timePeriod) {\r\n\t\tawait dispatchEvents(context);\r\n\t\treturn;\r\n\t}\r\n\r\n\tconst cleanup = logBeforeTimeout(context);\r\n\tconst timePeriod: TimePeriod = event.timePeriod;\r\n\tconst mmrPercentile: MmrPercentileFilter = event.mmrPercentile;\r\n\r\n\t// console.log('aggregating data', timePeriod, mmrPercentile);\r\n\t// Build the list of files based on the timeframe, and load all of these\r\n\tconst patchInfo = await getLastBattlegroundsPatch();\r\n\tconst hourlyData: readonly BgsHeroStatsV2[] = await loadHourlyDataFromS3(\r\n\t\t'hero',\r\n\t\ttimePeriod,\r\n\t\tmmrPercentile,\r\n\t\tpatchInfo,\r\n\t);\r\n\t// console.log('hourlyData', hourlyData.length);\r\n\tconst lastUpdate = hourlyData\r\n\t\t.map((d) => ({\r\n\t\t\tdate: new Date(d.lastUpdateDate),\r\n\t\t\tdateStr: d.lastUpdateDate,\r\n\t\t\ttime: new Date(d.lastUpdateDate).getTime(),\r\n\t\t}))\r\n\t\t.sort((a, b) => b.time - a.time)[0].date;\r\n\t// console.log('loaded hourly data', lastUpdate);\r\n\r\n\t// Here it's ok that the MMR corresponding to each MMR percentile is not the same across all the hourly data chunks\r\n\t// as the actual MMR evolves over time\r\n\t// The only issue is that the samples might be too small for the MMR percentiles to be really representative in\r\n\t// each group\r\n\t// Empirically, it looks like there isn't much variation on a hour-by-hour basis, so it should be ok\r\n\t// A possible solution, if this becomes an issue, is to compute the MMR percentiles on the full last day of data\r\n\t// when building the hourly data\r\n\tconst mergedStats: readonly BgsGlobalHeroStat[] = mergeStats(hourlyData, mmrPercentile, allCards);\r\n\tconst mmrPercentiles: readonly MmrPercentile[] = buildMmrPercentiles(hourlyData);\r\n\t// console.log(\r\n\t// \t'mergedStats',\r\n\t// \tmergedStats?.map((s) => s.dataPoints).reduce((a, b) => a + b, 0),\r\n\t// \tmergedStats?.length,\r\n\t// );\r\n\tconst testHeroStat = mergedStats.find((stat) => stat.heroCardId === 'BG21_HERO_010');\r\n\t// console.log('testHeroStat', testHeroStat, testHeroStat?.combatWinrate);\r\n\r\n\tawait persistData(mergedStats, mmrPercentiles, lastUpdate, timePeriod, mmrPercentile);\r\n\tcleanup();\r\n};\r\n\r\nconst dispatchEvents = async (context: Context) => {\r\n\tconsole.log('dispatching events');\r\n\tconst allTimePeriod: readonly TimePeriod[] = ['all-time', 'past-three', 'past-seven', 'last-patch'];\r\n\tconst mmrPercentiles: readonly MmrPercentileFilter[] = [100, 50, 25, 10, 1];\r\n\tfor (const timePeriod of allTimePeriod) {\r\n\t\tfor (const percentile of mmrPercentiles) {\r\n\t\t\tconst newEvent = {\r\n\t\t\t\ttimePeriod: timePeriod,\r\n\t\t\t\tmmrPercentile: percentile,\r\n\t\t\t};\r\n\t\t\tconst params = {\r\n\t\t\t\tFunctionName: context.functionName,\r\n\t\t\t\tInvocationType: 'Event',\r\n\t\t\t\tLogType: 'Tail',\r\n\t\t\t\tPayload: JSON.stringify(newEvent),\r\n\t\t\t};\r\n\t\t\t// console.log('\\tinvoking lambda', params);\r\n\t\t\tconst result = await lambda\r\n\t\t\t\t.invoke({\r\n\t\t\t\t\tFunctionName: context.functionName,\r\n\t\t\t\t\tInvocationType: 'Event',\r\n\t\t\t\t\tLogType: 'Tail',\r\n\t\t\t\t\tPayload: JSON.stringify(newEvent),\r\n\t\t\t\t})\r\n\t\t\t\t.promise();\r\n\t\t\t// console.log('\\tinvocation result', result);\r\n\t\t\tawait sleep(50);\r\n\t\t}\r\n\t}\r\n};\r\n"]}
@@ -0,0 +1,2 @@
1
+ import { BgsGlobalHeroStat, MmrPercentile, MmrPercentileFilter, TimePeriod } from '../../models';
2
+ export declare const persistData: (mergedStats: readonly BgsGlobalHeroStat[], mmrPercentiles: readonly MmrPercentile[], lastUpdate: Date, timePeriod: TimePeriod, mmrPercentile: MmrPercentileFilter) => Promise<void>;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.persistData = void 0;
4
+ const zlib_1 = require("zlib");
5
+ const _build_battlegrounds_hero_stats_1 = require("../../hourly/_build-battlegrounds-hero-stats");
6
+ const config_1 = require("../config");
7
+ const _build_aggregated_stats_1 = require("./_build-aggregated-stats");
8
+ const persistData = async (mergedStats, mmrPercentiles, lastUpdate, timePeriod, mmrPercentile) => {
9
+ const stat = {
10
+ heroStats: mergedStats.map((stat) => ({
11
+ ...stat,
12
+ mmrPercentile: mmrPercentile,
13
+ timePeriod: timePeriod,
14
+ })),
15
+ lastUpdateDate: lastUpdate,
16
+ dataPoints: mergedStats.map((s) => s.dataPoints).reduce((a, b) => a + b, 0),
17
+ mmrPercentiles: mmrPercentiles,
18
+ };
19
+ await _build_aggregated_stats_1.s3.writeFile((0, zlib_1.gzipSync)(JSON.stringify(stat)), _build_battlegrounds_hero_stats_1.STATS_BUCKET, config_1.STAT_KEY_HERO.replace('%mmrPercentile%', `${mmrPercentile}`).replace('%timePeriod%', timePeriod), 'application/json', 'gzip');
20
+ };
21
+ exports.persistData = persistData;
22
+ //# sourceMappingURL=s3-saver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3-saver.js","sourceRoot":"","sources":["../../../src/aggregate-hourly/heroes/s3-saver.ts"],"names":[],"mappings":";;;AAAA,+BAAgC;AAChC,kGAA4E;AAE5E,sCAA0C;AAC1C,uEAA+C;AAExC,MAAM,WAAW,GAAG,KAAK,EAC/B,WAAyC,EACzC,cAAwC,EACxC,UAAgB,EAChB,UAAsB,EACtB,aAAkC,EACjC,EAAE;IACH,MAAM,IAAI,GAAmB;QAC5B,SAAS,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACrC,GAAG,IAAI;YACP,aAAa,EAAE,aAAa;YAC5B,UAAU,EAAE,UAAU;SACtB,CAAC,CAAC;QACH,cAAc,EAAE,UAAU;QAC1B,UAAU,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC3E,cAAc,EAAE,cAAc;KAC9B,CAAC;IAEF,MAAM,4BAAE,CAAC,SAAS,CACjB,IAAA,eAAQ,EAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,EAC9B,8CAAY,EACZ,sBAAa,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,cAAc,EAAE,UAAU,CAAC,EAChG,kBAAkB,EAClB,MAAM,CACN,CAAC;AACH,CAAC,CAAC;AAzBW,QAAA,WAAW,eAyBtB","sourcesContent":["import { gzipSync } from 'zlib';\r\nimport { STATS_BUCKET } from '../../hourly/_build-battlegrounds-hero-stats';\r\nimport { BgsGlobalHeroStat, BgsHeroStatsV2, MmrPercentile, MmrPercentileFilter, TimePeriod } from '../../models';\r\nimport { STAT_KEY_HERO } from '../config';\r\nimport { s3 } from './_build-aggregated-stats';\r\n\r\nexport const persistData = async (\r\n\tmergedStats: readonly BgsGlobalHeroStat[],\r\n\tmmrPercentiles: readonly MmrPercentile[],\r\n\tlastUpdate: Date,\r\n\ttimePeriod: TimePeriod,\r\n\tmmrPercentile: MmrPercentileFilter,\r\n) => {\r\n\tconst stat: BgsHeroStatsV2 = {\r\n\t\theroStats: mergedStats.map((stat) => ({\r\n\t\t\t...stat,\r\n\t\t\tmmrPercentile: mmrPercentile,\r\n\t\t\ttimePeriod: timePeriod,\r\n\t\t})),\r\n\t\tlastUpdateDate: lastUpdate,\r\n\t\tdataPoints: mergedStats.map((s) => s.dataPoints).reduce((a, b) => a + b, 0),\r\n\t\tmmrPercentiles: mmrPercentiles,\r\n\t};\r\n\t// console.log('persisting data', stat.dataPoints, stat.heroStats?.length);\r\n\tawait s3.writeFile(\r\n\t\tgzipSync(JSON.stringify(stat)),\r\n\t\tSTATS_BUCKET,\r\n\t\tSTAT_KEY_HERO.replace('%mmrPercentile%', `${mmrPercentile}`).replace('%timePeriod%', timePeriod),\r\n\t\t'application/json',\r\n\t\t'gzip',\r\n\t);\r\n};\r\n"]}
@@ -0,0 +1,3 @@
1
+ import { AllCardsService } from '@firestone-hs/reference-data';
2
+ import { BgsGlobalHeroStat, BgsHeroStatsV2, MmrPercentileFilter } from '../../models';
3
+ export declare const mergeStats: (hourlyData: readonly BgsHeroStatsV2[], mmrPercentile: MmrPercentileFilter, allCards: AllCardsService) => readonly BgsGlobalHeroStat[];
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mergeStats = void 0;
4
+ const aws_lambda_utils_1 = require("@firestone-hs/aws-lambda-utils");
5
+ const util_functions_1 = require("../../common/util-functions");
6
+ const mergeStats = (hourlyData, mmrPercentile, allCards) => {
7
+ const allStats = hourlyData
8
+ .flatMap((data) => data.heroStats)
9
+ .filter((stat) => stat.mmrPercentile === mmrPercentile)
10
+ .filter((stat) => stat.heroCardId !== "TB_BaconShop_HERO_PH");
11
+ const groupedByHero = (0, aws_lambda_utils_1.groupByFunction)((stat) => stat.heroCardId)(allStats);
12
+ const result = Object.values(groupedByHero).map((stats) => mergeStatsForSingleHero(stats, allCards));
13
+ return result;
14
+ };
15
+ exports.mergeStats = mergeStats;
16
+ const mergeStatsForSingleHero = (stats, allCards) => {
17
+ const ref = stats[0];
18
+ const debug = ref.heroCardId === 'BG21_HERO_010';
19
+ const totalWeightedAverage = stats.map((s) => s.averagePosition * s.dataPoints).reduce((a, b) => a + b, 0);
20
+ const totalDataPoints = stats.map((s) => s.dataPoints).reduce((a, b) => a + b, 0);
21
+ const averagePosition = totalWeightedAverage / totalDataPoints;
22
+ const totalWeightedVariance = stats
23
+ .map((s) => s.standardDeviation * s.standardDeviation * s.dataPoints)
24
+ .reduce((a, b) => a + b, 0);
25
+ const overallVariance = totalWeightedVariance / totalDataPoints;
26
+ const standardDeviation = Math.sqrt(overallVariance);
27
+ const standardDeviationOfTheMean = standardDeviation / Math.sqrt(totalDataPoints);
28
+ const result = {
29
+ heroCardId: ref.heroCardId,
30
+ dataPoints: stats.map((stat) => stat.dataPoints).reduce((a, b) => a + b, 0),
31
+ averagePosition: (0, util_functions_1.round)(averagePosition),
32
+ standardDeviation: (0, util_functions_1.round)(standardDeviation),
33
+ standardDeviationOfTheMean: (0, util_functions_1.round)(standardDeviationOfTheMean),
34
+ conservativePositionEstimate: (0, util_functions_1.round)(averagePosition + 3 * standardDeviationOfTheMean),
35
+ placementDistribution: mergePlacementDistributions(stats),
36
+ warbandStats: mergeWarbandStats(stats),
37
+ combatWinrate: mergeCombatWinrate(stats, debug),
38
+ tribeStats: mergeTribeStats(stats, averagePosition),
39
+ anomalyStats: [],
40
+ };
41
+ return result;
42
+ };
43
+ const mergeTribeStats = (stats, refAveragePosition) => {
44
+ const allTribeStats = stats.flatMap((stat) => stat.tribeStats);
45
+ const uniqueTribes = new Set(allTribeStats.map((tribe) => tribe.tribe));
46
+ const result = [...uniqueTribes].map((tribe) => {
47
+ const tribeStats = allTribeStats.filter((stat) => stat.tribe === tribe);
48
+ const averagePosition = tribeStats.map((stat) => stat.averagePosition * stat.dataPoints).reduce((a, b) => a + b, 0) /
49
+ tribeStats.map((stat) => stat.dataPoints).reduce((a, b) => a + b, 0);
50
+ return {
51
+ tribe: tribe,
52
+ dataPoints: tribeStats.map((stat) => stat.dataPoints).reduce((a, b) => a + b, 0),
53
+ dataPointsOnMissingTribe: tribeStats
54
+ .map((stat) => stat.dataPointsOnMissingTribe)
55
+ .reduce((a, b) => a + b, 0),
56
+ averagePosition: (0, util_functions_1.round)(averagePosition),
57
+ impactAveragePosition: (0, util_functions_1.round)(averagePosition - refAveragePosition),
58
+ };
59
+ });
60
+ return result;
61
+ };
62
+ const mergePlacementDistributions = (stats) => {
63
+ var _a, _b;
64
+ const rawMerge = mergePlacementDistributionsRaw(stats);
65
+ const result = [];
66
+ const totalDataPoints = rawMerge.map((s) => s.totalMatches).reduce((a, b) => a + b, 0);
67
+ for (let i = 1; i <= 8; i++) {
68
+ const total = (_b = (_a = rawMerge.find((d) => d.rank === i)) === null || _a === void 0 ? void 0 : _a.totalMatches) !== null && _b !== void 0 ? _b : 0;
69
+ result.push({ rank: i, percentage: (0, util_functions_1.round)((100 * total) / totalDataPoints) });
70
+ }
71
+ return result;
72
+ };
73
+ const mergePlacementDistributionsRaw = (stats) => {
74
+ const rawStats = stats.map((stat) => stat.placementDistributionRaw);
75
+ const result = [];
76
+ for (let i = 1; i <= 8; i++) {
77
+ const total = rawStats
78
+ .map((d) => { var _a, _b; return (_b = (_a = d === null || d === void 0 ? void 0 : d.find((info) => info.rank === i)) === null || _a === void 0 ? void 0 : _a.totalMatches) !== null && _b !== void 0 ? _b : 0; })
79
+ .reduce((a, b) => a + b, 0);
80
+ result.push({ rank: i, totalMatches: total });
81
+ }
82
+ return result;
83
+ };
84
+ const mergeWarbandStats = (stats) => {
85
+ const { rawMerge, maxTurn } = mergeWarbandStatsRaw(stats);
86
+ const result = [];
87
+ for (let i = 1; i <= maxTurn; i++) {
88
+ const statsForTurn = rawMerge.find((d) => d.turn === i);
89
+ result.push({ turn: i, averageStats: (0, util_functions_1.round)(statsForTurn.totalStats / statsForTurn.dataPoints) });
90
+ }
91
+ return result;
92
+ };
93
+ const mergeWarbandStatsRaw = (stats) => {
94
+ const rawStats = stats.map((stat) => stat.warbandStatsRaw).filter((s) => !!(s === null || s === void 0 ? void 0 : s.length));
95
+ const result = [];
96
+ const maxTurn = Math.min(20, Math.max(...rawStats.map((stat) => getMaxTurn(stat))));
97
+ for (let i = 1; i <= maxTurn; i++) {
98
+ const rawStatsForTurn = rawStats.map((stat) => stat.find((info) => info.turn === i));
99
+ const totalStats = rawStatsForTurn.map((d) => { var _a; return (_a = d === null || d === void 0 ? void 0 : d.totalStats) !== null && _a !== void 0 ? _a : 0; }).reduce((a, b) => a + b, 0);
100
+ const totalDataPoints = rawStatsForTurn.map((d) => { var _a; return (_a = d === null || d === void 0 ? void 0 : d.dataPoints) !== null && _a !== void 0 ? _a : 0; }).reduce((a, b) => a + b, 0);
101
+ result.push({ turn: i, totalStats: totalStats, dataPoints: totalDataPoints });
102
+ }
103
+ return { rawMerge: result, maxTurn: maxTurn };
104
+ };
105
+ const mergeCombatWinrate = (stats, debug = false) => {
106
+ const { rawMerge, maxTurn } = mergeCombatWinrateRaw(stats, debug);
107
+ const result = [];
108
+ for (let i = 1; i <= maxTurn; i++) {
109
+ const statForTurn = rawMerge.find((d) => d.turn === i);
110
+ result.push({ turn: i, winrate: (0, util_functions_1.round)(statForTurn.totalWinrate / statForTurn.dataPoints) });
111
+ }
112
+ return result;
113
+ };
114
+ const mergeCombatWinrateRaw = (stats, debug = false) => {
115
+ const rawStats = stats.map((stat) => stat.combatWinrateRaw).filter((stat) => !!(stat === null || stat === void 0 ? void 0 : stat.length));
116
+ const result = [];
117
+ const maxTurn = Math.min(20, Math.max(...rawStats.map((stat) => getMaxTurn(stat))));
118
+ for (let i = 0; i <= maxTurn; i++) {
119
+ const rawStatsForTurn = rawStats.map((stat) => stat.find((info) => info.turn === i));
120
+ const totalWinrate = rawStatsForTurn.map((d) => { var _a; return (_a = d === null || d === void 0 ? void 0 : d.totalWinrate) !== null && _a !== void 0 ? _a : 0; }).reduce((a, b) => a + b, 0);
121
+ const totalDataPoints = rawStatsForTurn.map((d) => { var _a; return (_a = d === null || d === void 0 ? void 0 : d.dataPoints) !== null && _a !== void 0 ? _a : 0; }).reduce((a, b) => a + b, 0);
122
+ result.push({ turn: i, totalWinrate: totalWinrate, dataPoints: totalDataPoints });
123
+ }
124
+ return { rawMerge: result, maxTurn: maxTurn };
125
+ };
126
+ const getMaxTurn = (rawStats) => {
127
+ if (!(rawStats === null || rawStats === void 0 ? void 0 : rawStats.length)) {
128
+ return 0;
129
+ }
130
+ return Math.max(...rawStats.map((stat) => stat.turn));
131
+ };
132
+ //# sourceMappingURL=stats-merger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats-merger.js","sourceRoot":"","sources":["../../../src/aggregate-hourly/heroes/stats-merger.ts"],"names":[],"mappings":"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","sourcesContent":["import { groupByFunction } from '@firestone-hs/aws-lambda-utils';\r\nimport { AllCardsService, CardIds } from '@firestone-hs/reference-data';\r\nimport { round } from '../../common/util-functions';\r\nimport { BgsGlobalHeroStat, BgsHeroStatsV2, BgsHeroTribeStat, MmrPercentileFilter } from '../../models';\r\n\r\nexport const mergeStats = (\r\n\thourlyData: readonly BgsHeroStatsV2[],\r\n\tmmrPercentile: MmrPercentileFilter,\r\n\tallCards: AllCardsService,\r\n): readonly BgsGlobalHeroStat[] => {\r\n\tconst allStats: readonly BgsGlobalHeroStat[] = hourlyData\r\n\t\t.flatMap((data) => data.heroStats)\r\n\t\t.filter((stat) => stat.mmrPercentile === mmrPercentile)\r\n\t\t.filter((stat) => stat.heroCardId !== CardIds.BaconphheroHeroic);\r\n\t// console.debug('allStats', mmrPercentile, allStats.length, '/', hourlyData.length);\r\n\tconst groupedByHero: {\r\n\t\t[heroCardId: string]: readonly BgsGlobalHeroStat[];\r\n\t} = groupByFunction((stat: BgsGlobalHeroStat) => stat.heroCardId)(allStats);\r\n\tconst result: readonly BgsGlobalHeroStat[] = Object.values(groupedByHero).map((stats) =>\r\n\t\tmergeStatsForSingleHero(stats, allCards),\r\n\t);\r\n\treturn result;\r\n};\r\n\r\nconst mergeStatsForSingleHero = (stats: readonly BgsGlobalHeroStat[], allCards: AllCardsService): BgsGlobalHeroStat => {\r\n\tconst ref = stats[0];\r\n\tconst debug = ref.heroCardId === 'BG21_HERO_010';\r\n\r\n\tconst totalWeightedAverage = stats.map((s) => s.averagePosition * s.dataPoints).reduce((a, b) => a + b, 0);\r\n\tconst totalDataPoints = stats.map((s) => s.dataPoints).reduce((a, b) => a + b, 0);\r\n\tconst averagePosition = totalWeightedAverage / totalDataPoints;\r\n\r\n\tconst totalWeightedVariance = stats\r\n\t\t.map((s) => s.standardDeviation * s.standardDeviation * s.dataPoints)\r\n\t\t.reduce((a, b) => a + b, 0);\r\n\tconst overallVariance = totalWeightedVariance / totalDataPoints;\r\n\tconst standardDeviation = Math.sqrt(overallVariance);\r\n\tconst standardDeviationOfTheMean = standardDeviation / Math.sqrt(totalDataPoints);\r\n\r\n\tconst result: BgsGlobalHeroStat = {\r\n\t\theroCardId: ref.heroCardId,\r\n\t\tdataPoints: stats.map((stat) => stat.dataPoints).reduce((a, b) => a + b, 0),\r\n\t\taveragePosition: round(averagePosition),\r\n\t\tstandardDeviation: round(standardDeviation),\r\n\t\tstandardDeviationOfTheMean: round(standardDeviationOfTheMean),\r\n\t\tconservativePositionEstimate: round(averagePosition + 3 * standardDeviationOfTheMean),\r\n\t\tplacementDistribution: mergePlacementDistributions(stats),\r\n\t\twarbandStats: mergeWarbandStats(stats),\r\n\t\tcombatWinrate: mergeCombatWinrate(stats, debug),\r\n\t\ttribeStats: mergeTribeStats(stats, averagePosition),\r\n\t\tanomalyStats: [], // mergeAnomalyStats(stats),\r\n\t};\r\n\treturn result;\r\n};\r\n\r\nconst mergeTribeStats = (\r\n\tstats: readonly BgsGlobalHeroStat[],\r\n\trefAveragePosition: number,\r\n): readonly BgsHeroTribeStat[] => {\r\n\tconst allTribeStats = stats.flatMap((stat) => stat.tribeStats);\r\n\tconst uniqueTribes = new Set(allTribeStats.map((tribe) => tribe.tribe));\r\n\tconst result: BgsHeroTribeStat[] = [...uniqueTribes].map((tribe) => {\r\n\t\tconst tribeStats = allTribeStats.filter((stat) => stat.tribe === tribe);\r\n\t\tconst averagePosition =\r\n\t\t\ttribeStats.map((stat) => stat.averagePosition * stat.dataPoints).reduce((a, b) => a + b, 0) /\r\n\t\t\ttribeStats.map((stat) => stat.dataPoints).reduce((a, b) => a + b, 0);\r\n\t\treturn {\r\n\t\t\ttribe: tribe,\r\n\t\t\tdataPoints: tribeStats.map((stat) => stat.dataPoints).reduce((a, b) => a + b, 0),\r\n\t\t\tdataPointsOnMissingTribe: tribeStats\r\n\t\t\t\t.map((stat) => stat.dataPointsOnMissingTribe)\r\n\t\t\t\t.reduce((a, b) => a + b, 0),\r\n\t\t\taveragePosition: round(averagePosition),\r\n\t\t\timpactAveragePosition: round(averagePosition - refAveragePosition),\r\n\t\t};\r\n\t});\r\n\treturn result;\r\n};\r\n\r\nconst mergePlacementDistributions = (\r\n\tstats: readonly BgsGlobalHeroStat[],\r\n): readonly { rank: number; percentage: number }[] => {\r\n\tconst rawMerge = mergePlacementDistributionsRaw(stats);\r\n\tconst result: { rank: number; percentage: number }[] = [];\r\n\tconst totalDataPoints = rawMerge.map((s) => s.totalMatches).reduce((a, b) => a + b, 0);\r\n\tfor (let i = 1; i <= 8; i++) {\r\n\t\tconst total: number = rawMerge.find((d) => d.rank === i)?.totalMatches ?? 0;\r\n\t\tresult.push({ rank: i, percentage: round((100 * total) / totalDataPoints) });\r\n\t}\r\n\treturn result;\r\n};\r\n\r\nconst mergePlacementDistributionsRaw = (\r\n\tstats: readonly BgsGlobalHeroStat[],\r\n): readonly { rank: number; totalMatches: number }[] => {\r\n\tconst rawStats = stats.map((stat) => stat.placementDistributionRaw);\r\n\t// console.debug(\r\n\t// \t'will merge placement distributions',\r\n\t// \tstats[0].heroCardId,\r\n\t// \tstats[0],\r\n\t// \trawStats.length,\r\n\t// \trawStats.filter((s) => !!s).length,\r\n\t// );\r\n\t// Legacy, can be removed after 2024-01-31\r\n\t// const pStats = stats\r\n\t// \t.filter((stat) => stat.placementDistribution?.length)\r\n\t// \t.map((stat) =>\r\n\t// \t\tstat.placementDistribution.map((s) => ({\r\n\t// \t\t\trank: s.rank,\r\n\t// \t\t\ttotalMatches: s.percentage * stat.dataPoints,\r\n\t// \t\t})),\r\n\t// \t);\r\n\t// const allRawStats = [...rawStats, ...pStats].filter((s) => !!s);\r\n\tconst result: { rank: number; totalMatches: number }[] = [];\r\n\tfor (let i = 1; i <= 8; i++) {\r\n\t\tconst total: number = rawStats\r\n\t\t\t.map((d) => d?.find((info) => info.rank === i)?.totalMatches ?? 0)\r\n\t\t\t.reduce((a, b) => a + b, 0);\r\n\t\tresult.push({ rank: i, totalMatches: total });\r\n\t}\r\n\treturn result;\r\n};\r\n\r\nconst mergeWarbandStats = (stats: readonly BgsGlobalHeroStat[]): readonly { turn: number; averageStats: number }[] => {\r\n\tconst { rawMerge, maxTurn } = mergeWarbandStatsRaw(stats);\r\n\tconst result: { turn: number; averageStats: number }[] = [];\r\n\tfor (let i = 1; i <= maxTurn; i++) {\r\n\t\tconst statsForTurn = rawMerge.find((d) => d.turn === i);\r\n\t\tresult.push({ turn: i, averageStats: round(statsForTurn.totalStats / statsForTurn.dataPoints) });\r\n\t}\r\n\treturn result;\r\n};\r\n\r\nconst mergeWarbandStatsRaw = (\r\n\tstats: readonly BgsGlobalHeroStat[],\r\n): { rawMerge: readonly { turn: number; dataPoints: number; totalStats: number }[]; maxTurn: number } => {\r\n\tconst rawStats = stats.map((stat) => stat.warbandStatsRaw).filter((s) => !!s?.length);\r\n\tconst result: { turn: number; dataPoints: number; totalStats: number }[] = [];\r\n\tconst maxTurn = Math.min(20, Math.max(...rawStats.map((stat) => getMaxTurn(stat))));\r\n\tfor (let i = 1; i <= maxTurn; i++) {\r\n\t\tconst rawStatsForTurn = rawStats.map((stat) => stat.find((info) => info.turn === i));\r\n\t\tconst totalStats: number = rawStatsForTurn.map((d) => d?.totalStats ?? 0).reduce((a, b) => a + b, 0);\r\n\t\tconst totalDataPoints = rawStatsForTurn.map((d) => d?.dataPoints ?? 0).reduce((a, b) => a + b, 0);\r\n\t\tresult.push({ turn: i, totalStats: totalStats, dataPoints: totalDataPoints });\r\n\t}\r\n\treturn { rawMerge: result, maxTurn: maxTurn };\r\n};\r\n\r\nconst mergeCombatWinrate = (\r\n\tstats: readonly BgsGlobalHeroStat[],\r\n\tdebug = false,\r\n): readonly { turn: number; winrate: number }[] => {\r\n\tconst { rawMerge, maxTurn } = mergeCombatWinrateRaw(stats, debug);\r\n\tconst result: { turn: number; winrate: number }[] = [];\r\n\tfor (let i = 1; i <= maxTurn; i++) {\r\n\t\tconst statForTurn = rawMerge.find((d) => d.turn === i);\r\n\t\tresult.push({ turn: i, winrate: round(statForTurn.totalWinrate / statForTurn.dataPoints) });\r\n\t}\r\n\treturn result;\r\n};\r\n\r\nconst mergeCombatWinrateRaw = (\r\n\tstats: readonly BgsGlobalHeroStat[],\r\n\tdebug = false,\r\n): { rawMerge: readonly { turn: number; dataPoints: number; totalWinrate: number }[]; maxTurn: number } => {\r\n\tconst rawStats = stats.map((stat) => stat.combatWinrateRaw).filter((stat) => !!stat?.length);\r\n\t// debug && console.log('rawStats', rawStats?.length, rawStats);\r\n\tconst result: { turn: number; dataPoints: number; totalWinrate: number }[] = [];\r\n\tconst maxTurn = Math.min(20, Math.max(...rawStats.map((stat) => getMaxTurn(stat))));\r\n\t// debug && console.log('maxTurn', maxTurn);\r\n\tfor (let i = 0; i <= maxTurn; i++) {\r\n\t\tconst rawStatsForTurn = rawStats.map((stat) => stat.find((info) => info.turn === i));\r\n\t\tconst totalWinrate: number = rawStatsForTurn.map((d) => d?.totalWinrate ?? 0).reduce((a, b) => a + b, 0);\r\n\t\tconst totalDataPoints = rawStatsForTurn.map((d) => d?.dataPoints ?? 0).reduce((a, b) => a + b, 0);\r\n\t\tresult.push({ turn: i, totalWinrate: totalWinrate, dataPoints: totalDataPoints });\r\n\t}\r\n\t// debug && console.log('result', result);\r\n\treturn { rawMerge: result, maxTurn: maxTurn };\r\n};\r\n\r\nconst getMaxTurn = (rawStats: readonly { turn: number }[]): number => {\r\n\tif (!rawStats?.length) {\r\n\t\treturn 0;\r\n\t}\r\n\treturn Math.max(...rawStats.map((stat) => stat.turn));\r\n};\r\n"]}
@@ -0,0 +1,4 @@
1
+ import { PatchInfo } from '@firestone-hs/aws-lambda-utils';
2
+ import { TimePeriod } from '../models';
3
+ export declare const computeHoursBackFromNow: (timePeriod: TimePeriod, patchInfo: PatchInfo) => number;
4
+ export declare const buildFileNames: (hoursBack: number) => readonly string[];
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildFileNames = exports.computeHoursBackFromNow = void 0;
4
+ const computeHoursBackFromNow = (timePeriod, patchInfo) => {
5
+ switch (timePeriod) {
6
+ case 'past-three':
7
+ return 3 * 24;
8
+ case 'past-seven':
9
+ return 7 * 24;
10
+ case 'all-time':
11
+ return 20 * 24;
12
+ case 'last-patch':
13
+ const patchReleaseDate = new Date(patchInfo.date);
14
+ const hours = Math.floor((Date.now() - patchReleaseDate.getTime()) / (1000 * 60 * 60));
15
+ return hours;
16
+ }
17
+ };
18
+ exports.computeHoursBackFromNow = computeHoursBackFromNow;
19
+ const buildFileNames = (hoursBack) => {
20
+ const fileNames = [];
21
+ const now = new Date();
22
+ for (let i = 0; i < hoursBack; i++) {
23
+ const date = new Date(now.getTime() - i * 60 * 60 * 1000);
24
+ date.setMinutes(0);
25
+ date.setSeconds(0);
26
+ date.setMilliseconds(0);
27
+ const dateStr = date.toISOString();
28
+ fileNames.push(`${dateStr}`);
29
+ }
30
+ return fileNames;
31
+ };
32
+ exports.buildFileNames = buildFileNames;
33
+ //# sourceMappingURL=hourly-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hourly-utils.js","sourceRoot":"","sources":["../../src/aggregate-hourly/hourly-utils.ts"],"names":[],"mappings":";;;AAIO,MAAM,uBAAuB,GAAG,CAAC,UAAsB,EAAE,SAAoB,EAAU,EAAE;IAC/F,QAAQ,UAAU,EAAE;QACnB,KAAK,YAAY;YAChB,OAAO,CAAC,GAAG,EAAE,CAAC;QACf,KAAK,YAAY;YAChB,OAAO,CAAC,GAAG,EAAE,CAAC;QACf,KAAK,UAAU;YACd,OAAO,EAAE,GAAG,EAAE,CAAC;QAChB,KAAK,YAAY;YAChB,MAAM,gBAAgB,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAClD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,gBAAgB,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;YAEvF,OAAO,KAAK,CAAC;KACd;AACF,CAAC,CAAC;AAdW,QAAA,uBAAuB,2BAclC;AAEK,MAAM,cAAc,GAAG,CAAC,SAAiB,EAAqB,EAAE;IAGtE,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE;QACnC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC1D,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QACnB,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QACnB,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAExB,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QACnC,SAAS,CAAC,IAAI,CAAC,GAAG,OAAO,EAAE,CAAC,CAAC;KAC7B;IACD,OAAO,SAAS,CAAC;AAClB,CAAC,CAAC;AAfW,QAAA,cAAc,kBAezB","sourcesContent":["/* eslint-disable no-case-declarations */\r\nimport { PatchInfo } from '@firestone-hs/aws-lambda-utils';\r\nimport { TimePeriod } from '../models';\r\n\r\nexport const computeHoursBackFromNow = (timePeriod: TimePeriod, patchInfo: PatchInfo): number => {\r\n\tswitch (timePeriod) {\r\n\t\tcase 'past-three':\r\n\t\t\treturn 3 * 24;\r\n\t\tcase 'past-seven':\r\n\t\t\treturn 7 * 24;\r\n\t\tcase 'all-time':\r\n\t\t\treturn 20 * 24;\r\n\t\tcase 'last-patch':\r\n\t\t\tconst patchReleaseDate = new Date(patchInfo.date);\r\n\t\t\tconst hours = Math.floor((Date.now() - patchReleaseDate.getTime()) / (1000 * 60 * 60));\r\n\t\t\t// console.debug('hours since last patch', hours, patchReleaseDate, patchInfo);\r\n\t\t\treturn hours;\r\n\t}\r\n};\r\n\r\nexport const buildFileNames = (hoursBack: number): readonly string[] => {\r\n\t// Build a list of file names, in the form YYYY-MM-dd (e.g. 2020-05-01)\r\n\t// that start from the day before the current date and go back in time\r\n\tconst fileNames: string[] = [];\r\n\tconst now = new Date();\r\n\tfor (let i = 0; i < hoursBack; i++) {\r\n\t\tconst date = new Date(now.getTime() - i * 60 * 60 * 1000);\r\n\t\tdate.setMinutes(0);\r\n\t\tdate.setSeconds(0);\r\n\t\tdate.setMilliseconds(0);\r\n\t\t// The date in the format YYYY-MM-ddTHH:mm:ss.sssZ\r\n\t\tconst dateStr = date.toISOString();\r\n\t\tfileNames.push(`${dateStr}`);\r\n\t}\r\n\treturn fileNames;\r\n};\r\n"]}
@@ -0,0 +1,3 @@
1
+ import { BgsQuestStats } from '../model-quests';
2
+ import { BgsHeroStatsV2, MmrPercentile } from '../models';
3
+ export declare const buildMmrPercentiles: (hourlyData: readonly (BgsHeroStatsV2 | BgsQuestStats)[]) => readonly MmrPercentile[];
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildMmrPercentiles = void 0;
4
+ const buildMmrPercentiles = (hourlyData) => {
5
+ return [...hourlyData].sort((a, b) => new Date(b.lastUpdateDate).getTime() - new Date(a.lastUpdateDate).getTime())[0].mmrPercentiles;
6
+ };
7
+ exports.buildMmrPercentiles = buildMmrPercentiles;
8
+ //# sourceMappingURL=percentiles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"percentiles.js","sourceRoot":"","sources":["../../src/aggregate-hourly/percentiles.ts"],"names":[],"mappings":";;;AAGO,MAAM,mBAAmB,GAAG,CAClC,UAAuD,EAC5B,EAAE;IAG7B,OAAO,CAAC,GAAG,UAAU,CAAC,CAAC,IAAI,CAC1B,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,OAAO,EAAE,CACrF,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC;AACrB,CAAC,CAAC;AARW,QAAA,mBAAmB,uBAQ9B","sourcesContent":["import { BgsQuestStats } from '../model-quests';\r\nimport { BgsHeroStatsV2, MmrPercentile } from '../models';\r\n\r\nexport const buildMmrPercentiles = (\r\n\thourlyData: readonly (BgsHeroStatsV2 | BgsQuestStats)[],\r\n): readonly MmrPercentile[] => {\r\n\t// For now we simply pick the latest MMR percentiles, as it reflects the most accurately the current\r\n\t// state of the game\r\n\treturn [...hourlyData].sort(\r\n\t\t(a, b) => new Date(b.lastUpdateDate).getTime() - new Date(a.lastUpdateDate).getTime(),\r\n\t)[0].mmrPercentiles;\r\n};\r\n"]}
@@ -0,0 +1,5 @@
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>;
5
+ export default _default;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.s3 = void 0;
7
+ const aws_lambda_utils_1 = require("@firestone-hs/aws-lambda-utils");
8
+ const reference_data_1 = require("@firestone-hs/reference-data");
9
+ const aws_sdk_1 = __importDefault(require("aws-sdk"));
10
+ const percentiles_1 = require("../percentiles");
11
+ const s3_loader_1 = require("../s3-loader");
12
+ const quest_stats_merger_1 = require("./quest-stats-merger");
13
+ const reward_stats_merger_1 = require("./reward-stats-merger");
14
+ const s3_saver_1 = require("./s3-saver");
15
+ const allCards = new reference_data_1.AllCardsService();
16
+ exports.s3 = new aws_lambda_utils_1.S3();
17
+ const lambda = new aws_sdk_1.default.Lambda();
18
+ exports.default = async (event, context) => {
19
+ await allCards.initializeCardsDb();
20
+ if (!event.timePeriod) {
21
+ await dispatchEvents(context);
22
+ return;
23
+ }
24
+ const cleanup = (0, aws_lambda_utils_1.logBeforeTimeout)(context);
25
+ const timePeriod = event.timePeriod;
26
+ const mmrPercentile = event.mmrPercentile;
27
+ const patchInfo = await (0, aws_lambda_utils_1.getLastBattlegroundsPatch)();
28
+ const hourlyData = await (0, s3_loader_1.loadHourlyDataFromS3)('quest', timePeriod, mmrPercentile, patchInfo);
29
+ const lastUpdate = hourlyData
30
+ .map((d) => ({
31
+ date: new Date(d.lastUpdateDate),
32
+ dateStr: d.lastUpdateDate,
33
+ time: new Date(d.lastUpdateDate).getTime(),
34
+ }))
35
+ .sort((a, b) => b.time - a.time)[0].date;
36
+ const mergedQuestStats = (0, quest_stats_merger_1.mergeQuestStats)(hourlyData, mmrPercentile, allCards);
37
+ const mergedRewardStats = (0, reward_stats_merger_1.mergeRewardStats)(hourlyData, mmrPercentile, allCards);
38
+ const mmrPercentiles = (0, percentiles_1.buildMmrPercentiles)(hourlyData);
39
+ await (0, s3_saver_1.persistData)(mergedQuestStats, mergedRewardStats, mmrPercentiles, lastUpdate, timePeriod, mmrPercentile);
40
+ cleanup();
41
+ };
42
+ const dispatchEvents = async (context) => {
43
+ console.log('dispatching events');
44
+ const allTimePeriod = ['all-time', 'past-three', 'past-seven', 'last-patch'];
45
+ const mmrPercentiles = [100, 50, 25, 10, 1];
46
+ for (const timePeriod of allTimePeriod) {
47
+ for (const percentile of mmrPercentiles) {
48
+ const newEvent = {
49
+ timePeriod: timePeriod,
50
+ mmrPercentile: percentile,
51
+ };
52
+ const params = {
53
+ FunctionName: context.functionName,
54
+ InvocationType: 'Event',
55
+ LogType: 'Tail',
56
+ Payload: JSON.stringify(newEvent),
57
+ };
58
+ const result = await lambda
59
+ .invoke({
60
+ FunctionName: context.functionName,
61
+ InvocationType: 'Event',
62
+ LogType: 'Tail',
63
+ Payload: JSON.stringify(newEvent),
64
+ })
65
+ .promise();
66
+ await (0, aws_lambda_utils_1.sleep)(50);
67
+ }
68
+ }
69
+ };
70
+ //# sourceMappingURL=_build-aggregated-stats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_build-aggregated-stats.js","sourceRoot":"","sources":["../../../src/aggregate-hourly/quests/_build-aggregated-stats.ts"],"names":[],"mappings":"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","sourcesContent":["import { S3, getLastBattlegroundsPatch, logBeforeTimeout, sleep } from '@firestone-hs/aws-lambda-utils';\r\nimport { AllCardsService } from '@firestone-hs/reference-data';\r\nimport { Context } from 'aws-lambda';\r\nimport AWS from 'aws-sdk';\r\nimport { BgsGlobalQuestStat, BgsGlobalRewardStat, BgsQuestStats } from '../../model-quests';\r\nimport { MmrPercentile, MmrPercentileFilter, TimePeriod } from '../../models';\r\nimport { buildMmrPercentiles } from '../percentiles';\r\nimport { loadHourlyDataFromS3 } from '../s3-loader';\r\nimport { mergeQuestStats } from './quest-stats-merger';\r\nimport { mergeRewardStats } from './reward-stats-merger';\r\nimport { persistData } from './s3-saver';\r\n\r\nconst allCards = new AllCardsService();\r\nexport const s3 = new S3();\r\nconst lambda = new AWS.Lambda();\r\n\r\nexport default async (event, context: Context): Promise<any> => {\r\n\tawait allCards.initializeCardsDb();\r\n\r\n\tif (!event.timePeriod) {\r\n\t\tawait dispatchEvents(context);\r\n\t\treturn;\r\n\t}\r\n\r\n\tconst cleanup = logBeforeTimeout(context);\r\n\tconst timePeriod: TimePeriod = event.timePeriod;\r\n\tconst mmrPercentile: MmrPercentileFilter = event.mmrPercentile;\r\n\r\n\t// console.log('aggregating data', timePeriod, mmrPercentile);\r\n\t// Build the list of files based on the timeframe, and load all of these\r\n\tconst patchInfo = await getLastBattlegroundsPatch();\r\n\tconst hourlyData: readonly BgsQuestStats[] = await loadHourlyDataFromS3(\r\n\t\t'quest',\r\n\t\ttimePeriod,\r\n\t\tmmrPercentile,\r\n\t\tpatchInfo,\r\n\t);\r\n\t// console.log('hourlyData', hourlyData.length);\r\n\tconst lastUpdate = hourlyData\r\n\t\t.map((d) => ({\r\n\t\t\tdate: new Date(d.lastUpdateDate),\r\n\t\t\tdateStr: d.lastUpdateDate,\r\n\t\t\ttime: new Date(d.lastUpdateDate).getTime(),\r\n\t\t}))\r\n\t\t.sort((a, b) => b.time - a.time)[0].date;\r\n\t// console.log('loaded hourly data', lastUpdate);\r\n\r\n\t// Here it's ok that the MMR corresponding to each MMR percentile is not the same across all the hourly data chunks\r\n\t// as the actual MMR evolves over time\r\n\t// The only issue is that the samples might be too small for the MMR percentiles to be really representative in\r\n\t// each group\r\n\t// Empirically, it looks like there isn't much variation on a hour-by-hour basis, so it should be ok\r\n\t// A possible solution, if this becomes an issue, is to compute the MMR percentiles on the full last day of data\r\n\t// when building the hourly data\r\n\tconst mergedQuestStats: readonly BgsGlobalQuestStat[] = mergeQuestStats(hourlyData, mmrPercentile, allCards);\r\n\tconst mergedRewardStats: readonly BgsGlobalRewardStat[] = mergeRewardStats(hourlyData, mmrPercentile, allCards);\r\n\tconst mmrPercentiles: readonly MmrPercentile[] = buildMmrPercentiles(hourlyData);\r\n\tawait persistData(mergedQuestStats, mergedRewardStats, mmrPercentiles, lastUpdate, timePeriod, mmrPercentile);\r\n\tcleanup();\r\n};\r\n\r\nconst dispatchEvents = async (context: Context) => {\r\n\tconsole.log('dispatching events');\r\n\tconst allTimePeriod: readonly TimePeriod[] = ['all-time', 'past-three', 'past-seven', 'last-patch'];\r\n\tconst mmrPercentiles: readonly MmrPercentileFilter[] = [100, 50, 25, 10, 1];\r\n\tfor (const timePeriod of allTimePeriod) {\r\n\t\tfor (const percentile of mmrPercentiles) {\r\n\t\t\tconst newEvent = {\r\n\t\t\t\ttimePeriod: timePeriod,\r\n\t\t\t\tmmrPercentile: percentile,\r\n\t\t\t};\r\n\t\t\tconst params = {\r\n\t\t\t\tFunctionName: context.functionName,\r\n\t\t\t\tInvocationType: 'Event',\r\n\t\t\t\tLogType: 'Tail',\r\n\t\t\t\tPayload: JSON.stringify(newEvent),\r\n\t\t\t};\r\n\t\t\t// console.log('\\tinvoking lambda', params);\r\n\t\t\tconst result = await lambda\r\n\t\t\t\t.invoke({\r\n\t\t\t\t\tFunctionName: context.functionName,\r\n\t\t\t\t\tInvocationType: 'Event',\r\n\t\t\t\t\tLogType: 'Tail',\r\n\t\t\t\t\tPayload: JSON.stringify(newEvent),\r\n\t\t\t\t})\r\n\t\t\t\t.promise();\r\n\t\t\t// console.log('\\tinvocation result', result);\r\n\t\t\tawait sleep(50);\r\n\t\t}\r\n\t}\r\n};\r\n"]}
@@ -0,0 +1,4 @@
1
+ import { AllCardsService } from '@firestone-hs/reference-data';
2
+ import { BgsGlobalQuestStat, BgsQuestStats } from '../../model-quests';
3
+ import { MmrPercentileFilter } from '../../models';
4
+ export declare const mergeQuestStats: (hourlyData: readonly BgsQuestStats[], mmrPercentile: MmrPercentileFilter, allCards: AllCardsService) => readonly BgsGlobalQuestStat[];
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mergeQuestStats = void 0;
4
+ const aws_lambda_utils_1 = require("@firestone-hs/aws-lambda-utils");
5
+ const mergeQuestStats = (hourlyData, mmrPercentile, allCards) => {
6
+ const allStats = hourlyData
7
+ .flatMap((data) => data.questStats)
8
+ .filter((stat) => stat.mmrPercentile === mmrPercentile);
9
+ const groupedByQuest = (0, aws_lambda_utils_1.groupByFunction)((stat) => stat.questCardId)(allStats);
10
+ const result = Object.values(groupedByQuest).map((stats) => mergeStatsForSingleQuest(stats, allCards));
11
+ return result;
12
+ };
13
+ exports.mergeQuestStats = mergeQuestStats;
14
+ const mergeStatsForSingleQuest = (stats, allCards) => {
15
+ const ref = stats[0];
16
+ const totalDataPoints = stats.map((stat) => stat.dataPoints).reduce((a, b) => a + b, 0);
17
+ const totalTurnsToComplete = stats.map((s) => s.averageTurnToComplete * s.dataPoints).reduce((a, b) => a + b, 0);
18
+ const totalCompleted = stats.map((s) => s.completionRate * s.dataPoints).reduce((a, b) => a + b, 0);
19
+ const refAverageTurnToComplete = totalTurnsToComplete / totalDataPoints;
20
+ const refCompletionRate = totalCompleted / totalDataPoints;
21
+ const result = {
22
+ questCardId: ref.questCardId,
23
+ dataPoints: totalDataPoints,
24
+ averageTurnToComplete: refAverageTurnToComplete,
25
+ completionRate: refCompletionRate,
26
+ difficultyStats: mergeDifficultyStatsForSingleQuest(stats, refAverageTurnToComplete, refCompletionRate),
27
+ heroStats: mergeHeroStatsForSingleQuest(stats),
28
+ tribeStats: mergeTribeStatsForSingleQuest(stats, refAverageTurnToComplete, refCompletionRate),
29
+ };
30
+ return result;
31
+ };
32
+ const mergeTribeStatsForSingleQuest = (stats, refAverageTurnToComplete, refCompletionRate) => {
33
+ const tribeStats = stats.flatMap((s) => s.tribeStats);
34
+ const groupedByTribe = (0, aws_lambda_utils_1.groupByFunction)((stat) => stat.tribe)(tribeStats);
35
+ const result = Object.values(groupedByTribe).map((stats) => mergeTribeStatsForSingleTribe(stats, refAverageTurnToComplete, refCompletionRate));
36
+ return result;
37
+ };
38
+ const mergeTribeStatsForSingleTribe = (stats, refAverageTurnToComplete, refCompletionRate) => {
39
+ const ref = stats[0];
40
+ const totalDataPoints = stats.map((s) => s.dataPoints).reduce((a, b) => a + b, 0);
41
+ const totalTurnsToComplete = stats.map((s) => s.averageTurnToComplete * s.dataPoints).reduce((a, b) => a + b, 0);
42
+ const totalCompleted = stats.map((s) => s.completionRate * s.dataPoints).reduce((a, b) => a + b, 0);
43
+ const averageTurnToComplete = totalTurnsToComplete / totalDataPoints;
44
+ const completionRate = totalCompleted / totalDataPoints;
45
+ const result = {
46
+ tribe: ref.tribe,
47
+ dataPoints: totalDataPoints,
48
+ averageTurnToComplete: averageTurnToComplete,
49
+ completionRate: completionRate,
50
+ impactTurnToComplete: averageTurnToComplete - refAverageTurnToComplete,
51
+ impactCompletionRate: completionRate - refCompletionRate,
52
+ };
53
+ return result;
54
+ };
55
+ const mergeHeroStatsForSingleQuest = (stats) => {
56
+ const heroStats = stats.flatMap((s) => s.heroStats);
57
+ const groupedByHero = (0, aws_lambda_utils_1.groupByFunction)((stat) => stat.heroCardId)(heroStats);
58
+ const result = Object.values(groupedByHero).map((stats) => mergeHeroStatsForSingleHero(stats));
59
+ return result;
60
+ };
61
+ const mergeHeroStatsForSingleHero = (stats) => {
62
+ const ref = stats[0];
63
+ const totalDataPoints = stats.map((s) => s.dataPoints).reduce((a, b) => a + b, 0);
64
+ const totalTurnsToComplete = stats.map((s) => s.averageTurnToComplete * s.dataPoints).reduce((a, b) => a + b, 0);
65
+ const totalCompleted = stats.map((s) => s.completionRate * s.dataPoints).reduce((a, b) => a + b, 0);
66
+ const averageTurnToComplete = totalTurnsToComplete / totalDataPoints;
67
+ const completionRate = totalCompleted / totalDataPoints;
68
+ const result = {
69
+ heroCardId: ref.heroCardId,
70
+ dataPoints: totalDataPoints,
71
+ averageTurnToComplete: averageTurnToComplete,
72
+ completionRate: completionRate,
73
+ };
74
+ return result;
75
+ };
76
+ const mergeDifficultyStatsForSingleQuest = (stats, refAverageTurnToComplete, refCompletionRate) => {
77
+ const difficultyStats = stats.flatMap((s) => s.difficultyStats);
78
+ const groupedByDifficulty = (0, aws_lambda_utils_1.groupByFunction)((stat) => stat.difficulty)(difficultyStats);
79
+ const result = Object.values(groupedByDifficulty).map((stats) => mergeDifficultyStatsForSingleDifficulty(stats, refAverageTurnToComplete, refCompletionRate));
80
+ return result;
81
+ };
82
+ const mergeDifficultyStatsForSingleDifficulty = (stats, refAverageTurnToComplete, refCompletionRate) => {
83
+ const ref = stats[0];
84
+ const totalDataPoints = stats.map((s) => s.dataPoints).reduce((a, b) => a + b, 0);
85
+ const totalTurnsToComplete = stats.map((s) => s.averageTurnToComplete * s.dataPoints).reduce((a, b) => a + b, 0);
86
+ const totalCompleted = stats.map((s) => s.completionRate * s.dataPoints).reduce((a, b) => a + b, 0);
87
+ const averageTurnToComplete = totalTurnsToComplete / totalDataPoints;
88
+ const completionRate = totalCompleted / totalDataPoints;
89
+ const result = {
90
+ difficulty: ref.difficulty,
91
+ dataPoints: totalDataPoints,
92
+ averageTurnToComplete: averageTurnToComplete,
93
+ completionRate: completionRate,
94
+ impactTurnToComplete: averageTurnToComplete - refAverageTurnToComplete,
95
+ impactCompletionRate: completionRate - refCompletionRate,
96
+ };
97
+ return result;
98
+ };
99
+ //# sourceMappingURL=quest-stats-merger.js.map