Compare commits
3 Commits
686962b678
...
5d8320fe8a
| Author | SHA1 | Date | |
|---|---|---|---|
|
5d8320fe8a
|
|||
|
c7d0d929be
|
|||
|
8ee981b949
|
@@ -9,14 +9,19 @@ const readFileAsync = promisify(readFile)
|
||||
const CDRAGON_BASE = 'https://raw.communitydragon.org/'
|
||||
|
||||
// Cache directory - can be configured via environment variable
|
||||
// Default to dev/cdragon for development
|
||||
// In development, use dev/data/cdragon relative to project root
|
||||
// In production, use /cdragon (shared volume)
|
||||
const getCacheDir = () => {
|
||||
if (process.env.CDRAGON_CACHE_DIR) {
|
||||
return process.env.CDRAGON_CACHE_DIR
|
||||
}
|
||||
// Default to dev/cdragon relative to project root
|
||||
// Check if we're in development mode (explicitly set)
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
return join(process.cwd(), '..', 'dev', 'data', 'cdragon')
|
||||
}
|
||||
// Default to /cdragon for production (Docker)
|
||||
return '/cdragon'
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current patch from the patch.txt file or fallback to 'latest'
|
||||
|
||||
@@ -46,7 +46,8 @@ async function downloadCDragonAssets(patch: string) {
|
||||
console.log(`\n=== Downloading CDragon assets for patch ${cdragonPatch} ===`)
|
||||
|
||||
// Get cache directory from environment or use default
|
||||
// In development, use a local directory relative to project root; in production (Docker), use /cdragon
|
||||
// In development, use a local directory relative to project root
|
||||
// In production (Docker), use /cdragon (shared volume with frontend)
|
||||
const defaultCacheDir =
|
||||
process.env.NODE_ENV === 'development'
|
||||
? resolve(__dirname, '../../dev/data/cdragon')
|
||||
|
||||
@@ -177,7 +177,7 @@ function handleMatchBuilds(
|
||||
participantIndex: number,
|
||||
builds: Builds,
|
||||
platform?: string
|
||||
): Build {
|
||||
): { build: Build; startItemId: number | undefined } {
|
||||
const timeline: Timeline = match.timeline
|
||||
|
||||
// Find or create the build for this participant's rune configuration
|
||||
@@ -185,6 +185,7 @@ function handleMatchBuilds(
|
||||
build.count += 1
|
||||
|
||||
const items: Array<{ itemId: number; goldAdvantage: GoldAdvantageTag; platform?: string }> = []
|
||||
let startItemId: number | undefined = undefined
|
||||
for (const frame of timeline.info.frames) {
|
||||
for (const event of frame.events) {
|
||||
if (event.participantId != participantIndex) continue
|
||||
@@ -267,9 +268,11 @@ function handleMatchBuilds(
|
||||
// This tree includes start item as the root, then branching paths
|
||||
if (items.length > 0) {
|
||||
treeMerge(build.items, items)
|
||||
// The first item is the starter item
|
||||
startItemId = items[0].itemId
|
||||
}
|
||||
|
||||
return build
|
||||
return { build, startItemId }
|
||||
}
|
||||
|
||||
function handleMatch(match: Match, champions: Map<number, ChampionData>, platform?: string) {
|
||||
@@ -365,14 +368,22 @@ function handleMatch(match: Match, champions: Map<number, ChampionData>, platfor
|
||||
}
|
||||
|
||||
// Items and runes (builds)
|
||||
const build = handleMatchBuilds(match, participant, participantIndex, lane.builds, platform)
|
||||
const { build, startItemId } = handleMatchBuilds(
|
||||
match,
|
||||
participant,
|
||||
participantIndex,
|
||||
lane.builds,
|
||||
platform
|
||||
)
|
||||
|
||||
// First back data - store at build level
|
||||
// First back data - store at build level with start item tracking
|
||||
const firstBackData = extractFirstBackFromMatch(match, participantIndex)
|
||||
if (firstBackData) {
|
||||
if (!build.firstBacksRaw) {
|
||||
build.firstBacksRaw = []
|
||||
}
|
||||
// Include the starter item ID for proper filtering when splitting builds
|
||||
firstBackData.startItemId = startItemId
|
||||
build.firstBacksRaw.push(firstBackData)
|
||||
}
|
||||
}
|
||||
@@ -445,16 +456,44 @@ function splitMergeOnStarterItem(build: Build, championName: string): BuildWithS
|
||||
console.log(`Warning: for champion ${championName}, start item splits build variant.`)
|
||||
const builds = []
|
||||
for (const c of build.items.children) {
|
||||
// Calculate the ratio for proportional distribution
|
||||
const ratio = c.count / build.count
|
||||
|
||||
// Proportionally distribute boots counts
|
||||
const scaledBoots = build.boots.map(b => ({
|
||||
data: b.data,
|
||||
count: Math.round(b.count * ratio)
|
||||
}))
|
||||
|
||||
// Proportionally distribute suppItems counts
|
||||
const scaledSuppItems = build.suppItems.map(s => ({
|
||||
data: s.data,
|
||||
count: Math.round(s.count * ratio)
|
||||
}))
|
||||
|
||||
// Proportionally distribute bootsFirstCount
|
||||
const scaledBootsFirstCount = Math.round(build.bootsFirstCount * ratio)
|
||||
|
||||
// Filter firstBacksRaw by starter item
|
||||
let filteredFirstBacksRaw: FirstBackData[] | undefined
|
||||
if (build.firstBacksRaw && build.firstBacksRaw.length > 0) {
|
||||
// Filter by the starter item ID that was tracked when storing firstBacksRaw
|
||||
filteredFirstBacksRaw = build.firstBacksRaw.filter(fb => fb.startItemId === c.data)
|
||||
if (filteredFirstBacksRaw.length === 0) {
|
||||
filteredFirstBacksRaw = undefined
|
||||
}
|
||||
}
|
||||
|
||||
builds.push({
|
||||
runeKeystone: build.runeKeystone,
|
||||
runes: build.runes,
|
||||
items: c,
|
||||
bootsFirstCount: build.bootsFirstCount,
|
||||
bootsFirstCount: scaledBootsFirstCount,
|
||||
count: c.count,
|
||||
startItems: [{ data: c.data!, count: c.count }],
|
||||
suppItems: build.suppItems,
|
||||
boots: build.boots,
|
||||
firstBacksRaw: build.firstBacksRaw
|
||||
suppItems: scaledSuppItems,
|
||||
boots: scaledBoots,
|
||||
firstBacksRaw: filteredFirstBacksRaw
|
||||
})
|
||||
c.data = undefined
|
||||
}
|
||||
|
||||
@@ -264,12 +264,8 @@ function deriveTags(node: ItemTree, expectedRegionDistribution?: PlatformCounts)
|
||||
const totalExpected = REGION_KEYS.reduce((sum, key) => sum + expectedRegionDistribution[key], 0)
|
||||
|
||||
if (totalExpected > 0) {
|
||||
// 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
|
||||
|
||||
// Loop through all regions to derive tags
|
||||
// Tag if one region accounts for >= 60% of the normalized distribution
|
||||
// Normalized value = actual percentage / expected percentage ratio
|
||||
const regionTags: Array<{ key: keyof PlatformCounts; tag: ItemTag }> = [
|
||||
{ key: 'euw', tag: 'region_euw' },
|
||||
{ key: 'eun', tag: 'region_eun' },
|
||||
@@ -277,12 +273,23 @@ function deriveTags(node: ItemTree, expectedRegionDistribution?: PlatformCounts)
|
||||
{ key: 'kr', tag: 'region_kr' }
|
||||
]
|
||||
|
||||
for (const { key, tag } of regionTags) {
|
||||
// Calculate normalized values (actual/expected ratio) for each region
|
||||
const normalizedValues = regionTags.map(({ key, tag }) => {
|
||||
const expectedPct = expectedRegionDistribution[key] / totalExpected
|
||||
const actualPct = node.platformCount[key] / totalRegionCount
|
||||
const normalizedValue = expectedPct > 0 ? actualPct / expectedPct : 0
|
||||
return { tag, value: normalizedValue }
|
||||
})
|
||||
|
||||
if (actualPct >= expectedPct * SIGNIFICANCE_THRESHOLD && actualPct >= MINIMUM_PCT) {
|
||||
const totalNormalized = normalizedValues.reduce((sum, { value }) => sum + value, 0)
|
||||
|
||||
// Tag the region if it accounts for >= 60% of the normalized distribution
|
||||
if (totalNormalized > 0) {
|
||||
for (const { tag, value } of normalizedValues) {
|
||||
if (value / totalNormalized >= 0.6) {
|
||||
tags.push(tag)
|
||||
break // Only tag the most dominant region
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ export interface ItemSet {
|
||||
export interface FirstBackData {
|
||||
timestamp: number
|
||||
itemSet: ItemSet
|
||||
startItemId?: number
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user