@cs2dak/cohort 0.2.1 → 1.0.0
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.
- package/package.json +3 -3
- package/src/index.ts +59 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cs2dak/cohort",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"exports": {
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"@rivalhub/rival-rating": "^0.1.0",
|
|
11
|
-
"@cs2dak/contract": "0.
|
|
12
|
-
"@cs2dak/core": "0.
|
|
11
|
+
"@cs2dak/contract": "1.0.0",
|
|
12
|
+
"@cs2dak/core": "1.0.0"
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"src/index.ts"
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
deriveAccountSignalsV2,
|
|
3
|
+
derivePlayerWeaponHighlights,
|
|
3
4
|
deriveRRIndicators,
|
|
4
5
|
computeAccountRatingsV2
|
|
5
6
|
} from "@cs2dak/core";
|
|
@@ -9,6 +10,7 @@ import {
|
|
|
9
10
|
type AccountSignalsV2,
|
|
10
11
|
type DemoPackage,
|
|
11
12
|
type RRIndicators,
|
|
13
|
+
type PlayerWeaponHighlightFacts,
|
|
12
14
|
type SeasonCohortBundle,
|
|
13
15
|
type TeamKey,
|
|
14
16
|
type ValueAccountsWeights
|
|
@@ -57,6 +59,7 @@ interface PlayerAccumulator {
|
|
|
57
59
|
mapCount: number;
|
|
58
60
|
signals: AccountSignalsV2[];
|
|
59
61
|
indicators: RRIndicators[];
|
|
62
|
+
weaponHighlights: PlayerWeaponHighlightFacts[];
|
|
60
63
|
perMatch: Array<{ matchId: string; steamId64: string; accountRR: number; rrV1: number }>;
|
|
61
64
|
}
|
|
62
65
|
|
|
@@ -73,6 +76,7 @@ export function buildSeasonCohort(
|
|
|
73
76
|
for (const demo of demos) {
|
|
74
77
|
const signals = deriveAccountSignalsV2(demo.pkg);
|
|
75
78
|
const indicators = deriveRRIndicators(demo.pkg);
|
|
79
|
+
const weaponHighlights = derivePlayerWeaponHighlights(demo.pkg);
|
|
76
80
|
const matchAccounts = computeAccountRatingsV2(demo.pkg);
|
|
77
81
|
const matchAccountBySteamId = new Map(matchAccounts.map((row) => [row.signals.steamId64, row.rr]));
|
|
78
82
|
const rrBySteamId = new Map(indicators.map((row) => [row.steamId64, computeRR(row, rrWeights)]));
|
|
@@ -94,6 +98,7 @@ export function buildSeasonCohort(
|
|
|
94
98
|
mapCount: 0,
|
|
95
99
|
signals: [],
|
|
96
100
|
indicators: [],
|
|
101
|
+
weaponHighlights: [],
|
|
97
102
|
perMatch: []
|
|
98
103
|
}));
|
|
99
104
|
|
|
@@ -105,6 +110,8 @@ export function buildSeasonCohort(
|
|
|
105
110
|
acc.mapCount += 1;
|
|
106
111
|
acc.signals.push(signal);
|
|
107
112
|
acc.indicators.push(indicator);
|
|
113
|
+
const weaponHighlight = weaponHighlights.find((row) => row.steamId64 === player.steamId64);
|
|
114
|
+
if (weaponHighlight) acc.weaponHighlights.push(weaponHighlight);
|
|
108
115
|
acc.perMatch.push({
|
|
109
116
|
matchId: demo.matchId,
|
|
110
117
|
steamId64: player.steamId64,
|
|
@@ -140,9 +147,17 @@ export function buildSeasonCohort(
|
|
|
140
147
|
const prismResults = new Map(computePrism(prismInputs, prismWeights).map((row) => [row.steamId64, row]));
|
|
141
148
|
|
|
142
149
|
return seasonCohortBundleSchema.parse({
|
|
143
|
-
version: "cs2-demo-analysis-kit/
|
|
150
|
+
version: "cs2-demo-analysis-kit/cohort-1.0",
|
|
144
151
|
matchCount: demos.length,
|
|
145
152
|
weightsVersion: `${rrWeights.version}+${valueWeights.version}+${prismWeights.version}`,
|
|
153
|
+
provenance: {
|
|
154
|
+
cohortVersion: "cs2-demo-analysis-kit/cohort-1.0",
|
|
155
|
+
sourceSchemaVersion: "cs2-demo-format/2.0",
|
|
156
|
+
matches: demos.map((demo) => ({
|
|
157
|
+
matchId: demo.matchId,
|
|
158
|
+
sourceDemoHash: demo.pkg.manifest.demo?.hash ?? null
|
|
159
|
+
}))
|
|
160
|
+
},
|
|
146
161
|
players: seasonRows
|
|
147
162
|
.map((row) => {
|
|
148
163
|
const balanced = balancedByKey.get(row.acc.playerKey)!;
|
|
@@ -159,6 +174,7 @@ export function buildSeasonCohort(
|
|
|
159
174
|
rrV1: round(row.rrV1.rr, 3),
|
|
160
175
|
rrV1Percentile: round(rrToPercentile(rrV1Scores, row.rrV1.rr), 1),
|
|
161
176
|
indicators: row.indicators,
|
|
177
|
+
weaponHighlights: aggregateWeaponHighlights(row.acc.playerKey, row.acc.weaponHighlights),
|
|
162
178
|
accountRR: round(balanced.rr, 3),
|
|
163
179
|
accountRRRaw: round(balanced.rrRaw, 3),
|
|
164
180
|
accountBreakdown: {
|
|
@@ -174,7 +190,6 @@ export function buildSeasonCohort(
|
|
|
174
190
|
perMatch: row.acc.perMatch.sort((a, b) => a.matchId.localeCompare(b.matchId))
|
|
175
191
|
};
|
|
176
192
|
})
|
|
177
|
-
.sort((a, b) => b.accountRR - a.accountRR || b.rrV1 - a.rrV1 || a.name.localeCompare(b.name))
|
|
178
193
|
});
|
|
179
194
|
}
|
|
180
195
|
|
|
@@ -189,7 +204,7 @@ function aggregateAccountSignals(steamId64: string, rows: AccountSignalsV2[]): A
|
|
|
189
204
|
return {
|
|
190
205
|
steamId64,
|
|
191
206
|
rounds: sum(rows, (row) => row.rounds),
|
|
192
|
-
sourceVersion: "cs2-demo-analysis-kit/
|
|
207
|
+
sourceVersion: "cs2-demo-analysis-kit/cohort-1.0",
|
|
193
208
|
combat: {
|
|
194
209
|
kills: sum(rows, (row) => row.combat.kills),
|
|
195
210
|
deaths: sum(rows, (row) => row.combat.deaths),
|
|
@@ -333,6 +348,47 @@ function aggregateRRIndicators(steamId64: string, rows: RRIndicators[]): RRIndic
|
|
|
333
348
|
};
|
|
334
349
|
}
|
|
335
350
|
|
|
351
|
+
function aggregateWeaponHighlights(
|
|
352
|
+
steamId64: string,
|
|
353
|
+
rows: PlayerWeaponHighlightFacts[]
|
|
354
|
+
): PlayerWeaponHighlightFacts {
|
|
355
|
+
const weapons = new Map<string, PlayerWeaponHighlightFacts["weapons"][number]>();
|
|
356
|
+
for (const row of rows) {
|
|
357
|
+
for (const weapon of row.weapons) {
|
|
358
|
+
const current = weapons.get(weapon.weapon) ?? {
|
|
359
|
+
weapon: weapon.weapon,
|
|
360
|
+
kills: 0,
|
|
361
|
+
headshotKills: 0,
|
|
362
|
+
tradeKills: 0,
|
|
363
|
+
noScopeKills: 0,
|
|
364
|
+
throughSmokeKills: 0,
|
|
365
|
+
wallbangKills: 0,
|
|
366
|
+
penetratedObjects: 0
|
|
367
|
+
};
|
|
368
|
+
current.kills += weapon.kills;
|
|
369
|
+
current.headshotKills += weapon.headshotKills;
|
|
370
|
+
current.tradeKills += weapon.tradeKills;
|
|
371
|
+
current.noScopeKills += weapon.noScopeKills;
|
|
372
|
+
current.throughSmokeKills += weapon.throughSmokeKills;
|
|
373
|
+
current.wallbangKills += weapon.wallbangKills;
|
|
374
|
+
current.penetratedObjects += weapon.penetratedObjects;
|
|
375
|
+
weapons.set(weapon.weapon, current);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
steamId64,
|
|
380
|
+
totalKills: sum(rows, (row) => row.totalKills),
|
|
381
|
+
weapons: [...weapons.values()]
|
|
382
|
+
.sort((a, b) => b.kills - a.kills || a.weapon.localeCompare(b.weapon)),
|
|
383
|
+
highlights: {
|
|
384
|
+
wallbangKills: sumNullable(rows.map((row) => row.highlights.wallbangKills)),
|
|
385
|
+
noScopeKills: sumNullable(rows.map((row) => row.highlights.noScopeKills)),
|
|
386
|
+
throughSmokeKills: sumNullable(rows.map((row) => row.highlights.throughSmokeKills)),
|
|
387
|
+
collateralKills: sumNullable(rows.map((row) => row.highlights.collateralKills))
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
336
392
|
// 账户平衡的数学(标准化 + 残差化)已迁入 rival-rating 的 computeCohortAccountsRR(公式归属)。
|
|
337
393
|
// 本层只负责把 std(rrV1) 作为 targetStd 传进去,对齐 v2 与 HLTV 逆向的量纲。
|
|
338
394
|
function stdev(xs: number[]): number {
|