-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add first iteration for the new hero stats computation (missing warba…
…nds)
- Loading branch information
1 parent
4601cd8
commit 88d0c70
Showing
8 changed files
with
224 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { Race } from '@firestone-hs/reference-data'; | ||
import { MmrPercentile } from '../bgs-global-stats'; | ||
import { WithMmrAndTimePeriod } from '../quests-v2/charged-stat'; | ||
|
||
export interface BgsHeroStatsV2 { | ||
readonly lastUpdateDate: Date; | ||
readonly mmrPercentiles: readonly MmrPercentile[]; | ||
readonly dataPoints: number; | ||
readonly heroStats: readonly WithMmrAndTimePeriod<BgsGlobalHeroStat>[]; | ||
} | ||
|
||
export interface BgsGlobalHeroStat { | ||
readonly heroCardId: string; | ||
readonly dataPoints: number; | ||
readonly averagePosition: number; | ||
readonly placementDistribution: readonly { rank: number; percentage: number }[]; | ||
readonly combatWinrate: readonly { turn: number; winrate: number }[]; | ||
// // Same | ||
// readonly warbandStats: readonly { turn: number; dataPoints: number; totalStats: number }[]; | ||
readonly tribeStats: readonly BgsHeroTribeStat[]; | ||
} | ||
|
||
export interface BgsHeroTribeStat { | ||
readonly tribe: Race; | ||
readonly dataPoints: number; | ||
readonly averagePosition: number; | ||
readonly impactAveragePosition: number; | ||
readonly placementDistribution: readonly { rank: number; percentage: number }[]; | ||
readonly impactPlacementDistribution: readonly { rank: number; impact: number }[]; | ||
readonly combatWinrate: readonly { turn: number; winrate: number }[]; | ||
readonly impactCombatWinrate: readonly { turn: number; impact: number }[]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import { groupByFunction } from '@firestone-hs/aws-lambda-utils'; | ||
import { AllCardsService, CardIds, Race } from '@firestone-hs/reference-data'; | ||
import { buildCombatWinrate } from '../build-battlegrounds-hero-stats-new'; | ||
import { buildPlacementDistributionWithPercentages } from '../common'; | ||
import { InternalBgsRow } from '../internal-model'; | ||
import { normalizeHeroCardId } from '../utils/util-functions'; | ||
import { BgsGlobalHeroStat, BgsHeroTribeStat } from './bgs-hero-stat'; | ||
|
||
export const buildStats = ( | ||
rows: readonly InternalBgsRow[], | ||
allCards: AllCardsService, | ||
): readonly BgsGlobalHeroStat[] => { | ||
const groupedByHero: { | ||
[questCardId: string]: readonly InternalBgsRow[]; | ||
} = groupByFunction((row: InternalBgsRow) => normalizeHeroCardId(row.heroCardId, allCards))(rows); | ||
return Object.values(groupedByHero).flatMap(data => buildStatsForSingleHero(data)); | ||
}; | ||
|
||
// All rows here belong to a single hero | ||
const buildStatsForSingleHero = (rows: readonly InternalBgsRow[]): BgsGlobalHeroStat => { | ||
const ref = rows[0]; | ||
const averagePosition = average(rows.map(r => r.rank)); | ||
const placementDistribution = buildPlacementDistributionWithPercentages(rows); | ||
const rawCombatWinrates = buildCombatWinrate(rows); | ||
const combatWinrate: readonly { turn: number; winrate: number }[] = rawCombatWinrates.map(info => ({ | ||
turn: info.turn, | ||
winrate: info.totalWinrate / info.dataPoints, | ||
})); | ||
const result: BgsGlobalHeroStat = { | ||
heroCardId: ref.heroCardId, | ||
dataPoints: rows.length, | ||
averagePosition: averagePosition, | ||
placementDistribution: placementDistribution, | ||
combatWinrate: combatWinrate, | ||
tribeStats: buildTribeStats(rows, averagePosition, placementDistribution, combatWinrate), | ||
}; | ||
if (ref.heroCardId === CardIds.PatchesThePirateBattlegrounds) { | ||
console.debug('Patches sanity', result); | ||
console.debug( | ||
'Patches sanity', | ||
result.tribeStats.map(t => t.impactAveragePosition * t.dataPoints).reduce((a, b) => a + b, 0), | ||
); | ||
console.debug( | ||
'Patches sanity', | ||
rows.filter(r => !r.tribes.includes('' + Race.PIRATE)).length, | ||
rows.filter(r => !r.tribes.includes('' + Race.PIRATE)), | ||
); | ||
} | ||
return result; | ||
}; | ||
|
||
const buildTribeStats = ( | ||
rows: readonly InternalBgsRow[], | ||
refAveragePosition: number, | ||
refPlacementDistribution: readonly { rank: number; percentage: number }[], | ||
refCombatWinrate: readonly { turn: number; winrate: number }[], | ||
): readonly BgsHeroTribeStat[] => { | ||
const uniqueTribes: readonly Race[] = [...new Set(rows.flatMap(r => r.tribes.split(',')).map(r => parseInt(r)))]; | ||
return uniqueTribes.map(tribe => { | ||
const rowsForTribe = rows.filter(r => r.tribes.split(',').includes('' + tribe)); | ||
const rowsWithoutTribe = rows.filter(r => !r.tribes.split(',').includes('' + tribe)); | ||
const averagePosition = average(rowsForTribe.map(r => r.rank)); | ||
const placementDistribution = buildPlacementDistributionWithPercentages(rowsForTribe); | ||
const rawCombatWinrates = buildCombatWinrate(rowsForTribe); | ||
const combatWinrate = rawCombatWinrates.map(info => ({ | ||
turn: info.turn, | ||
winrate: info.totalWinrate / info.dataPoints, | ||
})); | ||
return { | ||
tribe: tribe, | ||
dataPoints: rowsForTribe.length, | ||
dataPointsOnMissingTribe: rowsWithoutTribe.length, | ||
averagePosition: averagePosition, | ||
impactAveragePosition: averagePosition - refAveragePosition, | ||
placementDistribution: placementDistribution, | ||
impactPlacementDistribution: refPlacementDistribution.map(p => { | ||
const newPlacementInfo = placementDistribution.find(p2 => p2.rank === p.rank); | ||
// Cna happen when there isn't a lot of data points, typically for high MMR | ||
if (!newPlacementInfo) { | ||
console.log('missing placement info', placementDistribution, p); | ||
} | ||
return { | ||
rank: p.rank, | ||
impact: (newPlacementInfo?.percentage ?? 0) - p.percentage, | ||
}; | ||
}), | ||
combatWinrate: combatWinrate, | ||
impactCombatWinrate: refCombatWinrate.map(c => { | ||
const newCombatWinrate = combatWinrate.find(c2 => c2.turn === c.turn); | ||
if (!newCombatWinrate) { | ||
console.debug('missing winrate info', combatWinrate); | ||
} | ||
return { | ||
turn: c.turn, | ||
impact: (newCombatWinrate?.winrate ?? 0) - c.winrate, | ||
}; | ||
}), | ||
}; | ||
}); | ||
}; | ||
|
||
const average = (data: readonly number[]): number => { | ||
return data.reduce((a, b) => a + b, 0) / data.length; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { logger } from '@firestone-hs/aws-lambda-utils'; | ||
import { AllCardsService } from '@firestone-hs/reference-data'; | ||
import { gzipSync } from 'zlib'; | ||
import { s3 } from '../build-battlegrounds-hero-stats-new'; | ||
import { PatchInfo } from '../common'; | ||
import { InternalBgsRow } from '../internal-model'; | ||
import { buildSplitStats } from '../quests-v2/data-filter'; | ||
import { BgsHeroStatsV2 } from './bgs-hero-stat'; | ||
import { buildStats } from './stats-buikder'; | ||
|
||
export const STATS_BUCKET = `static.zerotoheroes.com`; | ||
|
||
export const handleStatsV2 = async ( | ||
timePeriod: 'all-time' | 'past-three' | 'past-seven' | 'last-patch', | ||
rows: readonly InternalBgsRow[], | ||
lastPatch: PatchInfo, | ||
allCards: AllCardsService, | ||
) => { | ||
const rowsWithStats = rows.filter(row => !!row.rank).filter(r => !!r.tribes?.length); | ||
console.log('total relevant rows', rowsWithStats?.length); | ||
|
||
const statResult = await buildSplitStats(rowsWithStats, timePeriod, lastPatch, (data: InternalBgsRow[]) => | ||
buildStats(data, allCards), | ||
); | ||
const stats = statResult.stats; | ||
const statsV2: BgsHeroStatsV2 = { | ||
lastUpdateDate: new Date(), | ||
mmrPercentiles: statResult.mmrPercentiles, | ||
heroStats: stats, | ||
dataPoints: stats.map(s => s.dataPoints).reduce((a, b) => a + b, 0), | ||
}; | ||
logger.log('\tbuilt stats', statsV2.dataPoints, statsV2.heroStats?.length); | ||
const timeSuffix = timePeriod; | ||
await s3.writeFile( | ||
gzipSync(JSON.stringify(statsV2)), | ||
STATS_BUCKET, | ||
`api/bgs/stats-v2/bgs-${timeSuffix}.gz.json`, | ||
'application/json', | ||
'gzip', | ||
); | ||
}; |