wows-compute 1.0.0 → 1.1.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/README.md +128 -110
- package/dist/index-CkKx_Wtz.d.mts +286 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -1
- package/dist/strength-CBihFxZD.mjs +243 -0
- package/dist/strength.d.mts +2 -79
- package/dist/strength.mjs +2 -153
- package/package.json +1 -1
- package/dist/index-DoUlnTgG.d.mts +0 -157
package/README.md
CHANGED
|
@@ -1,110 +1,128 @@
|
|
|
1
|
-
# wows-compute
|
|
2
|
-
|
|
3
|
-
《战舰世界》对局数据的纯计算内核。无文件 IO、无网络、无运行时状态:输入是已经解析好的赛前名单或赛后回放结果,输出是可直接持久化或展示的 `MatchRecord`、评级与统计。
|
|
4
|
-
|
|
5
|
-
已提供:`./match`、评级、时间、`./recent`、`./aggregate`、`./metrics`、`./strength
|
|
6
|
-
|
|
7
|
-
## 安装
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
pnpm add wows-compute wows-number-api-sdk
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
ESM-only,Node.js 22.18+。
|
|
14
|
-
|
|
15
|
-
## 入口
|
|
16
|
-
|
|
17
|
-
| 入口 | 内容 |
|
|
18
|
-
| --- | --- |
|
|
19
|
-
| `wows-compute` | PR / PR
|
|
20
|
-
| `wows-compute/match` | `ArenaRoster`、`MatchRecord`、合并、赛前赛后关联、组队 |
|
|
21
|
-
| `wows-compute/metrics` | 字段字典与派生指标 |
|
|
22
|
-
| `wows-compute/strength` |
|
|
23
|
-
|
|
24
|
-
## 快速上手
|
|
25
|
-
|
|
26
|
-
```ts
|
|
27
|
-
import { fromReplay, groupDivisions } from 'wows-compute/match'
|
|
28
|
-
|
|
29
|
-
const record = fromReplay(parsedReplay)
|
|
30
|
-
// record.arenaId、record.outcome、record.quality、record.participants
|
|
31
|
-
|
|
32
|
-
const divisions = groupDivisions(record.participants)
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
`fromReplay` 接受 `wows-replay-parser` 的 `ParsedReplay` 结构,不依赖它的包,只有类型层面的结构兼容。
|
|
36
|
-
|
|
37
|
-
### 平局
|
|
38
|
-
|
|
39
|
-
事实层保留 `outcome: 'draw'`;统计层用 `statOutcome('draw')` 映射为 `loss`,即平局双输。`winnerTeamId` 为负数表示平局,`0` 是合法队伍。
|
|
40
|
-
|
|
41
|
-
### 时间
|
|
42
|
-
|
|
43
|
-
`battleTime` 优先取结果包的 `startDt`(UTC)。回放元数据的 `dateTime` 是录制者本机时间,跨区不可靠,只在没有结果包时作为回退。
|
|
44
|
-
|
|
45
|
-
### 评级
|
|
46
|
-
|
|
47
|
-
```ts
|
|
48
|
-
import { accountPrEstimated, accountPrPooled, levelOf, shipPr } from 'wows-compute'
|
|
49
|
-
|
|
50
|
-
const ship = shipPr(stats, expected) // 精确,exact: true
|
|
51
|
-
const account = accountPrEstimated(totals, expected) // PR*,exact: false
|
|
52
|
-
const pooled = accountPrPooled(ships, expected, sample) // 精确加权,exact: true
|
|
53
|
-
|
|
54
|
-
levelOf('pr', 1200) // { levelKey: 'average', floor: 1100, ... }
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
`exact: false` 是产品承诺:调用方必须把它显示为 `PR*`,不能与精确 PR 不加区分地混排。偏差方向通过 `bias` 输出;没有 `shipMix` 时是 `unknown`,不猜。
|
|
58
|
-
|
|
59
|
-
PR 公式、预期值和分档阈值全部复用 `wows-number-api-sdk`,本包不复制常量。
|
|
60
|
-
|
|
61
|
-
### 近期与聚合
|
|
62
|
-
|
|
63
|
-
```ts
|
|
64
|
-
import { aggregateTeam, recentMatches } from 'wows-compute'
|
|
65
|
-
|
|
66
|
-
const recent = recentMatches(records, { subject: { kind: 'self' }, last: 20 }, expected)
|
|
67
|
-
const team = aggregateTeam(records, teamId, {
|
|
68
|
-
weighting: 'equal', // 监控页:每个席位一票
|
|
69
|
-
prAggregation: 'mean',
|
|
70
|
-
expected,
|
|
71
|
-
})
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
- 近期只统计“已解析并入库”的对局,不扫描历史回放、不补缺口。
|
|
75
|
-
- `winRate` 是百分数,例如 `54.3`。
|
|
76
|
-
- 平局计入 `battles`,胜率为 `wins / battles`;`losses` 只计实际失败。
|
|
77
|
-
- 记录总览用 `weighting: 'battles'` + `prAggregation: 'pooled'`;监控页用 `equal` + `mean`。
|
|
78
|
-
- 分母为 0 返回 `null`;`coverage` 的 `resolved + hidden + bot + failed` 恒等于 `total`。
|
|
79
|
-
|
|
80
|
-
### 指标字典
|
|
81
|
-
|
|
82
|
-
```ts
|
|
83
|
-
import { METRICS, deriveMetrics } from 'wows-compute/metrics'
|
|
84
|
-
|
|
85
|
-
deriveMetrics(player.result.fields) // { damage, frags, potentialDamage, ... }
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
指标只做字段语义包装,不复制原始数据。新增字段必须能在回放字段映射中找到,测试会先失败。
|
|
89
|
-
|
|
90
|
-
### 对局强度原料
|
|
91
|
-
|
|
92
|
-
```ts
|
|
93
|
-
import { buildPlayerPoints, pairByRole, referenceLines } from 'wows-compute/strength'
|
|
94
|
-
|
|
95
|
-
const points = buildPlayerPoints(roster, ratings, catalog)
|
|
96
|
-
const reference = referenceLines(points)
|
|
97
|
-
const matchups = pairByRole(allyPoints, enemyPoints)
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
只输出散点原料、参考线和逐角色差值,不输出综合强度分或预测。
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
1
|
+
# wows-compute
|
|
2
|
+
|
|
3
|
+
《战舰世界》对局数据的纯计算内核。无文件 IO、无网络、无运行时状态:输入是已经解析好的赛前名单或赛后回放结果,输出是可直接持久化或展示的 `MatchRecord`、评级与统计。
|
|
4
|
+
|
|
5
|
+
已提供:`./match`、评级、时间、`./recent`、`./aggregate`、`./metrics`、`./strength`(含概览归纳)。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add wows-compute wows-number-api-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
ESM-only,Node.js 22.18+。
|
|
14
|
+
|
|
15
|
+
## 入口
|
|
16
|
+
|
|
17
|
+
| 入口 | 内容 |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| `wows-compute` | PR / PR*、等级、近期统计、聚合、概览归纳、游戏日与周界 |
|
|
20
|
+
| `wows-compute/match` | `ArenaRoster`、`MatchRecord`、合并、赛前赛后关联、组队 |
|
|
21
|
+
| `wows-compute/metrics` | 字段字典与派生指标 |
|
|
22
|
+
| `wows-compute/strength` | 散点原料、概览归纳、参考线、角色配对 |
|
|
23
|
+
|
|
24
|
+
## 快速上手
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { fromReplay, groupDivisions } from 'wows-compute/match'
|
|
28
|
+
|
|
29
|
+
const record = fromReplay(parsedReplay)
|
|
30
|
+
// record.arenaId、record.outcome、record.quality、record.participants
|
|
31
|
+
|
|
32
|
+
const divisions = groupDivisions(record.participants)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`fromReplay` 接受 `wows-replay-parser` 的 `ParsedReplay` 结构,不依赖它的包,只有类型层面的结构兼容。
|
|
36
|
+
|
|
37
|
+
### 平局
|
|
38
|
+
|
|
39
|
+
事实层保留 `outcome: 'draw'`;统计层用 `statOutcome('draw')` 映射为 `loss`,即平局双输。`winnerTeamId` 为负数表示平局,`0` 是合法队伍。
|
|
40
|
+
|
|
41
|
+
### 时间
|
|
42
|
+
|
|
43
|
+
`battleTime` 优先取结果包的 `startDt`(UTC)。回放元数据的 `dateTime` 是录制者本机时间,跨区不可靠,只在没有结果包时作为回退。
|
|
44
|
+
|
|
45
|
+
### 评级
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { accountPrEstimated, accountPrPooled, levelOf, shipPr } from 'wows-compute'
|
|
49
|
+
|
|
50
|
+
const ship = shipPr(stats, expected) // 精确,exact: true
|
|
51
|
+
const account = accountPrEstimated(totals, expected) // PR*,exact: false
|
|
52
|
+
const pooled = accountPrPooled(ships, expected, sample) // 精确加权,exact: true
|
|
53
|
+
|
|
54
|
+
levelOf('pr', 1200) // { levelKey: 'average', floor: 1100, ... }
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`exact: false` 是产品承诺:调用方必须把它显示为 `PR*`,不能与精确 PR 不加区分地混排。偏差方向通过 `bias` 输出;没有 `shipMix` 时是 `unknown`,不猜。
|
|
58
|
+
|
|
59
|
+
PR 公式、预期值和分档阈值全部复用 `wows-number-api-sdk`,本包不复制常量。
|
|
60
|
+
|
|
61
|
+
### 近期与聚合
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { aggregateTeam, recentMatches } from 'wows-compute'
|
|
65
|
+
|
|
66
|
+
const recent = recentMatches(records, { subject: { kind: 'self' }, last: 20 }, expected)
|
|
67
|
+
const team = aggregateTeam(records, teamId, {
|
|
68
|
+
weighting: 'equal', // 监控页:每个席位一票
|
|
69
|
+
prAggregation: 'mean',
|
|
70
|
+
expected,
|
|
71
|
+
})
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- 近期只统计“已解析并入库”的对局,不扫描历史回放、不补缺口。
|
|
75
|
+
- `winRate` 是百分数,例如 `54.3`。
|
|
76
|
+
- 平局计入 `battles`,胜率为 `wins / battles`;`losses` 只计实际失败。
|
|
77
|
+
- 记录总览用 `weighting: 'battles'` + `prAggregation: 'pooled'`;监控页用 `equal` + `mean`。
|
|
78
|
+
- 分母为 0 返回 `null`;`coverage` 的 `resolved + hidden + bot + failed` 恒等于 `total`。
|
|
79
|
+
|
|
80
|
+
### 指标字典
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { METRICS, deriveMetrics } from 'wows-compute/metrics'
|
|
84
|
+
|
|
85
|
+
deriveMetrics(player.result.fields) // { damage, frags, potentialDamage, ... }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
指标只做字段语义包装,不复制原始数据。新增字段必须能在回放字段映射中找到,测试会先失败。
|
|
89
|
+
|
|
90
|
+
### 对局强度原料
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { buildPlayerPoints, pairByRole, referenceLines } from 'wows-compute/strength'
|
|
94
|
+
|
|
95
|
+
const points = buildPlayerPoints(roster, ratings, catalog)
|
|
96
|
+
const reference = referenceLines(points)
|
|
97
|
+
const matchups = pairByRole(allyPoints, enemyPoints)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
只输出散点原料、参考线和逐角色差值,不输出综合强度分或预测。
|
|
101
|
+
|
|
102
|
+
### 队伍概览
|
|
103
|
+
|
|
104
|
+
`summarizePlayers` / `summarizeRoster` 对已经标准化的 `PlayerPoint` 做加权归纳,输入不是对局记录:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { buildPlayerPoints, summarizeRoster } from 'wows-compute/strength'
|
|
108
|
+
|
|
109
|
+
const points = buildPlayerPoints(roster, ratings, catalog)
|
|
110
|
+
const { ally, enemy, unassigned } = summarizeRoster(points)
|
|
111
|
+
// ally.players、ally.battles、ally.winRate / avgDamage / avgFrags / pr、ally.coverage
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
- 与 `aggregate*` 的分工:`aggregate*` 从逐局 `MatchRecord` 推导统计;overview 只归纳已算好的值,赛前赛后可复用。
|
|
115
|
+
- 默认 `weighting: 'equal'`(一人一票),可改为 `'battles'` 按生涯场次加权。
|
|
116
|
+
- 每项均值只使用非 null 值,`0` 是合法值,分母为 0 返回 `null`。
|
|
117
|
+
- `coverage` 的单位是席位:`total` 含 Bot,`players` 只算真人,且 `resolved + hidden + bot + failed === total`。
|
|
118
|
+
- 概览 PR 是对账号 PR 的均值,`prExact` 恒为 `false`,展示时必须标 `PR*`。
|
|
119
|
+
|
|
120
|
+
## 约束
|
|
121
|
+
|
|
122
|
+
- 不读写文件、不访问网络、不 import `node:*`。
|
|
123
|
+
- 不扫描历史回放、不推断缺失对局、不做存储假设。
|
|
124
|
+
- 只读已解析的数据;需要回放解析时使用 `wows-replay-parser`。
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
MIT
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { c as MatchRecord, i as ArenaRoster, o as MatchGroup, p as Relation } from "./types-DwmuyfXs.mjs";
|
|
2
|
+
import { ExpectedValuesDocument, ShipBattleStats, SingleBattleInput } from "wows-number-api-sdk";
|
|
3
|
+
//#region src/rating/index.d.ts
|
|
4
|
+
interface SampleDescriptor {
|
|
5
|
+
kind: 'ingested' | 'lifetime' | 'match';
|
|
6
|
+
matches: number;
|
|
7
|
+
/** ISO timestamps; present for date windows. */
|
|
8
|
+
from?: string;
|
|
9
|
+
to?: string;
|
|
10
|
+
/** When querying another player: matches shared with the local player. */
|
|
11
|
+
sharedWith?: number;
|
|
12
|
+
players?: number;
|
|
13
|
+
}
|
|
14
|
+
interface EstimateBias {
|
|
15
|
+
direction: 'underestimate' | 'overestimate' | 'mixed' | 'unknown';
|
|
16
|
+
reason: 'ship-mix' | 'insufficient-data';
|
|
17
|
+
/** Relative baseline difference: pooled baseline / global average - 1. */
|
|
18
|
+
delta?: {
|
|
19
|
+
damage?: number;
|
|
20
|
+
frags?: number;
|
|
21
|
+
wins?: number;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
interface RatedValue {
|
|
25
|
+
value: number;
|
|
26
|
+
basis: 'ship' | 'account';
|
|
27
|
+
exact: boolean;
|
|
28
|
+
sample: SampleDescriptor;
|
|
29
|
+
/** Expected-values version, taken from `expected.time`. */
|
|
30
|
+
baselineVersion: string;
|
|
31
|
+
/** Only present when `exact === false`. */
|
|
32
|
+
bias?: EstimateBias;
|
|
33
|
+
}
|
|
34
|
+
interface AccountTotals {
|
|
35
|
+
battles: number;
|
|
36
|
+
damage: number;
|
|
37
|
+
frags: number;
|
|
38
|
+
wins: number;
|
|
39
|
+
}
|
|
40
|
+
interface ShipMixEntry {
|
|
41
|
+
shipId: number | string;
|
|
42
|
+
battles: number;
|
|
43
|
+
}
|
|
44
|
+
interface AccountPrOptions {
|
|
45
|
+
shipMix?: readonly ShipMixEntry[];
|
|
46
|
+
}
|
|
47
|
+
/** Personal Rating for one ship, using that ship's own expected values. */
|
|
48
|
+
declare function shipPr(stats: ShipBattleStats, expected: ExpectedValuesDocument, sample?: SampleDescriptor): RatedValue | null;
|
|
49
|
+
/** Personal Rating for a single battle. */
|
|
50
|
+
declare function battlePr(battle: SingleBattleInput, expected: ExpectedValuesDocument, sample?: SampleDescriptor): RatedValue | null;
|
|
51
|
+
/**
|
|
52
|
+
* Fast account PR*: account totals against the unweighted average of all
|
|
53
|
+
* expected values.
|
|
54
|
+
*
|
|
55
|
+
* This is an estimate, not a different formula: the account is treated as one
|
|
56
|
+
* synthetic ship. `exact: false` is a product promise — UI must mark it `PR*`.
|
|
57
|
+
*/
|
|
58
|
+
declare function accountPrEstimated(account: AccountTotals, expected: ExpectedValuesDocument, options?: AccountPrOptions): RatedValue | null;
|
|
59
|
+
/**
|
|
60
|
+
* Exact weighted account PR: pools per-ship totals and per-ship baselines.
|
|
61
|
+
*
|
|
62
|
+
* Ships without expected values are skipped by `calculatePR`, not counted
|
|
63
|
+
* as zero.
|
|
64
|
+
*/
|
|
65
|
+
declare function accountPrPooled(ships: readonly ShipBattleStats[], expected: ExpectedValuesDocument, sample: SampleDescriptor): RatedValue | null;
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/aggregate/index.d.ts
|
|
68
|
+
type Weighting = 'equal' | 'battles';
|
|
69
|
+
type PrAggregation = 'mean' | 'pooled';
|
|
70
|
+
interface Coverage {
|
|
71
|
+
total: number;
|
|
72
|
+
resolved: number;
|
|
73
|
+
hidden: number;
|
|
74
|
+
bot: number;
|
|
75
|
+
failed: number;
|
|
76
|
+
}
|
|
77
|
+
interface AggregatedValue {
|
|
78
|
+
value: number | null;
|
|
79
|
+
sample: SampleDescriptor;
|
|
80
|
+
coverage: Coverage;
|
|
81
|
+
}
|
|
82
|
+
interface AggregateOptions {
|
|
83
|
+
weighting: Weighting;
|
|
84
|
+
prAggregation: PrAggregation;
|
|
85
|
+
expected?: ExpectedValuesDocument;
|
|
86
|
+
matchGroups?: readonly MatchGroup[];
|
|
87
|
+
from?: number;
|
|
88
|
+
to?: number;
|
|
89
|
+
}
|
|
90
|
+
interface PlayerAggregate {
|
|
91
|
+
accountId: number;
|
|
92
|
+
name: string | null;
|
|
93
|
+
battles: number;
|
|
94
|
+
wins: number;
|
|
95
|
+
losses: number;
|
|
96
|
+
draws: number;
|
|
97
|
+
damage: number;
|
|
98
|
+
frags: number;
|
|
99
|
+
winRate: AggregatedValue;
|
|
100
|
+
avgDamage: AggregatedValue;
|
|
101
|
+
avgFrags: AggregatedValue;
|
|
102
|
+
pr: AggregatedValue;
|
|
103
|
+
}
|
|
104
|
+
interface TeamAggregate {
|
|
105
|
+
teamId: number | null;
|
|
106
|
+
players: number;
|
|
107
|
+
coverage: Coverage;
|
|
108
|
+
values: {
|
|
109
|
+
winRate: AggregatedValue;
|
|
110
|
+
avgDamage: AggregatedValue;
|
|
111
|
+
avgFrags: AggregatedValue;
|
|
112
|
+
pr: AggregatedValue;
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
interface ShipAggregate {
|
|
116
|
+
shipId: number;
|
|
117
|
+
battles: number;
|
|
118
|
+
wins: number;
|
|
119
|
+
losses: number;
|
|
120
|
+
draws: number;
|
|
121
|
+
damage: number;
|
|
122
|
+
frags: number;
|
|
123
|
+
winRate: AggregatedValue;
|
|
124
|
+
avgDamage: AggregatedValue;
|
|
125
|
+
avgFrags: AggregatedValue;
|
|
126
|
+
pr: AggregatedValue;
|
|
127
|
+
}
|
|
128
|
+
interface DailyPoint {
|
|
129
|
+
day: string;
|
|
130
|
+
matches: number;
|
|
131
|
+
battles: number;
|
|
132
|
+
wins: number;
|
|
133
|
+
losses: number;
|
|
134
|
+
draws: number;
|
|
135
|
+
winRate: number | null;
|
|
136
|
+
avgDamage: number | null;
|
|
137
|
+
avgFrags: number | null;
|
|
138
|
+
pr: AggregatedValue;
|
|
139
|
+
}
|
|
140
|
+
interface DailyOptions {
|
|
141
|
+
timeZone: string;
|
|
142
|
+
boundaryHour?: number;
|
|
143
|
+
expected?: ExpectedValuesDocument;
|
|
144
|
+
matchGroups?: readonly MatchGroup[];
|
|
145
|
+
from?: number;
|
|
146
|
+
to?: number;
|
|
147
|
+
}
|
|
148
|
+
/** Per-player aggregates over the selected records. Ordered by battles, then account id. */
|
|
149
|
+
declare function aggregatePlayers(records: readonly MatchRecord[], options: AggregateOptions): PlayerAggregate[];
|
|
150
|
+
/** One team's aggregate; coverage only counts that team's participants. */
|
|
151
|
+
declare function aggregateTeam(records: readonly MatchRecord[], teamId: number | null, options: AggregateOptions): TeamAggregate;
|
|
152
|
+
/** Per-ship aggregates over the selected records. Ordered by battles, then ship id. */
|
|
153
|
+
declare function aggregateShips(records: readonly MatchRecord[], options: AggregateOptions): ShipAggregate[];
|
|
154
|
+
/** Self performance per game day, ordered oldest first. */
|
|
155
|
+
declare function dailySeries(records: readonly MatchRecord[], options: DailyOptions): DailyPoint[];
|
|
156
|
+
//#endregion
|
|
157
|
+
//#region src/strength/index.d.ts
|
|
158
|
+
type ShipRole = 'submarine' | 'aircraft-carrier' | 'destroyer' | 'battleship' | 'cruiser-functional' | 'cruiser-firepower' | 'cruiser' | 'unknown';
|
|
159
|
+
/** Display / analysis order; not a composite score. */
|
|
160
|
+
declare const ROLE_ORDER: readonly ShipRole[];
|
|
161
|
+
interface ShipCatalogLike {
|
|
162
|
+
role?: (shipId: number) => ShipRole | null;
|
|
163
|
+
tier?: (shipId: number) => number | null;
|
|
164
|
+
}
|
|
165
|
+
interface PlayerRatingSnapshot {
|
|
166
|
+
pr?: RatedValue | null;
|
|
167
|
+
/** Lifetime win rate in percent, e.g. `54.3`. */
|
|
168
|
+
winRate?: number | null;
|
|
169
|
+
battles?: number | null;
|
|
170
|
+
/** Lifetime average damage; `null` when unavailable. */
|
|
171
|
+
avgDamage?: number | null;
|
|
172
|
+
/** Lifetime average frags; `null` when unavailable. */
|
|
173
|
+
avgFrags?: number | null;
|
|
174
|
+
/** Platform-flagged hidden profile, distinct from "no data". */
|
|
175
|
+
hidden?: boolean;
|
|
176
|
+
}
|
|
177
|
+
type PlayerRatingValue = RatedValue | PlayerRatingSnapshot;
|
|
178
|
+
type PlayerRatings = ReadonlyMap<number, PlayerRatingValue>;
|
|
179
|
+
interface PlayerPoint {
|
|
180
|
+
accountId: number | null;
|
|
181
|
+
name: string | null;
|
|
182
|
+
/** `null` for pre-battle rosters; relations are known, team ids are not. */
|
|
183
|
+
teamId: number | null;
|
|
184
|
+
shipId: number | null;
|
|
185
|
+
shipRole: ShipRole;
|
|
186
|
+
tier: number | null;
|
|
187
|
+
divisionId: number | null;
|
|
188
|
+
/** Percentage, e.g. `54.3`. */
|
|
189
|
+
winRate: number | null;
|
|
190
|
+
pr: number | null;
|
|
191
|
+
prExact: boolean;
|
|
192
|
+
battles: number | null;
|
|
193
|
+
isBot: boolean;
|
|
194
|
+
/** Roster relation; used to group points into sides. */
|
|
195
|
+
relation: Relation;
|
|
196
|
+
avgDamage: number | null;
|
|
197
|
+
avgFrags: number | null;
|
|
198
|
+
hidden: boolean;
|
|
199
|
+
}
|
|
200
|
+
interface ReferenceLines {
|
|
201
|
+
winRateMedian: number | null;
|
|
202
|
+
prMedian: number | null;
|
|
203
|
+
}
|
|
204
|
+
type Quadrant = 'q1' | 'q2' | 'q3' | 'q4' | 'unknown';
|
|
205
|
+
interface RoleWeightProfile {
|
|
206
|
+
weights?: Partial<Record<ShipRole, number>>;
|
|
207
|
+
}
|
|
208
|
+
interface RoleMatchup {
|
|
209
|
+
role: ShipRole;
|
|
210
|
+
allyCount: number;
|
|
211
|
+
enemyCount: number;
|
|
212
|
+
allyMedianPr: number | null;
|
|
213
|
+
enemyMedianPr: number | null;
|
|
214
|
+
allyMedianWinRate: number | null;
|
|
215
|
+
enemyMedianWinRate: number | null;
|
|
216
|
+
/** Ally median minus enemy median; `null` when either side lacks values. */
|
|
217
|
+
prDiff: number | null;
|
|
218
|
+
winRateDiff: number | null;
|
|
219
|
+
weight: number | null;
|
|
220
|
+
sample: {
|
|
221
|
+
ally: number;
|
|
222
|
+
enemy: number;
|
|
223
|
+
};
|
|
224
|
+
coverage: Coverage;
|
|
225
|
+
}
|
|
226
|
+
/** Maps a roster to scatter-plot points. Bots and hidden players stay in the list. */
|
|
227
|
+
declare function buildPlayerPoints(roster: ArenaRoster, ratings: PlayerRatings, catalog?: ShipCatalogLike): PlayerPoint[];
|
|
228
|
+
declare function median(values: readonly number[]): number | null;
|
|
229
|
+
declare function referenceLines(points: readonly PlayerPoint[]): ReferenceLines;
|
|
230
|
+
/**
|
|
231
|
+
* Quadrants relative to the reference lines.
|
|
232
|
+
*
|
|
233
|
+
* q1 high win rate + high PR, q2 low win rate + high PR,
|
|
234
|
+
* q3 low + low, q4 high win rate + low PR.
|
|
235
|
+
*/
|
|
236
|
+
declare function quadrantOf(point: PlayerPoint, reference: ReferenceLines): Quadrant;
|
|
237
|
+
/**
|
|
238
|
+
* Per-role ally/enemy comparison.
|
|
239
|
+
*
|
|
240
|
+
* Returns differences and samples only; no composite score and no prediction.
|
|
241
|
+
*/
|
|
242
|
+
declare function pairByRole(ally: readonly PlayerPoint[], enemy: readonly PlayerPoint[], profile?: RoleWeightProfile): RoleMatchup[];
|
|
243
|
+
interface SummaryOptions {
|
|
244
|
+
/**
|
|
245
|
+
* `equal` (default): one player, one vote.
|
|
246
|
+
* `battles`: weight by the player's lifetime battles.
|
|
247
|
+
*/
|
|
248
|
+
weighting?: Weighting;
|
|
249
|
+
}
|
|
250
|
+
interface PlayerSummary {
|
|
251
|
+
/** All slots on the side, including bots. */
|
|
252
|
+
total: number;
|
|
253
|
+
/** Non-bot slots. */
|
|
254
|
+
players: number;
|
|
255
|
+
/** Sum of non-null lifetime battles for non-bot players. */
|
|
256
|
+
battles: number;
|
|
257
|
+
/** Counts slots, not participant-match samples. */
|
|
258
|
+
coverage: Coverage;
|
|
259
|
+
/** Percentage, e.g. `54.3`. */
|
|
260
|
+
winRate: number | null;
|
|
261
|
+
avgDamage: number | null;
|
|
262
|
+
avgFrags: number | null;
|
|
263
|
+
pr: number | null;
|
|
264
|
+
/**
|
|
265
|
+
* Always false: a mean of account PR values is not an official team PR.
|
|
266
|
+
* UI must mark it `PR*`.
|
|
267
|
+
*/
|
|
268
|
+
prExact: false;
|
|
269
|
+
}
|
|
270
|
+
interface RosterSummary {
|
|
271
|
+
ally: PlayerSummary;
|
|
272
|
+
enemy: PlayerSummary;
|
|
273
|
+
/** Slots that cannot be assigned to a side (`relation === 'unknown'`). */
|
|
274
|
+
unassigned: number;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Reduces normalized player points into one side's overview.
|
|
278
|
+
*
|
|
279
|
+
* Averages only over non-null values; `0` is a valid value. Empty input
|
|
280
|
+
* returns an all-zero summary rather than throwing.
|
|
281
|
+
*/
|
|
282
|
+
declare function summarizePlayers(points: readonly PlayerPoint[], options?: SummaryOptions): PlayerSummary;
|
|
283
|
+
/** Groups points by `relation` and summarizes both sides. */
|
|
284
|
+
declare function summarizeRoster(points: readonly PlayerPoint[], options?: SummaryOptions): RosterSummary;
|
|
285
|
+
//#endregion
|
|
286
|
+
export { TeamAggregate as A, SampleDescriptor as B, AggregatedValue as C, PlayerAggregate as D, DailyPoint as E, dailySeries as F, shipPr as G, accountPrEstimated as H, AccountPrOptions as I, AccountTotals as L, aggregatePlayers as M, aggregateShips as N, PrAggregation as O, aggregateTeam as P, EstimateBias as R, AggregateOptions as S, DailyOptions as T, accountPrPooled as U, ShipMixEntry as V, battlePr as W, pairByRole as _, PlayerSummary as a, summarizePlayers as b, ReferenceLines as c, RosterSummary as d, ShipCatalogLike as f, median as g, buildPlayerPoints as h, PlayerRatings as i, Weighting as j, ShipAggregate as k, RoleMatchup as l, SummaryOptions as m, PlayerRatingSnapshot as n, Quadrant as o, ShipRole as p, PlayerRatingValue as r, ROLE_ORDER as s, PlayerPoint as t, RoleWeightProfile as u, quadrantOf as v, Coverage as w, summarizeRoster as x, referenceLines as y, RatedValue as z };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { A as TeamAggregate, B as SampleDescriptor, C as AggregatedValue, D as PlayerAggregate, E as DailyPoint, F as dailySeries, G as shipPr, H as accountPrEstimated, I as AccountPrOptions, L as AccountTotals, M as aggregatePlayers, N as aggregateShips, O as PrAggregation, P as aggregateTeam, R as EstimateBias, S as AggregateOptions, T as DailyOptions, U as accountPrPooled, V as ShipMixEntry, W as battlePr, a as PlayerSummary, b as summarizePlayers, d as RosterSummary, j as Weighting, k as ShipAggregate, m as SummaryOptions, w as Coverage, x as summarizeRoster, z as RatedValue } from "./index-CkKx_Wtz.mjs";
|
|
2
2
|
import { c as MatchRecord, o as MatchGroup } from "./types-DwmuyfXs.mjs";
|
|
3
3
|
import { ExpectedValuesDocument, ExpectedValuesDocument as ExpectedValuesDocument$1, PR_BANDS, PR_CATEGORY_LABELS, PersonalRatingCategory, PersonalRatingCategory as PersonalRatingCategory$1, ShipBattleStats, ShipExpectedValues, SingleBattleInput, WIN_RATE_BANDS, prCategory, winRateCategory } from "wows-number-api-sdk";
|
|
4
4
|
//#region src/levels/index.d.ts
|
|
@@ -79,4 +79,4 @@ export declare function weekBoundary(timestamp: number, timeZone: string, bounda
|
|
|
79
79
|
/** Inclusive range check; omitted bounds are open. */
|
|
80
80
|
export declare function inRange(timestamp: number, from?: number, to?: number): boolean;
|
|
81
81
|
//#endregion
|
|
82
|
-
export { AccountPrOptions, AccountTotals, AggregateOptions, AggregatedValue, Coverage, DailyOptions, DailyPoint, EstimateBias, type ExpectedValuesDocument, PR_BANDS, PR_CATEGORY_LABELS, type PersonalRatingCategory, PlayerAggregate, PrAggregation, RatedValue, SampleDescriptor, ShipAggregate, type ShipBattleStats, type ShipExpectedValues, ShipMixEntry, type SingleBattleInput, TeamAggregate, WIN_RATE_BANDS, Weighting, accountPrEstimated, accountPrPooled, aggregatePlayers, aggregateShips, aggregateTeam, battlePr, dailySeries, prCategory, shipPr, winRateCategory };
|
|
82
|
+
export { AccountPrOptions, AccountTotals, AggregateOptions, AggregatedValue, Coverage, DailyOptions, DailyPoint, EstimateBias, type ExpectedValuesDocument, PR_BANDS, PR_CATEGORY_LABELS, type PersonalRatingCategory, PlayerAggregate, type PlayerSummary, PrAggregation, RatedValue, type RosterSummary, SampleDescriptor, ShipAggregate, type ShipBattleStats, type ShipExpectedValues, ShipMixEntry, type SingleBattleInput, type SummaryOptions, TeamAggregate, WIN_RATE_BANDS, Weighting, accountPrEstimated, accountPrPooled, aggregatePlayers, aggregateShips, aggregateTeam, battlePr, dailySeries, prCategory, shipPr, summarizePlayers, summarizeRoster, winRateCategory };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { d as gameDay, f as inRange, l as outcomeOf, m as weekBoundary, p as parseBattleTime, u as statOutcome } from "./match-J2T9QiGg.mjs";
|
|
2
|
+
import { c as summarizeRoster, s as summarizePlayers } from "./strength-CBihFxZD.mjs";
|
|
2
3
|
import { PR_BANDS, PR_BANDS as PR_BANDS$1, PR_CATEGORY_LABELS, WIN_RATE_BANDS, WIN_RATE_BANDS as WIN_RATE_BANDS$1, calculateBattlePR, calculatePR, prCategory, prCategory as prCategory$1, shipExpectedValues, winRateCategory, winRateCategory as winRateCategory$1 } from "wows-number-api-sdk";
|
|
3
4
|
//#region src/rating/index.ts
|
|
4
5
|
const ACCOUNT_SHIP_ID = "__account__";
|
|
@@ -666,4 +667,4 @@ function recentMatches(records, query, expected) {
|
|
|
666
667
|
};
|
|
667
668
|
}
|
|
668
669
|
//#endregion
|
|
669
|
-
export { PR_BANDS, PR_CATEGORY_LABELS, WIN_RATE_BANDS, accountPrEstimated, accountPrPooled, aggregatePlayers, aggregateShips, aggregateTeam, battlePr, dailySeries, gameDay, inRange, levelOf, parseBattleTime, prCategory, recentMatches, shipPr, weekBoundary, winRateCategory };
|
|
670
|
+
export { PR_BANDS, PR_CATEGORY_LABELS, WIN_RATE_BANDS, accountPrEstimated, accountPrPooled, aggregatePlayers, aggregateShips, aggregateTeam, battlePr, dailySeries, gameDay, inRange, levelOf, parseBattleTime, prCategory, recentMatches, shipPr, summarizePlayers, summarizeRoster, weekBoundary, winRateCategory };
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
//#region src/strength/index.ts
|
|
2
|
+
/** Display / analysis order; not a composite score. */
|
|
3
|
+
const ROLE_ORDER = [
|
|
4
|
+
"submarine",
|
|
5
|
+
"aircraft-carrier",
|
|
6
|
+
"destroyer",
|
|
7
|
+
"battleship",
|
|
8
|
+
"cruiser-functional",
|
|
9
|
+
"cruiser-firepower",
|
|
10
|
+
"cruiser",
|
|
11
|
+
"unknown"
|
|
12
|
+
];
|
|
13
|
+
function isRatedValue(value) {
|
|
14
|
+
return "value" in value && "basis" in value && "exact" in value;
|
|
15
|
+
}
|
|
16
|
+
function normalize(value) {
|
|
17
|
+
if (value === void 0) return {
|
|
18
|
+
pr: null,
|
|
19
|
+
winRate: null,
|
|
20
|
+
battles: null,
|
|
21
|
+
avgDamage: null,
|
|
22
|
+
avgFrags: null,
|
|
23
|
+
hidden: false
|
|
24
|
+
};
|
|
25
|
+
if (isRatedValue(value)) return {
|
|
26
|
+
pr: value,
|
|
27
|
+
winRate: null,
|
|
28
|
+
battles: value.sample.matches,
|
|
29
|
+
avgDamage: null,
|
|
30
|
+
avgFrags: null,
|
|
31
|
+
hidden: false
|
|
32
|
+
};
|
|
33
|
+
return {
|
|
34
|
+
pr: value.pr ?? null,
|
|
35
|
+
winRate: value.winRate ?? null,
|
|
36
|
+
battles: value.battles ?? value.pr?.sample.matches ?? null,
|
|
37
|
+
avgDamage: value.avgDamage ?? null,
|
|
38
|
+
avgFrags: value.avgFrags ?? null,
|
|
39
|
+
hidden: value.hidden ?? false
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Maps a roster to scatter-plot points. Bots and hidden players stay in the list. */
|
|
43
|
+
function buildPlayerPoints(roster, ratings, catalog) {
|
|
44
|
+
return roster.participants.map((participant) => {
|
|
45
|
+
const rating = participant.accountId === null ? normalize(void 0) : normalize(ratings.get(participant.accountId));
|
|
46
|
+
return {
|
|
47
|
+
accountId: participant.accountId,
|
|
48
|
+
name: participant.name,
|
|
49
|
+
teamId: null,
|
|
50
|
+
shipId: participant.shipId,
|
|
51
|
+
shipRole: participant.shipId === null ? "unknown" : catalog?.role?.(participant.shipId) ?? "unknown",
|
|
52
|
+
tier: participant.shipId === null ? null : catalog?.tier?.(participant.shipId) ?? null,
|
|
53
|
+
divisionId: null,
|
|
54
|
+
winRate: rating.winRate,
|
|
55
|
+
pr: rating.pr?.value ?? null,
|
|
56
|
+
prExact: rating.pr?.exact ?? false,
|
|
57
|
+
battles: rating.battles,
|
|
58
|
+
isBot: participant.isBot,
|
|
59
|
+
relation: participant.relation,
|
|
60
|
+
avgDamage: rating.avgDamage,
|
|
61
|
+
avgFrags: rating.avgFrags,
|
|
62
|
+
hidden: rating.hidden
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function median(values) {
|
|
67
|
+
if (values.length === 0) return null;
|
|
68
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
69
|
+
const middle = Math.floor(sorted.length / 2);
|
|
70
|
+
if (sorted.length % 2 === 1) return sorted[middle];
|
|
71
|
+
return (sorted[middle - 1] + sorted[middle]) / 2;
|
|
72
|
+
}
|
|
73
|
+
function referenceLines(points) {
|
|
74
|
+
return {
|
|
75
|
+
winRateMedian: median(points.map((point) => point.winRate).filter((value) => value !== null)),
|
|
76
|
+
prMedian: median(points.map((point) => point.pr).filter((value) => value !== null))
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Quadrants relative to the reference lines.
|
|
81
|
+
*
|
|
82
|
+
* q1 high win rate + high PR, q2 low win rate + high PR,
|
|
83
|
+
* q3 low + low, q4 high win rate + low PR.
|
|
84
|
+
*/
|
|
85
|
+
function quadrantOf(point, reference) {
|
|
86
|
+
if (point.winRate === null || point.pr === null || reference.winRateMedian === null || reference.prMedian === null) return "unknown";
|
|
87
|
+
const highWinRate = point.winRate >= reference.winRateMedian;
|
|
88
|
+
const highPr = point.pr >= reference.prMedian;
|
|
89
|
+
if (highWinRate) return highPr ? "q1" : "q4";
|
|
90
|
+
return highPr ? "q2" : "q3";
|
|
91
|
+
}
|
|
92
|
+
function roleCoverage(ally, enemy) {
|
|
93
|
+
const coverage = {
|
|
94
|
+
total: 0,
|
|
95
|
+
resolved: 0,
|
|
96
|
+
hidden: 0,
|
|
97
|
+
bot: 0,
|
|
98
|
+
failed: 0
|
|
99
|
+
};
|
|
100
|
+
for (const point of [...ally, ...enemy]) {
|
|
101
|
+
coverage.total += 1;
|
|
102
|
+
if (point.isBot) {
|
|
103
|
+
coverage.bot += 1;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (point.accountId === null) {
|
|
107
|
+
coverage.hidden += 1;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (point.pr === null) {
|
|
111
|
+
coverage.failed += 1;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
coverage.resolved += 1;
|
|
115
|
+
}
|
|
116
|
+
return coverage;
|
|
117
|
+
}
|
|
118
|
+
function diff(left, right) {
|
|
119
|
+
return left === null || right === null ? null : left - right;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Per-role ally/enemy comparison.
|
|
123
|
+
*
|
|
124
|
+
* Returns differences and samples only; no composite score and no prediction.
|
|
125
|
+
*/
|
|
126
|
+
function pairByRole(ally, enemy, profile) {
|
|
127
|
+
const allyByRole = /* @__PURE__ */ new Map();
|
|
128
|
+
const enemyByRole = /* @__PURE__ */ new Map();
|
|
129
|
+
for (const point of ally) {
|
|
130
|
+
const list = allyByRole.get(point.shipRole) ?? [];
|
|
131
|
+
list.push(point);
|
|
132
|
+
allyByRole.set(point.shipRole, list);
|
|
133
|
+
}
|
|
134
|
+
for (const point of enemy) {
|
|
135
|
+
const list = enemyByRole.get(point.shipRole) ?? [];
|
|
136
|
+
list.push(point);
|
|
137
|
+
enemyByRole.set(point.shipRole, list);
|
|
138
|
+
}
|
|
139
|
+
return ROLE_ORDER.filter((role) => allyByRole.has(role) || enemyByRole.has(role)).map((role) => {
|
|
140
|
+
const allies = allyByRole.get(role) ?? [];
|
|
141
|
+
const enemies = enemyByRole.get(role) ?? [];
|
|
142
|
+
const allyMedianPr = median(allies.map((point) => point.pr).filter((value) => value !== null));
|
|
143
|
+
const enemyMedianPr = median(enemies.map((point) => point.pr).filter((value) => value !== null));
|
|
144
|
+
const allyMedianWinRate = median(allies.map((point) => point.winRate).filter((value) => value !== null));
|
|
145
|
+
const enemyMedianWinRate = median(enemies.map((point) => point.winRate).filter((value) => value !== null));
|
|
146
|
+
return {
|
|
147
|
+
role,
|
|
148
|
+
allyCount: allies.length,
|
|
149
|
+
enemyCount: enemies.length,
|
|
150
|
+
allyMedianPr,
|
|
151
|
+
enemyMedianPr,
|
|
152
|
+
allyMedianWinRate,
|
|
153
|
+
enemyMedianWinRate,
|
|
154
|
+
prDiff: diff(allyMedianPr, enemyMedianPr),
|
|
155
|
+
winRateDiff: diff(allyMedianWinRate, enemyMedianWinRate),
|
|
156
|
+
weight: profile?.weights?.[role] ?? null,
|
|
157
|
+
sample: {
|
|
158
|
+
ally: allies.length,
|
|
159
|
+
enemy: enemies.length
|
|
160
|
+
},
|
|
161
|
+
coverage: roleCoverage(allies, enemies)
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
function summaryCoverage(points) {
|
|
166
|
+
const coverage = {
|
|
167
|
+
total: 0,
|
|
168
|
+
resolved: 0,
|
|
169
|
+
hidden: 0,
|
|
170
|
+
bot: 0,
|
|
171
|
+
failed: 0
|
|
172
|
+
};
|
|
173
|
+
for (const point of points) {
|
|
174
|
+
coverage.total += 1;
|
|
175
|
+
if (point.isBot) {
|
|
176
|
+
coverage.bot += 1;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (point.hidden || point.accountId === null) {
|
|
180
|
+
coverage.hidden += 1;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (point.winRate === null && point.avgDamage === null && point.avgFrags === null && point.pr === null) {
|
|
184
|
+
coverage.failed += 1;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
coverage.resolved += 1;
|
|
188
|
+
}
|
|
189
|
+
return coverage;
|
|
190
|
+
}
|
|
191
|
+
function meanOf(points, metric, weighting) {
|
|
192
|
+
let total = 0;
|
|
193
|
+
let weightSum = 0;
|
|
194
|
+
for (const point of points) {
|
|
195
|
+
const value = point[metric];
|
|
196
|
+
if (value === null) continue;
|
|
197
|
+
const weight = weighting === "battles" ? point.battles ?? 0 : 1;
|
|
198
|
+
if (weight <= 0) continue;
|
|
199
|
+
total += value * weight;
|
|
200
|
+
weightSum += weight;
|
|
201
|
+
}
|
|
202
|
+
return weightSum === 0 ? null : total / weightSum;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Reduces normalized player points into one side's overview.
|
|
206
|
+
*
|
|
207
|
+
* Averages only over non-null values; `0` is a valid value. Empty input
|
|
208
|
+
* returns an all-zero summary rather than throwing.
|
|
209
|
+
*/
|
|
210
|
+
function summarizePlayers(points, options = {}) {
|
|
211
|
+
const weighting = options.weighting ?? "equal";
|
|
212
|
+
const coverage = summaryCoverage(points);
|
|
213
|
+
const members = points.filter((point) => !point.isBot);
|
|
214
|
+
let battles = 0;
|
|
215
|
+
for (const point of members) if (point.battles !== null) battles += point.battles;
|
|
216
|
+
return {
|
|
217
|
+
total: coverage.total,
|
|
218
|
+
players: coverage.total - coverage.bot,
|
|
219
|
+
battles,
|
|
220
|
+
coverage,
|
|
221
|
+
winRate: meanOf(members, "winRate", weighting),
|
|
222
|
+
avgDamage: meanOf(members, "avgDamage", weighting),
|
|
223
|
+
avgFrags: meanOf(members, "avgFrags", weighting),
|
|
224
|
+
pr: meanOf(members, "pr", weighting),
|
|
225
|
+
prExact: false
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/** Groups points by `relation` and summarizes both sides. */
|
|
229
|
+
function summarizeRoster(points, options = {}) {
|
|
230
|
+
const ally = [];
|
|
231
|
+
const enemy = [];
|
|
232
|
+
let unassigned = 0;
|
|
233
|
+
for (const point of points) if (point.relation === "self" || point.relation === "ally") ally.push(point);
|
|
234
|
+
else if (point.relation === "enemy") enemy.push(point);
|
|
235
|
+
else unassigned += 1;
|
|
236
|
+
return {
|
|
237
|
+
ally: summarizePlayers(ally, options),
|
|
238
|
+
enemy: summarizePlayers(enemy, options),
|
|
239
|
+
unassigned
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
//#endregion
|
|
243
|
+
export { quadrantOf as a, summarizeRoster as c, pairByRole as i, buildPlayerPoints as n, referenceLines as o, median as r, summarizePlayers as s, ROLE_ORDER as t };
|
package/dist/strength.d.mts
CHANGED
|
@@ -1,80 +1,3 @@
|
|
|
1
|
-
import { r as
|
|
2
|
-
import { i as ArenaRoster } from "./types-DwmuyfXs.mjs";
|
|
1
|
+
import { _ as pairByRole, a as PlayerSummary, b as summarizePlayers, c as ReferenceLines, d as RosterSummary, f as ShipCatalogLike, g as median, h as buildPlayerPoints, i as PlayerRatings, l as RoleMatchup, m as SummaryOptions, n as PlayerRatingSnapshot, o as Quadrant, p as ShipRole, r as PlayerRatingValue, s as ROLE_ORDER, t as PlayerPoint, u as RoleWeightProfile, v as quadrantOf, x as summarizeRoster, y as referenceLines } from "./index-CkKx_Wtz.mjs";
|
|
3
2
|
import { o as groupDivisions } from "./index-_QacyXDj.mjs";
|
|
4
|
-
|
|
5
|
-
export type ShipRole = 'submarine' | 'aircraft-carrier' | 'destroyer' | 'battleship' | 'cruiser-functional' | 'cruiser-firepower' | 'cruiser' | 'unknown';
|
|
6
|
-
/** Display / analysis order; not a composite score. */
|
|
7
|
-
export declare const ROLE_ORDER: readonly ShipRole[];
|
|
8
|
-
export interface ShipCatalogLike {
|
|
9
|
-
role?: (shipId: number) => ShipRole | null;
|
|
10
|
-
tier?: (shipId: number) => number | null;
|
|
11
|
-
}
|
|
12
|
-
export interface PlayerRatingSnapshot {
|
|
13
|
-
pr?: RatedValue | null;
|
|
14
|
-
/** Lifetime win rate in percent, e.g. `54.3`. */
|
|
15
|
-
winRate?: number | null;
|
|
16
|
-
battles?: number | null;
|
|
17
|
-
}
|
|
18
|
-
export type PlayerRatingValue = RatedValue | PlayerRatingSnapshot;
|
|
19
|
-
export type PlayerRatings = ReadonlyMap<number, PlayerRatingValue>;
|
|
20
|
-
export interface PlayerPoint {
|
|
21
|
-
accountId: number | null;
|
|
22
|
-
name: string | null;
|
|
23
|
-
/** `null` for pre-battle rosters; relations are known, team ids are not. */
|
|
24
|
-
teamId: number | null;
|
|
25
|
-
shipId: number | null;
|
|
26
|
-
shipRole: ShipRole;
|
|
27
|
-
tier: number | null;
|
|
28
|
-
divisionId: number | null;
|
|
29
|
-
/** Percentage, e.g. `54.3`. */
|
|
30
|
-
winRate: number | null;
|
|
31
|
-
pr: number | null;
|
|
32
|
-
prExact: boolean;
|
|
33
|
-
battles: number | null;
|
|
34
|
-
isBot: boolean;
|
|
35
|
-
}
|
|
36
|
-
export interface ReferenceLines {
|
|
37
|
-
winRateMedian: number | null;
|
|
38
|
-
prMedian: number | null;
|
|
39
|
-
}
|
|
40
|
-
export type Quadrant = 'q1' | 'q2' | 'q3' | 'q4' | 'unknown';
|
|
41
|
-
export interface RoleWeightProfile {
|
|
42
|
-
weights?: Partial<Record<ShipRole, number>>;
|
|
43
|
-
}
|
|
44
|
-
export interface RoleMatchup {
|
|
45
|
-
role: ShipRole;
|
|
46
|
-
allyCount: number;
|
|
47
|
-
enemyCount: number;
|
|
48
|
-
allyMedianPr: number | null;
|
|
49
|
-
enemyMedianPr: number | null;
|
|
50
|
-
allyMedianWinRate: number | null;
|
|
51
|
-
enemyMedianWinRate: number | null;
|
|
52
|
-
/** Ally median minus enemy median; `null` when either side lacks values. */
|
|
53
|
-
prDiff: number | null;
|
|
54
|
-
winRateDiff: number | null;
|
|
55
|
-
weight: number | null;
|
|
56
|
-
sample: {
|
|
57
|
-
ally: number;
|
|
58
|
-
enemy: number;
|
|
59
|
-
};
|
|
60
|
-
coverage: Coverage;
|
|
61
|
-
}
|
|
62
|
-
/** Maps a roster to scatter-plot points. Bots and hidden players stay in the list. */
|
|
63
|
-
export declare function buildPlayerPoints(roster: ArenaRoster, ratings: PlayerRatings, catalog?: ShipCatalogLike): PlayerPoint[];
|
|
64
|
-
export declare function median(values: readonly number[]): number | null;
|
|
65
|
-
export declare function referenceLines(points: readonly PlayerPoint[]): ReferenceLines;
|
|
66
|
-
/**
|
|
67
|
-
* Quadrants relative to the reference lines.
|
|
68
|
-
*
|
|
69
|
-
* q1 high win rate + high PR, q2 low win rate + high PR,
|
|
70
|
-
* q3 low + low, q4 high win rate + low PR.
|
|
71
|
-
*/
|
|
72
|
-
export declare function quadrantOf(point: PlayerPoint, reference: ReferenceLines): Quadrant;
|
|
73
|
-
/**
|
|
74
|
-
* Per-role ally/enemy comparison.
|
|
75
|
-
*
|
|
76
|
-
* Returns differences and samples only; no composite score and no prediction.
|
|
77
|
-
*/
|
|
78
|
-
export declare function pairByRole(ally: readonly PlayerPoint[], enemy: readonly PlayerPoint[], profile?: RoleWeightProfile): RoleMatchup[];
|
|
79
|
-
//#endregion
|
|
80
|
-
export { groupDivisions };
|
|
3
|
+
export { PlayerPoint, PlayerRatingSnapshot, PlayerRatingValue, PlayerRatings, PlayerSummary, Quadrant, ROLE_ORDER, ReferenceLines, RoleMatchup, RoleWeightProfile, RosterSummary, ShipCatalogLike, ShipRole, SummaryOptions, buildPlayerPoints, groupDivisions, median, pairByRole, quadrantOf, referenceLines, summarizePlayers, summarizeRoster };
|
package/dist/strength.mjs
CHANGED
|
@@ -1,154 +1,3 @@
|
|
|
1
1
|
import { i as groupDivisions } from "./match-J2T9QiGg.mjs";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const ROLE_ORDER = [
|
|
5
|
-
"submarine",
|
|
6
|
-
"aircraft-carrier",
|
|
7
|
-
"destroyer",
|
|
8
|
-
"battleship",
|
|
9
|
-
"cruiser-functional",
|
|
10
|
-
"cruiser-firepower",
|
|
11
|
-
"cruiser",
|
|
12
|
-
"unknown"
|
|
13
|
-
];
|
|
14
|
-
function isRatedValue(value) {
|
|
15
|
-
return "value" in value && "basis" in value && "exact" in value;
|
|
16
|
-
}
|
|
17
|
-
function normalize(value) {
|
|
18
|
-
if (value === void 0) return {
|
|
19
|
-
pr: null,
|
|
20
|
-
winRate: null,
|
|
21
|
-
battles: null
|
|
22
|
-
};
|
|
23
|
-
if (isRatedValue(value)) return {
|
|
24
|
-
pr: value,
|
|
25
|
-
winRate: null,
|
|
26
|
-
battles: value.sample.matches
|
|
27
|
-
};
|
|
28
|
-
return {
|
|
29
|
-
pr: value.pr ?? null,
|
|
30
|
-
winRate: value.winRate ?? null,
|
|
31
|
-
battles: value.battles ?? value.pr?.sample.matches ?? null
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
/** Maps a roster to scatter-plot points. Bots and hidden players stay in the list. */
|
|
35
|
-
function buildPlayerPoints(roster, ratings, catalog) {
|
|
36
|
-
return roster.participants.map((participant) => {
|
|
37
|
-
const rating = participant.accountId === null ? normalize(void 0) : normalize(ratings.get(participant.accountId));
|
|
38
|
-
return {
|
|
39
|
-
accountId: participant.accountId,
|
|
40
|
-
name: participant.name,
|
|
41
|
-
teamId: null,
|
|
42
|
-
shipId: participant.shipId,
|
|
43
|
-
shipRole: participant.shipId === null ? "unknown" : catalog?.role?.(participant.shipId) ?? "unknown",
|
|
44
|
-
tier: participant.shipId === null ? null : catalog?.tier?.(participant.shipId) ?? null,
|
|
45
|
-
divisionId: null,
|
|
46
|
-
winRate: rating.winRate,
|
|
47
|
-
pr: rating.pr?.value ?? null,
|
|
48
|
-
prExact: rating.pr?.exact ?? false,
|
|
49
|
-
battles: rating.battles,
|
|
50
|
-
isBot: participant.isBot
|
|
51
|
-
};
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
function median(values) {
|
|
55
|
-
if (values.length === 0) return null;
|
|
56
|
-
const sorted = [...values].sort((a, b) => a - b);
|
|
57
|
-
const middle = Math.floor(sorted.length / 2);
|
|
58
|
-
if (sorted.length % 2 === 1) return sorted[middle];
|
|
59
|
-
return (sorted[middle - 1] + sorted[middle]) / 2;
|
|
60
|
-
}
|
|
61
|
-
function referenceLines(points) {
|
|
62
|
-
return {
|
|
63
|
-
winRateMedian: median(points.map((point) => point.winRate).filter((value) => value !== null)),
|
|
64
|
-
prMedian: median(points.map((point) => point.pr).filter((value) => value !== null))
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Quadrants relative to the reference lines.
|
|
69
|
-
*
|
|
70
|
-
* q1 high win rate + high PR, q2 low win rate + high PR,
|
|
71
|
-
* q3 low + low, q4 high win rate + low PR.
|
|
72
|
-
*/
|
|
73
|
-
function quadrantOf(point, reference) {
|
|
74
|
-
if (point.winRate === null || point.pr === null || reference.winRateMedian === null || reference.prMedian === null) return "unknown";
|
|
75
|
-
const highWinRate = point.winRate >= reference.winRateMedian;
|
|
76
|
-
const highPr = point.pr >= reference.prMedian;
|
|
77
|
-
if (highWinRate) return highPr ? "q1" : "q4";
|
|
78
|
-
return highPr ? "q2" : "q3";
|
|
79
|
-
}
|
|
80
|
-
function roleCoverage(ally, enemy) {
|
|
81
|
-
const coverage = {
|
|
82
|
-
total: 0,
|
|
83
|
-
resolved: 0,
|
|
84
|
-
hidden: 0,
|
|
85
|
-
bot: 0,
|
|
86
|
-
failed: 0
|
|
87
|
-
};
|
|
88
|
-
for (const point of [...ally, ...enemy]) {
|
|
89
|
-
coverage.total += 1;
|
|
90
|
-
if (point.isBot) {
|
|
91
|
-
coverage.bot += 1;
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
if (point.accountId === null) {
|
|
95
|
-
coverage.hidden += 1;
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
if (point.pr === null) {
|
|
99
|
-
coverage.failed += 1;
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
coverage.resolved += 1;
|
|
103
|
-
}
|
|
104
|
-
return coverage;
|
|
105
|
-
}
|
|
106
|
-
function diff(left, right) {
|
|
107
|
-
return left === null || right === null ? null : left - right;
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Per-role ally/enemy comparison.
|
|
111
|
-
*
|
|
112
|
-
* Returns differences and samples only; no composite score and no prediction.
|
|
113
|
-
*/
|
|
114
|
-
function pairByRole(ally, enemy, profile) {
|
|
115
|
-
const allyByRole = /* @__PURE__ */ new Map();
|
|
116
|
-
const enemyByRole = /* @__PURE__ */ new Map();
|
|
117
|
-
for (const point of ally) {
|
|
118
|
-
const list = allyByRole.get(point.shipRole) ?? [];
|
|
119
|
-
list.push(point);
|
|
120
|
-
allyByRole.set(point.shipRole, list);
|
|
121
|
-
}
|
|
122
|
-
for (const point of enemy) {
|
|
123
|
-
const list = enemyByRole.get(point.shipRole) ?? [];
|
|
124
|
-
list.push(point);
|
|
125
|
-
enemyByRole.set(point.shipRole, list);
|
|
126
|
-
}
|
|
127
|
-
return ROLE_ORDER.filter((role) => allyByRole.has(role) || enemyByRole.has(role)).map((role) => {
|
|
128
|
-
const allies = allyByRole.get(role) ?? [];
|
|
129
|
-
const enemies = enemyByRole.get(role) ?? [];
|
|
130
|
-
const allyMedianPr = median(allies.map((point) => point.pr).filter((value) => value !== null));
|
|
131
|
-
const enemyMedianPr = median(enemies.map((point) => point.pr).filter((value) => value !== null));
|
|
132
|
-
const allyMedianWinRate = median(allies.map((point) => point.winRate).filter((value) => value !== null));
|
|
133
|
-
const enemyMedianWinRate = median(enemies.map((point) => point.winRate).filter((value) => value !== null));
|
|
134
|
-
return {
|
|
135
|
-
role,
|
|
136
|
-
allyCount: allies.length,
|
|
137
|
-
enemyCount: enemies.length,
|
|
138
|
-
allyMedianPr,
|
|
139
|
-
enemyMedianPr,
|
|
140
|
-
allyMedianWinRate,
|
|
141
|
-
enemyMedianWinRate,
|
|
142
|
-
prDiff: diff(allyMedianPr, enemyMedianPr),
|
|
143
|
-
winRateDiff: diff(allyMedianWinRate, enemyMedianWinRate),
|
|
144
|
-
weight: profile?.weights?.[role] ?? null,
|
|
145
|
-
sample: {
|
|
146
|
-
ally: allies.length,
|
|
147
|
-
enemy: enemies.length
|
|
148
|
-
},
|
|
149
|
-
coverage: roleCoverage(allies, enemies)
|
|
150
|
-
};
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
//#endregion
|
|
154
|
-
export { ROLE_ORDER, buildPlayerPoints, groupDivisions, median, pairByRole, quadrantOf, referenceLines };
|
|
2
|
+
import { a as quadrantOf, c as summarizeRoster, i as pairByRole, n as buildPlayerPoints, o as referenceLines, r as median, s as summarizePlayers, t as ROLE_ORDER } from "./strength-CBihFxZD.mjs";
|
|
3
|
+
export { ROLE_ORDER, buildPlayerPoints, groupDivisions, median, pairByRole, quadrantOf, referenceLines, summarizePlayers, summarizeRoster };
|
package/package.json
CHANGED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { c as MatchRecord, o as MatchGroup } from "./types-DwmuyfXs.mjs";
|
|
2
|
-
import { ExpectedValuesDocument, ShipBattleStats, SingleBattleInput } from "wows-number-api-sdk";
|
|
3
|
-
//#region src/rating/index.d.ts
|
|
4
|
-
interface SampleDescriptor {
|
|
5
|
-
kind: 'ingested' | 'lifetime' | 'match';
|
|
6
|
-
matches: number;
|
|
7
|
-
/** ISO timestamps; present for date windows. */
|
|
8
|
-
from?: string;
|
|
9
|
-
to?: string;
|
|
10
|
-
/** When querying another player: matches shared with the local player. */
|
|
11
|
-
sharedWith?: number;
|
|
12
|
-
players?: number;
|
|
13
|
-
}
|
|
14
|
-
interface EstimateBias {
|
|
15
|
-
direction: 'underestimate' | 'overestimate' | 'mixed' | 'unknown';
|
|
16
|
-
reason: 'ship-mix' | 'insufficient-data';
|
|
17
|
-
/** Relative baseline difference: pooled baseline / global average - 1. */
|
|
18
|
-
delta?: {
|
|
19
|
-
damage?: number;
|
|
20
|
-
frags?: number;
|
|
21
|
-
wins?: number;
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
interface RatedValue {
|
|
25
|
-
value: number;
|
|
26
|
-
basis: 'ship' | 'account';
|
|
27
|
-
exact: boolean;
|
|
28
|
-
sample: SampleDescriptor;
|
|
29
|
-
/** Expected-values version, taken from `expected.time`. */
|
|
30
|
-
baselineVersion: string;
|
|
31
|
-
/** Only present when `exact === false`. */
|
|
32
|
-
bias?: EstimateBias;
|
|
33
|
-
}
|
|
34
|
-
interface AccountTotals {
|
|
35
|
-
battles: number;
|
|
36
|
-
damage: number;
|
|
37
|
-
frags: number;
|
|
38
|
-
wins: number;
|
|
39
|
-
}
|
|
40
|
-
interface ShipMixEntry {
|
|
41
|
-
shipId: number | string;
|
|
42
|
-
battles: number;
|
|
43
|
-
}
|
|
44
|
-
interface AccountPrOptions {
|
|
45
|
-
shipMix?: readonly ShipMixEntry[];
|
|
46
|
-
}
|
|
47
|
-
/** Personal Rating for one ship, using that ship's own expected values. */
|
|
48
|
-
declare function shipPr(stats: ShipBattleStats, expected: ExpectedValuesDocument, sample?: SampleDescriptor): RatedValue | null;
|
|
49
|
-
/** Personal Rating for a single battle. */
|
|
50
|
-
declare function battlePr(battle: SingleBattleInput, expected: ExpectedValuesDocument, sample?: SampleDescriptor): RatedValue | null;
|
|
51
|
-
/**
|
|
52
|
-
* Fast account PR*: account totals against the unweighted average of all
|
|
53
|
-
* expected values.
|
|
54
|
-
*
|
|
55
|
-
* This is an estimate, not a different formula: the account is treated as one
|
|
56
|
-
* synthetic ship. `exact: false` is a product promise — UI must mark it `PR*`.
|
|
57
|
-
*/
|
|
58
|
-
declare function accountPrEstimated(account: AccountTotals, expected: ExpectedValuesDocument, options?: AccountPrOptions): RatedValue | null;
|
|
59
|
-
/**
|
|
60
|
-
* Exact weighted account PR: pools per-ship totals and per-ship baselines.
|
|
61
|
-
*
|
|
62
|
-
* Ships without expected values are skipped by `calculatePR`, not counted
|
|
63
|
-
* as zero.
|
|
64
|
-
*/
|
|
65
|
-
declare function accountPrPooled(ships: readonly ShipBattleStats[], expected: ExpectedValuesDocument, sample: SampleDescriptor): RatedValue | null;
|
|
66
|
-
//#endregion
|
|
67
|
-
//#region src/aggregate/index.d.ts
|
|
68
|
-
type Weighting = 'equal' | 'battles';
|
|
69
|
-
type PrAggregation = 'mean' | 'pooled';
|
|
70
|
-
interface Coverage {
|
|
71
|
-
total: number;
|
|
72
|
-
resolved: number;
|
|
73
|
-
hidden: number;
|
|
74
|
-
bot: number;
|
|
75
|
-
failed: number;
|
|
76
|
-
}
|
|
77
|
-
interface AggregatedValue {
|
|
78
|
-
value: number | null;
|
|
79
|
-
sample: SampleDescriptor;
|
|
80
|
-
coverage: Coverage;
|
|
81
|
-
}
|
|
82
|
-
interface AggregateOptions {
|
|
83
|
-
weighting: Weighting;
|
|
84
|
-
prAggregation: PrAggregation;
|
|
85
|
-
expected?: ExpectedValuesDocument;
|
|
86
|
-
matchGroups?: readonly MatchGroup[];
|
|
87
|
-
from?: number;
|
|
88
|
-
to?: number;
|
|
89
|
-
}
|
|
90
|
-
interface PlayerAggregate {
|
|
91
|
-
accountId: number;
|
|
92
|
-
name: string | null;
|
|
93
|
-
battles: number;
|
|
94
|
-
wins: number;
|
|
95
|
-
losses: number;
|
|
96
|
-
draws: number;
|
|
97
|
-
damage: number;
|
|
98
|
-
frags: number;
|
|
99
|
-
winRate: AggregatedValue;
|
|
100
|
-
avgDamage: AggregatedValue;
|
|
101
|
-
avgFrags: AggregatedValue;
|
|
102
|
-
pr: AggregatedValue;
|
|
103
|
-
}
|
|
104
|
-
interface TeamAggregate {
|
|
105
|
-
teamId: number | null;
|
|
106
|
-
players: number;
|
|
107
|
-
coverage: Coverage;
|
|
108
|
-
values: {
|
|
109
|
-
winRate: AggregatedValue;
|
|
110
|
-
avgDamage: AggregatedValue;
|
|
111
|
-
avgFrags: AggregatedValue;
|
|
112
|
-
pr: AggregatedValue;
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
interface ShipAggregate {
|
|
116
|
-
shipId: number;
|
|
117
|
-
battles: number;
|
|
118
|
-
wins: number;
|
|
119
|
-
losses: number;
|
|
120
|
-
draws: number;
|
|
121
|
-
damage: number;
|
|
122
|
-
frags: number;
|
|
123
|
-
winRate: AggregatedValue;
|
|
124
|
-
avgDamage: AggregatedValue;
|
|
125
|
-
avgFrags: AggregatedValue;
|
|
126
|
-
pr: AggregatedValue;
|
|
127
|
-
}
|
|
128
|
-
interface DailyPoint {
|
|
129
|
-
day: string;
|
|
130
|
-
matches: number;
|
|
131
|
-
battles: number;
|
|
132
|
-
wins: number;
|
|
133
|
-
losses: number;
|
|
134
|
-
draws: number;
|
|
135
|
-
winRate: number | null;
|
|
136
|
-
avgDamage: number | null;
|
|
137
|
-
avgFrags: number | null;
|
|
138
|
-
pr: AggregatedValue;
|
|
139
|
-
}
|
|
140
|
-
interface DailyOptions {
|
|
141
|
-
timeZone: string;
|
|
142
|
-
boundaryHour?: number;
|
|
143
|
-
expected?: ExpectedValuesDocument;
|
|
144
|
-
matchGroups?: readonly MatchGroup[];
|
|
145
|
-
from?: number;
|
|
146
|
-
to?: number;
|
|
147
|
-
}
|
|
148
|
-
/** Per-player aggregates over the selected records. Ordered by battles, then account id. */
|
|
149
|
-
declare function aggregatePlayers(records: readonly MatchRecord[], options: AggregateOptions): PlayerAggregate[];
|
|
150
|
-
/** One team's aggregate; coverage only counts that team's participants. */
|
|
151
|
-
declare function aggregateTeam(records: readonly MatchRecord[], teamId: number | null, options: AggregateOptions): TeamAggregate;
|
|
152
|
-
/** Per-ship aggregates over the selected records. Ordered by battles, then ship id. */
|
|
153
|
-
declare function aggregateShips(records: readonly MatchRecord[], options: AggregateOptions): ShipAggregate[];
|
|
154
|
-
/** Self performance per game day, ordered oldest first. */
|
|
155
|
-
declare function dailySeries(records: readonly MatchRecord[], options: DailyOptions): DailyPoint[];
|
|
156
|
-
//#endregion
|
|
157
|
-
export { battlePr as C, accountPrPooled as S, EstimateBias as _, DailyPoint as a, ShipMixEntry as b, ShipAggregate as c, aggregatePlayers as d, aggregateShips as f, AccountTotals as g, AccountPrOptions as h, DailyOptions as i, TeamAggregate as l, dailySeries as m, AggregatedValue as n, PlayerAggregate as o, aggregateTeam as p, Coverage as r, PrAggregation as s, AggregateOptions as t, Weighting as u, RatedValue as v, shipPr as w, accountPrEstimated as x, SampleDescriptor as y };
|