feat: tag items depending on region and gold state when bought
This commit is contained in:
@@ -2,12 +2,14 @@ import { MongoClient } from 'mongodb'
|
||||
import {
|
||||
ItemTree,
|
||||
GoldAdvantageTag,
|
||||
PlatformCounts,
|
||||
treeInit,
|
||||
treeMerge,
|
||||
treeCutBranches,
|
||||
treeSort,
|
||||
treeMergeTree,
|
||||
areTreeSimilars
|
||||
areTreeSimilars,
|
||||
treeDeriveTags
|
||||
} from './item_tree'
|
||||
|
||||
import { Match, Timeline, Participant, Frame } from './api'
|
||||
@@ -101,6 +103,8 @@ type LaneData = {
|
||||
builds: Builds
|
||||
matchups?: Array<MatchupData>
|
||||
summonerSpells: Array<{ id: number; count: number; pickrate?: number }>
|
||||
// Region distribution for this lane (used for tag derivation)
|
||||
regionDistribution?: PlatformCounts
|
||||
}
|
||||
type ChampionData = {
|
||||
champion: Champion
|
||||
@@ -332,11 +336,21 @@ function handleMatch(match: Match, champions: Map<number, ChampionData>, platfor
|
||||
winrate: 0,
|
||||
pickrate: 0,
|
||||
summonerSpells: [],
|
||||
matchups: []
|
||||
matchups: [],
|
||||
regionDistribution: { euw: 0, eun: 0, na: 0, kr: 0 }
|
||||
}
|
||||
champion.lanes.push(lane)
|
||||
} else lane.count += 1
|
||||
|
||||
// Track region distribution for this lane
|
||||
if (lane.regionDistribution && platform) {
|
||||
const platformKey = platform.toLowerCase()
|
||||
if (platformKey === 'euw1') lane.regionDistribution.euw++
|
||||
else if (platformKey === 'eun1') lane.regionDistribution.eun++
|
||||
else if (platformKey === 'na1') lane.regionDistribution.na++
|
||||
else if (platformKey === 'kr') lane.regionDistribution.kr++
|
||||
}
|
||||
|
||||
// Initialize matchups if not present
|
||||
if (!lane.matchups) {
|
||||
lane.matchups = []
|
||||
@@ -589,6 +603,9 @@ function cleanupLaneBuilds(lane: LaneData) {
|
||||
treeCutBranches(build.items, 4, 0.05)
|
||||
treeSort(build.items)
|
||||
|
||||
// Derive tags from purchase patterns (gold advantage, region)
|
||||
treeDeriveTags(build.items, lane.regionDistribution)
|
||||
|
||||
// Remove boots that are not within percentage threshold
|
||||
arrayRemovePercentage(build.boots, build.count, 0.05)
|
||||
build.boots.sort((a, b) => b.count - a.count)
|
||||
|
||||
@@ -7,6 +7,9 @@ type PlatformCounts = {
|
||||
kr: number
|
||||
}
|
||||
|
||||
// Item tags that can be derived from purchase patterns
|
||||
type ItemTag = 'ahead' | 'behind' | 'region_euw' | 'region_eun' | 'region_na' | 'region_kr'
|
||||
|
||||
type ItemTree = {
|
||||
data: number | undefined
|
||||
count: number
|
||||
@@ -22,6 +25,9 @@ type ItemTree = {
|
||||
|
||||
// Platform tracking
|
||||
platformCount: PlatformCounts
|
||||
|
||||
// Derived tags for display
|
||||
tags: Array<ItemTag>
|
||||
}
|
||||
|
||||
function initPlatformCounts(): PlatformCounts {
|
||||
@@ -34,7 +40,8 @@ function treeInit(): ItemTree {
|
||||
count: 0,
|
||||
children: [],
|
||||
boughtWhen: { aheadCount: 0, behindCount: 0, evenCount: 0, meanGold: 0 },
|
||||
platformCount: initPlatformCounts()
|
||||
platformCount: initPlatformCounts(),
|
||||
tags: []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +80,8 @@ function nodeMerge(itemtree: ItemTree, node: ItemTree) {
|
||||
count: count,
|
||||
children: [],
|
||||
boughtWhen: { ...node.boughtWhen },
|
||||
platformCount: { ...node.platformCount }
|
||||
platformCount: { ...node.platformCount },
|
||||
tags: []
|
||||
}
|
||||
itemtree.children.push(next)
|
||||
}
|
||||
@@ -107,7 +115,8 @@ function treeMerge(
|
||||
eun: platformKey === 'eun1' ? 1 : 0,
|
||||
na: platformKey === 'na1' ? 1 : 0,
|
||||
kr: platformKey === 'kr' ? 1 : 0
|
||||
}
|
||||
},
|
||||
tags: []
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -122,7 +131,8 @@ function treeCutBranches(itemtree: ItemTree, thresholdCount: number, thresholdPe
|
||||
count: +Infinity,
|
||||
children: [],
|
||||
boughtWhen: { aheadCount: 0, behindCount: 0, evenCount: 0, meanGold: 0 },
|
||||
platformCount: initPlatformCounts()
|
||||
platformCount: initPlatformCounts(),
|
||||
tags: []
|
||||
}
|
||||
)
|
||||
itemtree.children.splice(itemtree.children.indexOf(leastUsedBranch), 1)
|
||||
@@ -169,7 +179,8 @@ function treeClone(tree: ItemTree): ItemTree {
|
||||
eun: tree.platformCount.eun,
|
||||
na: tree.platformCount.na,
|
||||
kr: tree.platformCount.kr
|
||||
}
|
||||
},
|
||||
tags: [...tree.tags]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,14 +277,100 @@ function areTreeSimilars(t1: ItemTree, t2: ItemTree): number {
|
||||
return Math.max(0, Math.min(1, similarity))
|
||||
}
|
||||
|
||||
/*
|
||||
* Derive tags for an item based on purchase patterns
|
||||
* Tags are derived when a specific condition is dominant (>= 60% threshold)
|
||||
* For region tags, we compare against expected distribution to find items that are
|
||||
* significantly more popular in a region than expected
|
||||
*/
|
||||
function deriveTags(node: ItemTree, expectedRegionDistribution?: PlatformCounts): void {
|
||||
const tags: Array<ItemTag> = []
|
||||
|
||||
// Derive gold situation tags
|
||||
const totalGoldSituations =
|
||||
node.boughtWhen.aheadCount + node.boughtWhen.behindCount + node.boughtWhen.evenCount
|
||||
if (totalGoldSituations > 0) {
|
||||
const aheadPct = node.boughtWhen.aheadCount / totalGoldSituations
|
||||
const behindPct = node.boughtWhen.behindCount / totalGoldSituations
|
||||
|
||||
// Only tag if there's a dominant pattern (>= 60%)
|
||||
if (aheadPct >= 0.6) {
|
||||
tags.push('ahead')
|
||||
} else if (behindPct >= 0.6) {
|
||||
tags.push('behind')
|
||||
}
|
||||
}
|
||||
|
||||
// Derive region tags by comparing against expected distribution
|
||||
const totalRegionCount =
|
||||
node.platformCount.euw + node.platformCount.eun + node.platformCount.na + node.platformCount.kr
|
||||
if (totalRegionCount > 0 && expectedRegionDistribution) {
|
||||
const totalExpected =
|
||||
expectedRegionDistribution.euw +
|
||||
expectedRegionDistribution.eun +
|
||||
expectedRegionDistribution.na +
|
||||
expectedRegionDistribution.kr
|
||||
|
||||
if (totalExpected > 0) {
|
||||
// Calculate expected percentages
|
||||
const expectedEuwPct = expectedRegionDistribution.euw / totalExpected
|
||||
const expectedEunPct = expectedRegionDistribution.eun / totalExpected
|
||||
const expectedNaPct = expectedRegionDistribution.na / totalExpected
|
||||
const expectedKrPct = expectedRegionDistribution.kr / totalExpected
|
||||
|
||||
// Calculate actual percentages for this item
|
||||
const actualEuwPct = node.platformCount.euw / totalRegionCount
|
||||
const actualEunPct = node.platformCount.eun / totalRegionCount
|
||||
const actualNaPct = node.platformCount.na / totalRegionCount
|
||||
const actualKrPct = node.platformCount.kr / totalRegionCount
|
||||
|
||||
// Tag if the item is significantly more popular in a region (>= 1.5x expected rate)
|
||||
// and has a minimum absolute percentage (>= 10%)
|
||||
const SIGNIFICANCE_THRESHOLD = 1.5
|
||||
const MINIMUM_PCT = 0.1
|
||||
|
||||
if (actualEuwPct >= expectedEuwPct * SIGNIFICANCE_THRESHOLD && actualEuwPct >= MINIMUM_PCT) {
|
||||
tags.push('region_euw')
|
||||
}
|
||||
if (actualEunPct >= expectedEunPct * SIGNIFICANCE_THRESHOLD && actualEunPct >= MINIMUM_PCT) {
|
||||
tags.push('region_eun')
|
||||
}
|
||||
if (actualNaPct >= expectedNaPct * SIGNIFICANCE_THRESHOLD && actualNaPct >= MINIMUM_PCT) {
|
||||
tags.push('region_na')
|
||||
}
|
||||
if (actualKrPct >= expectedKrPct * SIGNIFICANCE_THRESHOLD && actualKrPct >= MINIMUM_PCT) {
|
||||
tags.push('region_kr')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node.tags = tags
|
||||
|
||||
// Recursively derive tags for children
|
||||
for (const child of node.children) {
|
||||
deriveTags(child, expectedRegionDistribution)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply tag derivation to an entire tree
|
||||
* expectedRegionDistribution: the total region distribution for the champion/lane,
|
||||
* used to detect items that are region-specific
|
||||
*/
|
||||
function treeDeriveTags(itemtree: ItemTree, expectedRegionDistribution?: PlatformCounts): void {
|
||||
deriveTags(itemtree, expectedRegionDistribution)
|
||||
}
|
||||
|
||||
export {
|
||||
ItemTree,
|
||||
PlatformCounts,
|
||||
GoldAdvantageTag,
|
||||
ItemTag,
|
||||
treeMerge,
|
||||
treeInit,
|
||||
treeCutBranches,
|
||||
treeSort,
|
||||
treeMergeTree,
|
||||
areTreeSimilars
|
||||
areTreeSimilars,
|
||||
treeDeriveTags
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user