spells-mtg 0.4.0__tar.gz → 0.5.0__tar.gz

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.

Potentially problematic release.


This version of spells-mtg might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: spells-mtg
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: analaysis of 17Lands.com public datasets
5
5
  Author-Email: Joel Barnes <oelarnes@gmail.com>
6
6
  License: MIT
@@ -74,8 +74,9 @@ Spells is not affiliated with 17Lands. Please review the [Usage Guidelines](http
74
74
  - Supports calculating the standard aggregations and measures out of the box with no arguments (ALSA, GIH WR, etc)
75
75
  - Caches aggregate DataFrames in the local file system automatically for instantaneous reproduction of previous analysis
76
76
  - Manages grouping and filtering by built-in and custom columns at the row level
77
- - Provides 121 explicitly specified, enumerated, documented column definitions
77
+ - Provides 122 explicitly specified, enumerated, documented column definitions
78
78
  - Supports "Deck Color Data" aggregations with built-in column definitions.
79
+ - Lets you feed card metrics back in to column definitions to support scientific workflows like MLE
79
80
  - Provides a CLI tool `spells [add|refresh|clean|remove|info] [SET]` to download and manage external files
80
81
  - Downloads and manages public datasets from 17Lands
81
82
  - Retrieves and models booster configuration and card data from [MTGJSON](https://mtgjson.com/)
@@ -87,7 +88,7 @@ Spells is not affiliated with 17Lands. Please review the [Usage Guidelines](http
87
88
 
88
89
  ## summon
89
90
 
90
- `summon` takes four optional arguments, allowing a fully declarative specification of your desired analysis. Basic functionality not provided by this api can often be managed by simple chained calls using the polars API, e.g. sorting and post-agg filtering.
91
+ `summon` takes five optional arguments, allowing a fully declarative specification of your desired analysis. Basic functionality not provided by this api can often be managed by simple chained calls using the polars API, e.g. sorting and post-agg filtering.
91
92
  - `columns` specifies the desired output columns
92
93
  ```python
93
94
  >>> spells.summon('DSK', columns=["num_gp", "pct_gp", "gp_wr", "gp_wr_z"])
@@ -150,9 +151,9 @@ Spells is not affiliated with 17Lands. Please review the [Usage Guidelines](http
150
151
  - `extensions` allows for the specification of arbitrarily complex derived columns and aggregations, including custom columns built on top of custom columns.
151
152
  ```python
152
153
  >>> import polars as pl
153
- >>> from spells.columns import ColumnSpec
154
+ >>> from spells.columns import ColSpec
154
155
  >>> from spells.enums import ColType, View, ColName
155
- >>> ext = ColumnSpec(
156
+ >>> ext = ColSpec(
156
157
  ... name='deq_base',
157
158
  ... col_type=ColType.AGG,
158
159
  ... expr=(pl.col('gp_wr_excess') + 0.03 * (1 - pl.col('ata')/14).pow(2)) * pl.col('pct_gp'),
@@ -180,8 +181,34 @@ Spells is not affiliated with 17Lands. Please review the [Usage Guidelines](http
180
181
  │ Oblivious Bookworm ┆ 0.028605 ┆ uncommon ┆ GU │
181
182
  └──────────────────────────┴──────────┴──────────┴───────┘
182
183
  ```
183
-
184
- Note the use of chained calls to the Polars DataFrame api to perform manipulations on the result of `summon`.
184
+ - `card_context` takes a name-indexed DataFrame or name-keyed dict and allows the construction of column definitions based on the results.
185
+ ```python
186
+ >>> deq = spells.summon('DSK', columns=['deq_base'], filter_spec={'player_cohort': 'Top'}, extensions=[ext])
187
+ >>> ext = [
188
+ ... ColSpec(
189
+ ... name='picked_deq_base',
190
+ ... col_type=ColType.PICK_SUM,
191
+ ... expr=lambda name, card_context: card_context[name]['deq_base']
192
+ ... ),
193
+ ... ColSpec(
194
+ ... name='picked_deq_base_avg',
195
+ ... col_type=ColType.AGG,
196
+ ... expr=pl.col('picked_deq_base') / pl.col('num_taken')
197
+ ... ),
198
+ ... ]
199
+ >>> spells.summon('DSK', columns=['picked_deq_base_avg'], group_by=['player_cohort'], extensions=ext, card_context=deq)
200
+ shape: (4, 2)
201
+ ┌───────────────┬─────────────────────┐
202
+ │ player_cohort ┆ picked_deq_base_avg │
203
+ │ --- ┆ --- │
204
+ │ str ┆ f64 │
205
+ ╞═══════════════╪═════════════════════╡
206
+ │ Bottom ┆ 0.004826 │
207
+ │ Middle ┆ 0.00532 │
208
+ │ Other ┆ 0.004895 │
209
+ │ Top ┆ 0.005659 │
210
+ └───────────────┴─────────────────────┘
211
+ ```
185
212
 
186
213
  ## Installation
187
214
 
@@ -289,7 +316,7 @@ aggregations of non-numeric (or numeric) data types are not supported. If `None`
289
316
  - `{'lhs': 'player_cohort', 'op': 'in', 'rhs': ['Top', 'Middle']}` "player_cohort" value is either "Top" or "Middle". Supported values for `op` are `<`, `<=`, `>`, `>=`, `!=`, `=`, `in` and `nin`.
290
317
  - `{'$and': [{'lhs': 'draft_date', 'op': '>', 'rhs': datetime.date(2024, 10, 7)}, {'rank': 'Mythic'}]}` Drafts after October 7 by Mythic-ranked players. Supported values for query construction keys are `$and`, `$or`, and `$not`.
291
318
 
292
- - extensions: a list of `spells.columns.ColumnSpec` objects, which are appended to the definitions built-in columns described below. A name not in the enum `ColName` can be used in this way if it is the name of a provided extension. Existing names can also be redefined using extensions.
319
+ - extensions: a list of `spells.columns.ColSpec` objects, which are appended to the definitions built-in columns described below. A name not in the enum `ColName` can be used in this way if it is the name of a provided extension. Existing names can also be redefined using extensions.
293
320
 
294
321
  - read_cache/write_cache: Use the local file system to cache and retrieve aggregations to minimize expensive reads of the large datasets. You shouldn't need to touch these arguments unless you are debugging.
295
322
 
@@ -301,12 +328,12 @@ from spells.enums import ColName, ColType
301
328
 
302
329
  Recommended to import `ColName` for any usage of `summon`, and to import `ColType` when defining custom extensions.
303
330
 
304
- ### ColumnSpec
331
+ ### ColSpec
305
332
 
306
333
  ```python
307
- from spells.columns import ColumnSpec
334
+ from spells.columns import ColSpec
308
335
 
309
- ColumnSpec(
336
+ ColSpec(
310
337
  name: str,
311
338
  col_type: ColType,
312
339
  expr: pl.Expr | Callable[..., pl.Expr] | None = None,
@@ -366,6 +393,7 @@ A table of all included columns. Columns can be referenced by enum or by string
366
393
  | `PICK_NUMBER` | `"pick_number"` | `DRAFT` | `FILTER_ONLY` | Dataset Column | Int |
367
394
  | `PICK_NUM` | `"pick_num"` | `DRAFT` | `GROUP_BY` | 1-indexed | Int |
368
395
  | `TAKEN_AT` | `"taken_at` | `DRAFT` | `PICK_SUM` | Summable alias of `PICK_NUM` | Int |
396
+ | `NUM_DRAFTS` | `"num_drafts"` | `DRAFT` | `PICK_SUM` | | Int |
369
397
  | `NUM_TAKEN` | `"num_taken"` | `DRAFT` | `PICK_SUM` | Sum 1 over rows | Int |
370
398
  | `PICK` | `"pick"` | `DRAFT` | `FILTER_ONLY` | Dataset Column, joined as "name" | String |
371
399
  | `PICK_MAINDECK_RATE` | `"pick_maindeck_rate"` | `DRAFT` | `PICK_SUM` | Dataset Column | Float |
@@ -63,8 +63,9 @@ Spells is not affiliated with 17Lands. Please review the [Usage Guidelines](http
63
63
  - Supports calculating the standard aggregations and measures out of the box with no arguments (ALSA, GIH WR, etc)
64
64
  - Caches aggregate DataFrames in the local file system automatically for instantaneous reproduction of previous analysis
65
65
  - Manages grouping and filtering by built-in and custom columns at the row level
66
- - Provides 121 explicitly specified, enumerated, documented column definitions
66
+ - Provides 122 explicitly specified, enumerated, documented column definitions
67
67
  - Supports "Deck Color Data" aggregations with built-in column definitions.
68
+ - Lets you feed card metrics back in to column definitions to support scientific workflows like MLE
68
69
  - Provides a CLI tool `spells [add|refresh|clean|remove|info] [SET]` to download and manage external files
69
70
  - Downloads and manages public datasets from 17Lands
70
71
  - Retrieves and models booster configuration and card data from [MTGJSON](https://mtgjson.com/)
@@ -76,7 +77,7 @@ Spells is not affiliated with 17Lands. Please review the [Usage Guidelines](http
76
77
 
77
78
  ## summon
78
79
 
79
- `summon` takes four optional arguments, allowing a fully declarative specification of your desired analysis. Basic functionality not provided by this api can often be managed by simple chained calls using the polars API, e.g. sorting and post-agg filtering.
80
+ `summon` takes five optional arguments, allowing a fully declarative specification of your desired analysis. Basic functionality not provided by this api can often be managed by simple chained calls using the polars API, e.g. sorting and post-agg filtering.
80
81
  - `columns` specifies the desired output columns
81
82
  ```python
82
83
  >>> spells.summon('DSK', columns=["num_gp", "pct_gp", "gp_wr", "gp_wr_z"])
@@ -139,9 +140,9 @@ Spells is not affiliated with 17Lands. Please review the [Usage Guidelines](http
139
140
  - `extensions` allows for the specification of arbitrarily complex derived columns and aggregations, including custom columns built on top of custom columns.
140
141
  ```python
141
142
  >>> import polars as pl
142
- >>> from spells.columns import ColumnSpec
143
+ >>> from spells.columns import ColSpec
143
144
  >>> from spells.enums import ColType, View, ColName
144
- >>> ext = ColumnSpec(
145
+ >>> ext = ColSpec(
145
146
  ... name='deq_base',
146
147
  ... col_type=ColType.AGG,
147
148
  ... expr=(pl.col('gp_wr_excess') + 0.03 * (1 - pl.col('ata')/14).pow(2)) * pl.col('pct_gp'),
@@ -169,8 +170,34 @@ Spells is not affiliated with 17Lands. Please review the [Usage Guidelines](http
169
170
  │ Oblivious Bookworm ┆ 0.028605 ┆ uncommon ┆ GU │
170
171
  └──────────────────────────┴──────────┴──────────┴───────┘
171
172
  ```
172
-
173
- Note the use of chained calls to the Polars DataFrame api to perform manipulations on the result of `summon`.
173
+ - `card_context` takes a name-indexed DataFrame or name-keyed dict and allows the construction of column definitions based on the results.
174
+ ```python
175
+ >>> deq = spells.summon('DSK', columns=['deq_base'], filter_spec={'player_cohort': 'Top'}, extensions=[ext])
176
+ >>> ext = [
177
+ ... ColSpec(
178
+ ... name='picked_deq_base',
179
+ ... col_type=ColType.PICK_SUM,
180
+ ... expr=lambda name, card_context: card_context[name]['deq_base']
181
+ ... ),
182
+ ... ColSpec(
183
+ ... name='picked_deq_base_avg',
184
+ ... col_type=ColType.AGG,
185
+ ... expr=pl.col('picked_deq_base') / pl.col('num_taken')
186
+ ... ),
187
+ ... ]
188
+ >>> spells.summon('DSK', columns=['picked_deq_base_avg'], group_by=['player_cohort'], extensions=ext, card_context=deq)
189
+ shape: (4, 2)
190
+ ┌───────────────┬─────────────────────┐
191
+ │ player_cohort ┆ picked_deq_base_avg │
192
+ │ --- ┆ --- │
193
+ │ str ┆ f64 │
194
+ ╞═══════════════╪═════════════════════╡
195
+ │ Bottom ┆ 0.004826 │
196
+ │ Middle ┆ 0.00532 │
197
+ │ Other ┆ 0.004895 │
198
+ │ Top ┆ 0.005659 │
199
+ └───────────────┴─────────────────────┘
200
+ ```
174
201
 
175
202
  ## Installation
176
203
 
@@ -278,7 +305,7 @@ aggregations of non-numeric (or numeric) data types are not supported. If `None`
278
305
  - `{'lhs': 'player_cohort', 'op': 'in', 'rhs': ['Top', 'Middle']}` "player_cohort" value is either "Top" or "Middle". Supported values for `op` are `<`, `<=`, `>`, `>=`, `!=`, `=`, `in` and `nin`.
279
306
  - `{'$and': [{'lhs': 'draft_date', 'op': '>', 'rhs': datetime.date(2024, 10, 7)}, {'rank': 'Mythic'}]}` Drafts after October 7 by Mythic-ranked players. Supported values for query construction keys are `$and`, `$or`, and `$not`.
280
307
 
281
- - extensions: a list of `spells.columns.ColumnSpec` objects, which are appended to the definitions built-in columns described below. A name not in the enum `ColName` can be used in this way if it is the name of a provided extension. Existing names can also be redefined using extensions.
308
+ - extensions: a list of `spells.columns.ColSpec` objects, which are appended to the definitions built-in columns described below. A name not in the enum `ColName` can be used in this way if it is the name of a provided extension. Existing names can also be redefined using extensions.
282
309
 
283
310
  - read_cache/write_cache: Use the local file system to cache and retrieve aggregations to minimize expensive reads of the large datasets. You shouldn't need to touch these arguments unless you are debugging.
284
311
 
@@ -290,12 +317,12 @@ from spells.enums import ColName, ColType
290
317
 
291
318
  Recommended to import `ColName` for any usage of `summon`, and to import `ColType` when defining custom extensions.
292
319
 
293
- ### ColumnSpec
320
+ ### ColSpec
294
321
 
295
322
  ```python
296
- from spells.columns import ColumnSpec
323
+ from spells.columns import ColSpec
297
324
 
298
- ColumnSpec(
325
+ ColSpec(
299
326
  name: str,
300
327
  col_type: ColType,
301
328
  expr: pl.Expr | Callable[..., pl.Expr] | None = None,
@@ -355,6 +382,7 @@ A table of all included columns. Columns can be referenced by enum or by string
355
382
  | `PICK_NUMBER` | `"pick_number"` | `DRAFT` | `FILTER_ONLY` | Dataset Column | Int |
356
383
  | `PICK_NUM` | `"pick_num"` | `DRAFT` | `GROUP_BY` | 1-indexed | Int |
357
384
  | `TAKEN_AT` | `"taken_at` | `DRAFT` | `PICK_SUM` | Summable alias of `PICK_NUM` | Int |
385
+ | `NUM_DRAFTS` | `"num_drafts"` | `DRAFT` | `PICK_SUM` | | Int |
358
386
  | `NUM_TAKEN` | `"num_taken"` | `DRAFT` | `PICK_SUM` | Sum 1 over rows | Int |
359
387
  | `PICK` | `"pick"` | `DRAFT` | `FILTER_ONLY` | Dataset Column, joined as "name" | String |
360
388
  | `PICK_MAINDECK_RATE` | `"pick_maindeck_rate"` | `DRAFT` | `PICK_SUM` | Dataset Column | Float |
@@ -11,7 +11,7 @@ dependencies = [
11
11
  ]
12
12
  requires-python = ">=3.11"
13
13
  readme = "README.md"
14
- version = "0.4.0"
14
+ version = "0.5.0"
15
15
 
16
16
  [project.license]
17
17
  text = "MIT"
@@ -7,7 +7,7 @@ from spells.enums import View, ColName, ColType
7
7
 
8
8
 
9
9
  @dataclass(frozen=True)
10
- class ColumnSpec:
10
+ class ColSpec:
11
11
  name: str
12
12
  col_type: ColType
13
13
  expr: pl.Expr | Callable[..., pl.Expr] | None = None
@@ -16,7 +16,7 @@ class ColumnSpec:
16
16
 
17
17
 
18
18
  @dataclass(frozen=True)
19
- class ColumnDefinition:
19
+ class ColDef:
20
20
  name: str
21
21
  col_type: ColType
22
22
  expr: pl.Expr | tuple[pl.Expr, ...]
@@ -42,67 +42,67 @@ default_columns = [
42
42
  ]
43
43
 
44
44
  _column_specs = [
45
- ColumnSpec(
45
+ ColSpec(
46
46
  name=ColName.NAME,
47
47
  col_type=ColType.GROUP_BY,
48
48
  views=[View.CARD],
49
49
  ),
50
- ColumnSpec(
50
+ ColSpec(
51
51
  name=ColName.EXPANSION,
52
52
  col_type=ColType.GROUP_BY,
53
53
  views=[View.GAME, View.DRAFT],
54
54
  ),
55
- ColumnSpec(
55
+ ColSpec(
56
56
  name=ColName.EVENT_TYPE,
57
57
  col_type=ColType.GROUP_BY,
58
58
  views=[View.GAME, View.DRAFT],
59
59
  ),
60
- ColumnSpec(
60
+ ColSpec(
61
61
  name=ColName.DRAFT_ID,
62
62
  views=[View.GAME, View.DRAFT],
63
63
  col_type=ColType.FILTER_ONLY,
64
64
  ),
65
- ColumnSpec(
65
+ ColSpec(
66
66
  name=ColName.DRAFT_TIME,
67
67
  col_type=ColType.FILTER_ONLY,
68
68
  views=[View.GAME, View.DRAFT],
69
69
  ),
70
- ColumnSpec(
70
+ ColSpec(
71
71
  name=ColName.DRAFT_DATE,
72
72
  col_type=ColType.GROUP_BY,
73
73
  expr=pl.col(ColName.DRAFT_TIME).str.to_datetime("%Y-%m-%d %H:%M:%S").dt.date(),
74
74
  ),
75
- ColumnSpec(
75
+ ColSpec(
76
76
  name=ColName.DRAFT_DAY_OF_WEEK,
77
77
  col_type=ColType.GROUP_BY,
78
78
  expr=pl.col(ColName.DRAFT_TIME).str.to_datetime("%Y-%m-%d %H:%M:%S").dt.weekday(),
79
79
  ),
80
- ColumnSpec(
80
+ ColSpec(
81
81
  name=ColName.DRAFT_HOUR,
82
82
  col_type=ColType.GROUP_BY,
83
83
  expr=pl.col(ColName.DRAFT_TIME).str.to_datetime("%Y-%m-%d %H:%M:%S").dt.hour(),
84
84
  ),
85
- ColumnSpec(
85
+ ColSpec(
86
86
  name=ColName.DRAFT_WEEK,
87
87
  col_type=ColType.GROUP_BY,
88
88
  expr=pl.col(ColName.DRAFT_TIME).str.to_datetime("%Y-%m-%d %H:%M:%S").dt.week(),
89
89
  ),
90
- ColumnSpec(
90
+ ColSpec(
91
91
  name=ColName.RANK,
92
92
  col_type=ColType.GROUP_BY,
93
93
  views=[View.GAME, View.DRAFT],
94
94
  ),
95
- ColumnSpec(
95
+ ColSpec(
96
96
  name=ColName.USER_N_GAMES_BUCKET,
97
97
  col_type=ColType.GROUP_BY,
98
98
  views=[View.DRAFT, View.GAME],
99
99
  ),
100
- ColumnSpec(
100
+ ColSpec(
101
101
  name=ColName.USER_GAME_WIN_RATE_BUCKET,
102
102
  col_type=ColType.GROUP_BY,
103
103
  views=[View.DRAFT, View.GAME],
104
104
  ),
105
- ColumnSpec(
105
+ ColSpec(
106
106
  name=ColName.PLAYER_COHORT,
107
107
  col_type=ColType.GROUP_BY,
108
108
  expr=pl.when(pl.col(ColName.USER_N_GAMES_BUCKET) < 100)
@@ -117,303 +117,308 @@ _column_specs = [
117
117
  )
118
118
  ),
119
119
  ),
120
- ColumnSpec(
120
+ ColSpec(
121
121
  name=ColName.EVENT_MATCH_WINS,
122
122
  col_type=ColType.GROUP_BY,
123
123
  views=[View.DRAFT],
124
124
  ),
125
- ColumnSpec(
125
+ ColSpec(
126
126
  name=ColName.EVENT_MATCH_WINS_SUM,
127
127
  col_type=ColType.PICK_SUM,
128
128
  views=[View.DRAFT],
129
129
  expr=pl.col(ColName.EVENT_MATCH_WINS),
130
130
  ),
131
- ColumnSpec(
131
+ ColSpec(
132
132
  name=ColName.EVENT_MATCH_LOSSES,
133
133
  col_type=ColType.GROUP_BY,
134
134
  views=[View.DRAFT],
135
135
  ),
136
- ColumnSpec(
136
+ ColSpec(
137
137
  name=ColName.EVENT_MATCH_LOSSES_SUM,
138
138
  col_type=ColType.PICK_SUM,
139
139
  expr=pl.col(ColName.EVENT_MATCH_LOSSES),
140
140
  ),
141
- ColumnSpec(
141
+ ColSpec(
142
142
  name=ColName.EVENT_MATCHES,
143
143
  col_type=ColType.GROUP_BY,
144
144
  expr=pl.col(ColName.EVENT_MATCH_WINS) + pl.col(ColName.EVENT_MATCH_LOSSES),
145
145
  ),
146
- ColumnSpec(
146
+ ColSpec(
147
147
  name=ColName.EVENT_MATCHES_SUM,
148
148
  col_type=ColType.PICK_SUM,
149
149
  expr=pl.col(ColName.EVENT_MATCHES),
150
150
  ),
151
- ColumnSpec(
151
+ ColSpec(
152
152
  name=ColName.IS_TROPHY,
153
153
  col_type=ColType.GROUP_BY,
154
154
  expr=pl.when(pl.col(ColName.EVENT_TYPE) == "Traditional")
155
155
  .then(pl.col(ColName.EVENT_MATCH_WINS) == 3)
156
156
  .otherwise(pl.col(ColName.EVENT_MATCH_WINS) == 7),
157
157
  ),
158
- ColumnSpec(
158
+ ColSpec(
159
159
  name=ColName.IS_TROPHY_SUM,
160
160
  col_type=ColType.PICK_SUM,
161
161
  expr=pl.col(ColName.IS_TROPHY),
162
162
  ),
163
- ColumnSpec(
163
+ ColSpec(
164
164
  name=ColName.PACK_NUMBER,
165
165
  col_type=ColType.FILTER_ONLY, # use pack_num
166
166
  views=[View.DRAFT],
167
167
  ),
168
- ColumnSpec(
168
+ ColSpec(
169
169
  name=ColName.PACK_NUM,
170
170
  col_type=ColType.GROUP_BY,
171
171
  expr=pl.col(ColName.PACK_NUMBER) + 1,
172
172
  ),
173
- ColumnSpec(
173
+ ColSpec(
174
174
  name=ColName.PICK_NUMBER,
175
175
  col_type=ColType.FILTER_ONLY, # use pick_num
176
176
  views=[View.DRAFT],
177
177
  ),
178
- ColumnSpec(
178
+ ColSpec(
179
179
  name=ColName.PICK_NUM,
180
180
  col_type=ColType.GROUP_BY,
181
181
  expr=pl.col(ColName.PICK_NUMBER) + 1,
182
182
  ),
183
- ColumnSpec(
183
+ ColSpec(
184
184
  name=ColName.TAKEN_AT,
185
185
  col_type=ColType.PICK_SUM,
186
186
  expr=pl.col(ColName.PICK_NUM),
187
187
  ),
188
- ColumnSpec(
188
+ ColSpec(
189
189
  name=ColName.NUM_TAKEN,
190
190
  col_type=ColType.PICK_SUM,
191
191
  expr=pl.when(pl.col(ColName.PICK).is_not_null())
192
192
  .then(1)
193
193
  .otherwise(0),
194
194
  ),
195
- ColumnSpec(
195
+ ColSpec(
196
+ name=ColName.NUM_DRAFTS,
197
+ col_type=ColType.PICK_SUM,
198
+ expr=pl.when((pl.col(ColName.PACK_NUMBER) == 0) & (pl.col(ColName.PICK_NUMBER) == 0)).then(1).otherwise(0),
199
+ ),
200
+ ColSpec(
196
201
  name=ColName.PICK,
197
202
  col_type=ColType.FILTER_ONLY,
198
203
  views=[View.DRAFT],
199
204
  ),
200
- ColumnSpec(
205
+ ColSpec(
201
206
  name=ColName.PICK_MAINDECK_RATE,
202
207
  col_type=ColType.PICK_SUM,
203
208
  views=[View.DRAFT],
204
209
  ),
205
- ColumnSpec(
210
+ ColSpec(
206
211
  name=ColName.PICK_SIDEBOARD_IN_RATE,
207
212
  col_type=ColType.PICK_SUM,
208
213
  views=[View.DRAFT],
209
214
  ),
210
- ColumnSpec(
215
+ ColSpec(
211
216
  name=ColName.PACK_CARD,
212
217
  col_type=ColType.NAME_SUM,
213
218
  views=[View.DRAFT],
214
219
  ),
215
- ColumnSpec(
220
+ ColSpec(
216
221
  name=ColName.LAST_SEEN,
217
222
  col_type=ColType.NAME_SUM,
218
223
  expr=lambda name: pl.col(f"pack_card_{name}")
219
224
  * pl.min_horizontal(ColName.PICK_NUM, 8),
220
225
  ),
221
- ColumnSpec(
226
+ ColSpec(
222
227
  name=ColName.NUM_SEEN,
223
228
  col_type=ColType.NAME_SUM,
224
229
  expr=lambda name: pl.col(f"pack_card_{name}") * (pl.col(ColName.PICK_NUM) <= 8),
225
230
  ),
226
- ColumnSpec(
231
+ ColSpec(
227
232
  name=ColName.POOL,
228
233
  col_type=ColType.NAME_SUM,
229
234
  views=[View.DRAFT],
230
235
  ),
231
- ColumnSpec(
236
+ ColSpec(
232
237
  name=ColName.GAME_TIME,
233
238
  col_type=ColType.FILTER_ONLY,
234
239
  views=[View.GAME],
235
240
  ),
236
- ColumnSpec(
241
+ ColSpec(
237
242
  name=ColName.GAME_DATE,
238
243
  col_type=ColType.GROUP_BY,
239
244
  expr=pl.col(ColName.GAME_TIME).str.to_datetime("%Y-%m-%d %H-%M-%S").dt.date(),
240
245
  ),
241
- ColumnSpec(
246
+ ColSpec(
242
247
  name=ColName.GAME_DAY_OF_WEEK,
243
248
  col_type=ColType.GROUP_BY,
244
249
  expr=pl.col(ColName.GAME_TIME).str.to_datetime("%Y-%m-%d %H-%M-%S").dt.weekday(),
245
250
  ),
246
- ColumnSpec(
251
+ ColSpec(
247
252
  name=ColName.GAME_HOUR,
248
253
  col_type=ColType.GROUP_BY,
249
254
  expr=pl.col(ColName.GAME_TIME).str.to_datetime("%Y-%m-%d %H-%M-%S").dt.hour(),
250
255
  ),
251
- ColumnSpec(
256
+ ColSpec(
252
257
  name=ColName.GAME_WEEK,
253
258
  col_type=ColType.GROUP_BY,
254
259
  expr=pl.col(ColName.GAME_TIME).str.to_datetime("%Y-%m-%d %H-%M-%S").dt.week(),
255
260
  ),
256
- ColumnSpec(
261
+ ColSpec(
257
262
  name=ColName.BUILD_INDEX,
258
263
  col_type=ColType.GROUP_BY,
259
264
  views=[View.GAME],
260
265
  ),
261
- ColumnSpec(
266
+ ColSpec(
262
267
  name=ColName.MATCH_NUMBER,
263
268
  col_type=ColType.GROUP_BY,
264
269
  views=[View.GAME],
265
270
  ),
266
- ColumnSpec(
271
+ ColSpec(
267
272
  name=ColName.GAME_NUMBER,
268
273
  col_type=ColType.GROUP_BY,
269
274
  views=[View.GAME],
270
275
  ),
271
- ColumnSpec(
276
+ ColSpec(
272
277
  name=ColName.NUM_GAMES,
273
278
  col_type=ColType.GAME_SUM,
274
279
  expr=pl.col(ColName.GAME_NUMBER).is_not_null(),
275
280
  ),
276
- ColumnSpec(
281
+ ColSpec(
277
282
  name=ColName.NUM_MATCHES,
278
283
  col_type=ColType.GAME_SUM,
279
284
  expr=pl.col(ColName.GAME_NUMBER) == 1,
280
285
  ),
281
- ColumnSpec(
286
+ ColSpec(
282
287
  name=ColName.NUM_EVENTS,
283
288
  col_type=ColType.GAME_SUM,
284
289
  expr=(pl.col(ColName.GAME_NUMBER) == 1) & (pl.col(ColName.MATCH_NUMBER) == 1),
285
290
  ),
286
- ColumnSpec(
291
+ ColSpec(
287
292
  name=ColName.OPP_RANK,
288
293
  col_type=ColType.GROUP_BY,
289
294
  views=[View.GAME],
290
295
  ),
291
- ColumnSpec(
296
+ ColSpec(
292
297
  name=ColName.MAIN_COLORS,
293
298
  col_type=ColType.GROUP_BY,
294
299
  views=[View.GAME],
295
300
  ),
296
- ColumnSpec(
301
+ ColSpec(
297
302
  name=ColName.NUM_COLORS,
298
303
  col_type=ColType.GROUP_BY,
299
304
  expr=pl.col(ColName.MAIN_COLORS).str.len_chars(),
300
305
  ),
301
- ColumnSpec(
306
+ ColSpec(
302
307
  name=ColName.SPLASH_COLORS,
303
308
  col_type=ColType.GROUP_BY,
304
309
  views=[View.GAME],
305
310
  ),
306
- ColumnSpec(
311
+ ColSpec(
307
312
  name=ColName.HAS_SPLASH,
308
313
  col_type=ColType.GROUP_BY,
309
314
  expr=pl.col(ColName.SPLASH_COLORS).str.len_chars() > 0,
310
315
  ),
311
- ColumnSpec(
316
+ ColSpec(
312
317
  name=ColName.ON_PLAY,
313
318
  col_type=ColType.GROUP_BY,
314
319
  views=[View.GAME],
315
320
  ),
316
- ColumnSpec(
321
+ ColSpec(
317
322
  name=ColName.NUM_ON_PLAY,
318
323
  col_type=ColType.GAME_SUM,
319
324
  expr=pl.col(ColName.ON_PLAY),
320
325
  ),
321
- ColumnSpec(
326
+ ColSpec(
322
327
  name=ColName.NUM_MULLIGANS,
323
328
  col_type=ColType.GROUP_BY,
324
329
  views=[View.GAME],
325
330
  ),
326
- ColumnSpec(
331
+ ColSpec(
327
332
  name=ColName.NUM_MULLIGANS_SUM,
328
333
  col_type=ColType.GAME_SUM,
329
334
  expr=pl.col(ColName.NUM_MULLIGANS),
330
335
  ),
331
- ColumnSpec(
336
+ ColSpec(
332
337
  name=ColName.OPP_NUM_MULLIGANS,
333
338
  col_type=ColType.GAME_SUM,
334
339
  views=[View.GAME],
335
340
  ),
336
- ColumnSpec(
341
+ ColSpec(
337
342
  name=ColName.OPP_NUM_MULLIGANS_SUM,
338
343
  col_type=ColType.GAME_SUM,
339
344
  expr=pl.col(ColName.OPP_NUM_MULLIGANS),
340
345
  ),
341
- ColumnSpec(
346
+ ColSpec(
342
347
  name=ColName.OPP_COLORS,
343
348
  col_type=ColType.GROUP_BY,
344
349
  views=[View.GAME],
345
350
  ),
346
- ColumnSpec(
351
+ ColSpec(
347
352
  name=ColName.NUM_TURNS,
348
353
  col_type=ColType.GROUP_BY,
349
354
  views=[View.GAME],
350
355
  ),
351
- ColumnSpec(
356
+ ColSpec(
352
357
  name=ColName.NUM_TURNS_SUM,
353
358
  col_type=ColType.GAME_SUM,
354
359
  expr=pl.col(ColName.NUM_TURNS),
355
360
  ),
356
- ColumnSpec(
361
+ ColSpec(
357
362
  name=ColName.WON,
358
363
  col_type=ColType.GROUP_BY,
359
364
  views=[View.GAME],
360
365
  ),
361
- ColumnSpec(
366
+ ColSpec(
362
367
  name=ColName.NUM_WON,
363
368
  col_type=ColType.GAME_SUM,
364
369
  expr=pl.col(ColName.WON),
365
370
  ),
366
- ColumnSpec(
371
+ ColSpec(
367
372
  name=ColName.OPENING_HAND,
368
373
  col_type=ColType.NAME_SUM,
369
374
  views=[View.GAME],
370
375
  ),
371
- ColumnSpec(
376
+ ColSpec(
372
377
  name=ColName.WON_OPENING_HAND,
373
378
  col_type=ColType.NAME_SUM,
374
379
  expr=lambda name: pl.col(f"opening_hand_{name}") * pl.col(ColName.WON),
375
380
  ),
376
- ColumnSpec(
381
+ ColSpec(
377
382
  name=ColName.DRAWN,
378
383
  col_type=ColType.NAME_SUM,
379
384
  views=[View.GAME],
380
385
  ),
381
- ColumnSpec(
386
+ ColSpec(
382
387
  name=ColName.WON_DRAWN,
383
388
  col_type=ColType.NAME_SUM,
384
389
  expr=lambda name: pl.col(f"drawn_{name}") * pl.col(ColName.WON),
385
390
  ),
386
- ColumnSpec(
391
+ ColSpec(
387
392
  name=ColName.TUTORED,
388
393
  col_type=ColType.NAME_SUM,
389
394
  views=[View.GAME],
390
395
  ),
391
- ColumnSpec(
396
+ ColSpec(
392
397
  name=ColName.WON_TUTORED,
393
398
  col_type=ColType.NAME_SUM,
394
399
  expr=lambda name: pl.col(f"tutored_{name}") * pl.col(ColName.WON),
395
400
  ),
396
- ColumnSpec(
401
+ ColSpec(
397
402
  name=ColName.DECK,
398
403
  col_type=ColType.NAME_SUM,
399
404
  views=[View.GAME],
400
405
  ),
401
- ColumnSpec(
406
+ ColSpec(
402
407
  name=ColName.WON_DECK,
403
408
  col_type=ColType.NAME_SUM,
404
409
  expr=lambda name: pl.col(f"deck_{name}") * pl.col(ColName.WON),
405
410
  ),
406
- ColumnSpec(
411
+ ColSpec(
407
412
  name=ColName.SIDEBOARD,
408
413
  col_type=ColType.NAME_SUM,
409
414
  views=[View.GAME],
410
415
  ),
411
- ColumnSpec(
416
+ ColSpec(
412
417
  name=ColName.WON_SIDEBOARD,
413
418
  col_type=ColType.NAME_SUM,
414
419
  expr=lambda name: pl.col(f"sideboard_{name}") * pl.col(ColName.WON),
415
420
  ),
416
- ColumnSpec(
421
+ ColSpec(
417
422
  name=ColName.NUM_GNS,
418
423
  col_type=ColType.NAME_SUM,
419
424
  expr=lambda name: pl.max_horizontal(
@@ -424,251 +429,251 @@ _column_specs = [
424
429
  - pl.col(f"opening_hand_{name}"),
425
430
  ),
426
431
  ),
427
- ColumnSpec(
432
+ ColSpec(
428
433
  name=ColName.WON_NUM_GNS,
429
434
  col_type=ColType.NAME_SUM,
430
435
  expr=lambda name: pl.col(ColName.WON) * pl.col(f"num_gns_{name}"),
431
436
  ),
432
- ColumnSpec(
437
+ ColSpec(
433
438
  name=ColName.SET_CODE,
434
439
  col_type=ColType.CARD_ATTR,
435
440
  ),
436
- ColumnSpec(
441
+ ColSpec(
437
442
  name=ColName.COLOR,
438
443
  col_type=ColType.CARD_ATTR,
439
444
  ),
440
- ColumnSpec(
445
+ ColSpec(
441
446
  name=ColName.RARITY,
442
447
  col_type=ColType.CARD_ATTR,
443
448
  ),
444
- ColumnSpec(
449
+ ColSpec(
445
450
  name=ColName.COLOR_IDENTITY,
446
451
  col_type=ColType.CARD_ATTR,
447
452
  ),
448
- ColumnSpec(
453
+ ColSpec(
449
454
  name=ColName.CARD_TYPE,
450
455
  col_type=ColType.CARD_ATTR,
451
456
  ),
452
- ColumnSpec(
457
+ ColSpec(
453
458
  name=ColName.SUBTYPE,
454
459
  col_type=ColType.CARD_ATTR,
455
460
  ),
456
- ColumnSpec(
461
+ ColSpec(
457
462
  name=ColName.MANA_VALUE,
458
463
  col_type=ColType.CARD_ATTR,
459
464
  ),
460
- ColumnSpec(
465
+ ColSpec(
461
466
  name=ColName.DECK_MANA_VALUE,
462
467
  col_type=ColType.NAME_SUM,
463
468
  expr=lambda name, card_context: card_context[name][ColName.MANA_VALUE] * pl.col(f"deck_{name}"),
464
469
  ),
465
- ColumnSpec(
470
+ ColSpec(
466
471
  name=ColName.DECK_LANDS,
467
472
  col_type=ColType.NAME_SUM,
468
473
  expr=lambda name, card_context: pl.col(f"deck_{name}") * ( 1 if 'Land' in card_context[name][ColName.CARD_TYPE] else 0 )
469
474
  ),
470
- ColumnSpec(
475
+ ColSpec(
471
476
  name=ColName.DECK_SPELLS,
472
477
  col_type=ColType.NAME_SUM,
473
478
  expr=lambda name: pl.col(f"deck_{name}") - pl.col(f"deck_lands_{name}"),
474
479
  ),
475
- ColumnSpec(
480
+ ColSpec(
476
481
  name=ColName.MANA_COST,
477
482
  col_type=ColType.CARD_ATTR,
478
483
  ),
479
- ColumnSpec(
484
+ ColSpec(
480
485
  name=ColName.POWER,
481
486
  col_type=ColType.CARD_ATTR,
482
487
  ),
483
- ColumnSpec(
488
+ ColSpec(
484
489
  name=ColName.TOUGHNESS,
485
490
  col_type=ColType.CARD_ATTR,
486
491
  ),
487
- ColumnSpec(
492
+ ColSpec(
488
493
  name=ColName.IS_BONUS_SHEET,
489
494
  col_type=ColType.CARD_ATTR,
490
495
  ),
491
- ColumnSpec(
496
+ ColSpec(
492
497
  name=ColName.IS_DFC,
493
498
  col_type=ColType.CARD_ATTR,
494
499
  ),
495
- ColumnSpec(
500
+ ColSpec(
496
501
  name=ColName.ORACLE_TEXT,
497
502
  col_type=ColType.CARD_ATTR,
498
503
  ),
499
- ColumnSpec(
504
+ ColSpec(
500
505
  name=ColName.CARD_JSON,
501
506
  col_type=ColType.CARD_ATTR,
502
507
  ),
503
- ColumnSpec(
508
+ ColSpec(
504
509
  name=ColName.PICKED_MATCH_WR,
505
510
  col_type=ColType.AGG,
506
- expr=pl.col(ColName.EVENT_MATCH_WINS_SUM) / pl.col(ColName.EVENT_MATCHES),
511
+ expr=pl.col(ColName.EVENT_MATCH_WINS_SUM) / pl.col(ColName.EVENT_MATCHES_SUM),
507
512
  ),
508
- ColumnSpec(
513
+ ColSpec(
509
514
  name=ColName.TROPHY_RATE,
510
515
  col_type=ColType.AGG,
511
516
  expr=pl.col(ColName.IS_TROPHY_SUM) / pl.col(ColName.NUM_TAKEN),
512
517
  ),
513
- ColumnSpec(
518
+ ColSpec(
514
519
  name=ColName.GAME_WR,
515
520
  col_type=ColType.AGG,
516
521
  expr=pl.col(ColName.NUM_WON) / pl.col(ColName.NUM_GAMES),
517
522
  ),
518
- ColumnSpec(
523
+ ColSpec(
519
524
  name=ColName.ALSA,
520
525
  col_type=ColType.AGG,
521
526
  expr=pl.col(ColName.LAST_SEEN) / pl.col(ColName.NUM_SEEN),
522
527
  ),
523
- ColumnSpec(
528
+ ColSpec(
524
529
  name=ColName.ATA,
525
530
  col_type=ColType.AGG,
526
531
  expr=pl.col(ColName.TAKEN_AT) / pl.col(ColName.NUM_TAKEN),
527
532
  ),
528
- ColumnSpec(
533
+ ColSpec(
529
534
  name=ColName.NUM_GP,
530
535
  col_type=ColType.AGG,
531
536
  expr=pl.col(ColName.DECK),
532
537
  ),
533
- ColumnSpec(
538
+ ColSpec(
534
539
  name=ColName.PCT_GP,
535
540
  col_type=ColType.AGG,
536
541
  expr=pl.col(ColName.DECK) / (pl.col(ColName.DECK) + pl.col(ColName.SIDEBOARD)),
537
542
  ),
538
- ColumnSpec(
543
+ ColSpec(
539
544
  name=ColName.GP_WR,
540
545
  col_type=ColType.AGG,
541
546
  expr=pl.col(ColName.WON_DECK) / pl.col(ColName.DECK),
542
547
  ),
543
- ColumnSpec(
548
+ ColSpec(
544
549
  name=ColName.NUM_OH,
545
550
  col_type=ColType.AGG,
546
551
  expr=pl.col(ColName.OPENING_HAND),
547
552
  ),
548
- ColumnSpec(
553
+ ColSpec(
549
554
  name=ColName.OH_WR,
550
555
  col_type=ColType.AGG,
551
556
  expr=pl.col(ColName.WON_OPENING_HAND) / pl.col(ColName.OPENING_HAND),
552
557
  ),
553
- ColumnSpec(
558
+ ColSpec(
554
559
  name=ColName.NUM_GIH,
555
560
  col_type=ColType.AGG,
556
561
  expr=pl.col(ColName.OPENING_HAND) + pl.col(ColName.DRAWN),
557
562
  ),
558
- ColumnSpec(
563
+ ColSpec(
559
564
  name=ColName.NUM_GIH_WON,
560
565
  col_type=ColType.AGG,
561
566
  expr=pl.col(ColName.WON_OPENING_HAND) + pl.col(ColName.WON_DRAWN),
562
567
  ),
563
- ColumnSpec(
568
+ ColSpec(
564
569
  name=ColName.GIH_WR,
565
570
  col_type=ColType.AGG,
566
571
  expr=pl.col(ColName.NUM_GIH_WON) / pl.col(ColName.NUM_GIH),
567
572
  ),
568
- ColumnSpec(
573
+ ColSpec(
569
574
  name=ColName.GNS_WR,
570
575
  col_type=ColType.AGG,
571
576
  expr=pl.col(ColName.WON_NUM_GNS) / pl.col(ColName.NUM_GNS),
572
577
  ),
573
- ColumnSpec(
578
+ ColSpec(
574
579
  name=ColName.IWD,
575
580
  col_type=ColType.AGG,
576
581
  expr=pl.col(ColName.GIH_WR) - pl.col(ColName.GNS_WR),
577
582
  ),
578
- ColumnSpec(
583
+ ColSpec(
579
584
  name=ColName.NUM_IN_POOL,
580
585
  col_type=ColType.AGG,
581
586
  expr=pl.col(ColName.DECK) + pl.col(ColName.SIDEBOARD),
582
587
  ),
583
- ColumnSpec(
588
+ ColSpec(
584
589
  name=ColName.IN_POOL_WR,
585
590
  col_type=ColType.AGG,
586
591
  expr=(pl.col(ColName.WON_DECK) + pl.col(ColName.WON_SIDEBOARD))
587
592
  / pl.col(ColName.NUM_IN_POOL),
588
593
  ),
589
- ColumnSpec(
594
+ ColSpec(
590
595
  name=ColName.DECK_TOTAL,
591
596
  col_type=ColType.AGG,
592
597
  expr=pl.col(ColName.DECK).sum(),
593
598
  ),
594
- ColumnSpec(
599
+ ColSpec(
595
600
  name=ColName.WON_DECK_TOTAL,
596
601
  col_type=ColType.AGG,
597
602
  expr=pl.col(ColName.WON_DECK).sum(),
598
603
  ),
599
- ColumnSpec(
604
+ ColSpec(
600
605
  name=ColName.GP_WR_MEAN,
601
606
  col_type=ColType.AGG,
602
607
  expr=pl.col(ColName.WON_DECK_TOTAL) / pl.col(ColName.DECK_TOTAL),
603
608
  ),
604
- ColumnSpec(
609
+ ColSpec(
605
610
  name=ColName.GP_WR_EXCESS,
606
611
  col_type=ColType.AGG,
607
612
  expr=pl.col(ColName.GP_WR) - pl.col(ColName.GP_WR_MEAN),
608
613
  ),
609
- ColumnSpec(
614
+ ColSpec(
610
615
  name=ColName.GP_WR_VAR,
611
616
  col_type=ColType.AGG,
612
617
  expr=(pl.col(ColName.GP_WR_EXCESS).pow(2) * pl.col(ColName.NUM_GP)).sum()
613
618
  / pl.col(ColName.DECK_TOTAL),
614
619
  ),
615
- ColumnSpec(
620
+ ColSpec(
616
621
  name=ColName.GP_WR_STDEV,
617
622
  col_type=ColType.AGG,
618
623
  expr=pl.col(ColName.GP_WR_VAR).sqrt(),
619
624
  ),
620
- ColumnSpec(
625
+ ColSpec(
621
626
  name=ColName.GP_WR_Z,
622
627
  col_type=ColType.AGG,
623
628
  expr=pl.col(ColName.GP_WR_EXCESS) / pl.col(ColName.GP_WR_STDEV),
624
629
  ),
625
- ColumnSpec(
630
+ ColSpec(
626
631
  name=ColName.GIH_TOTAL,
627
632
  col_type=ColType.AGG,
628
633
  expr=pl.col(ColName.NUM_GIH).sum(),
629
634
  ),
630
- ColumnSpec(
635
+ ColSpec(
631
636
  name=ColName.WON_GIH_TOTAL,
632
637
  col_type=ColType.AGG,
633
638
  expr=pl.col(ColName.NUM_GIH_WON).sum(),
634
639
  ),
635
- ColumnSpec(
640
+ ColSpec(
636
641
  name=ColName.GIH_WR_MEAN,
637
642
  col_type=ColType.AGG,
638
643
  expr=pl.col(ColName.WON_GIH_TOTAL) / pl.col(ColName.GIH_TOTAL),
639
644
  ),
640
- ColumnSpec(
645
+ ColSpec(
641
646
  name=ColName.GIH_WR_EXCESS,
642
647
  col_type=ColType.AGG,
643
648
  expr=pl.col(ColName.GIH_WR) - pl.col(ColName.GIH_WR_MEAN),
644
649
  ),
645
- ColumnSpec(
650
+ ColSpec(
646
651
  name=ColName.GIH_WR_VAR,
647
652
  col_type=ColType.AGG,
648
653
  expr=(pl.col(ColName.GIH_WR_EXCESS).pow(2) * pl.col(ColName.NUM_GIH)).sum()
649
654
  / pl.col(ColName.GIH_TOTAL),
650
655
  ),
651
- ColumnSpec(
656
+ ColSpec(
652
657
  name=ColName.GIH_WR_STDEV,
653
658
  col_type=ColType.AGG,
654
659
  expr=pl.col(ColName.GIH_WR_VAR).sqrt(),
655
660
  ),
656
- ColumnSpec(
661
+ ColSpec(
657
662
  name=ColName.GIH_WR_Z,
658
663
  col_type=ColType.AGG,
659
664
  expr=pl.col(ColName.GIH_WR_EXCESS) / pl.col(ColName.GIH_WR_STDEV),
660
665
  ),
661
- ColumnSpec(
666
+ ColSpec(
662
667
  name=ColName.DECK_MANA_VALUE_AVG,
663
668
  col_type=ColType.AGG,
664
669
  expr=pl.col(ColName.DECK_MANA_VALUE) / pl.col(ColName.DECK_SPELLS),
665
670
  ),
666
- ColumnSpec(
671
+ ColSpec(
667
672
  name=ColName.DECK_LANDS_AVG,
668
673
  col_type=ColType.AGG,
669
674
  expr=pl.col(ColName.DECK_LANDS) / pl.col(ColName.NUM_GAMES),
670
675
  ),
671
- ColumnSpec(
676
+ ColSpec(
672
677
  name=ColName.DECK_SPELLS_AVG,
673
678
  col_type=ColType.AGG,
674
679
  expr=pl.col(ColName.DECK_SPELLS) / pl.col(ColName.NUM_GAMES),
@@ -14,12 +14,13 @@ from inspect import signature
14
14
  from typing import Callable, TypeVar, Any
15
15
 
16
16
  import polars as pl
17
+ from polars.exceptions import ColumnNotFoundError
17
18
 
18
19
  from spells.external import data_file_path
19
20
  import spells.cache
20
21
  import spells.filter
21
22
  import spells.manifest
22
- from spells.columns import ColumnDefinition, ColumnSpec
23
+ from spells.columns import ColDef, ColSpec
23
24
  from spells.enums import View, ColName, ColType
24
25
 
25
26
 
@@ -53,7 +54,7 @@ def _get_names(set_code: str) -> list[str]:
53
54
  return names
54
55
 
55
56
 
56
- def _get_card_context(set_code: str, col_spec_map: dict[str, ColumnSpec]) -> dict[str, dict[str, Any]]:
57
+ def _get_card_context(set_code: str, col_spec_map: dict[str, ColSpec], card_context: pl.DataFrame | dict[str, dict[str, Any]] | None) -> dict[str, dict[str, Any]]:
57
58
  card_attr_specs = {col:spec for col, spec in col_spec_map.items() if spec.col_type == ColType.CARD_ATTR or spec.name == ColName.NAME}
58
59
  col_def_map = _hydrate_col_defs(set_code, card_attr_specs, card_only=True)
59
60
 
@@ -65,12 +66,25 @@ def _get_card_context(set_code: str, col_spec_map: dict[str, ColumnSpec]) -> dic
65
66
  card_df, frozenset(columns), col_def_map, is_agg_view=False
66
67
  ).to_dicts()
67
68
 
68
- card_context = {row[ColName.NAME]: row for row in select_rows}
69
+ loaded_context = {row[ColName.NAME]: row for row in select_rows}
69
70
 
70
- return card_context
71
+ if card_context is not None:
72
+ if isinstance(card_context, pl.DataFrame):
73
+ try:
74
+ card_context = {row[ColName.NAME]: row for row in card_context.to_dicts()}
75
+ except ColumnNotFoundError:
76
+ raise ValueError("card_context DataFrame must have column 'name'")
77
+
78
+ names = list(loaded_context.keys())
79
+ for name in names:
80
+ assert name in card_context, f"card_context must include a row for each card name. {name} missing."
81
+ for col, value in card_context[name].items():
82
+ loaded_context[name][col] = value
83
+
84
+ return loaded_context
71
85
 
72
86
 
73
- def _determine_expression(spec: ColumnSpec, names: list[str], card_context: dict[str, dict]) -> pl.Expr | tuple[pl.Expr, ...]:
87
+ def _determine_expression(spec: ColSpec, names: list[str], card_context: dict[str, dict]) -> pl.Expr | tuple[pl.Expr, ...]:
74
88
  def seed_params(expr):
75
89
  params = {}
76
90
 
@@ -115,7 +129,7 @@ def _determine_expression(spec: ColumnSpec, names: list[str], card_context: dict
115
129
  return expr
116
130
 
117
131
 
118
- def _infer_dependencies(name: str, expr: pl.Expr | tuple[pl.Expr,...], col_spec_map: dict[str, ColumnSpec], names: list[str]) -> set[str]:
132
+ def _infer_dependencies(name: str, expr: pl.Expr | tuple[pl.Expr,...], col_spec_map: dict[str, ColSpec], names: list[str]) -> set[str]:
119
133
  dependencies = set()
120
134
  tricky_ones = set()
121
135
 
@@ -150,13 +164,13 @@ def _infer_dependencies(name: str, expr: pl.Expr | tuple[pl.Expr,...], col_spec_
150
164
  return dependencies
151
165
 
152
166
 
153
- def _hydrate_col_defs(set_code: str, col_spec_map: dict[str, ColumnSpec], card_only=False):
167
+ def _hydrate_col_defs(set_code: str, col_spec_map: dict[str, ColSpec], card_context: pl.DataFrame | dict[str, dict] | None = None, card_only: bool =False):
154
168
  names = _get_names(set_code)
155
169
 
156
170
  if card_only:
157
171
  card_context = {}
158
172
  else:
159
- card_context = _get_card_context(set_code, col_spec_map)
173
+ card_context = _get_card_context(set_code, col_spec_map, card_context)
160
174
 
161
175
  assert len(names) > 0, "there should be names"
162
176
  hydrated = {}
@@ -184,7 +198,7 @@ def _hydrate_col_defs(set_code: str, col_spec_map: dict[str, ColumnSpec], card_o
184
198
  )
185
199
  )
186
200
 
187
- cdef = ColumnDefinition(
201
+ cdef = ColDef(
188
202
  name=spec.name,
189
203
  col_type=spec.col_type,
190
204
  views=set(spec.views or set()),
@@ -199,7 +213,7 @@ def _hydrate_col_defs(set_code: str, col_spec_map: dict[str, ColumnSpec], card_o
199
213
  def _view_select(
200
214
  df: DF,
201
215
  view_cols: frozenset[str],
202
- col_def_map: dict[str, ColumnDefinition],
216
+ col_def_map: dict[str, ColDef],
203
217
  is_agg_view: bool,
204
218
  ) -> DF:
205
219
  base_cols = frozenset()
@@ -339,17 +353,18 @@ def summon(
339
353
  columns: list[str] | None = None,
340
354
  group_by: list[str] | None = None,
341
355
  filter_spec: dict | None = None,
342
- extensions: list[ColumnSpec] | None = None,
356
+ extensions: list[ColSpec] | None = None,
343
357
  use_streaming: bool = False,
344
358
  read_cache: bool = True,
345
359
  write_cache: bool = True,
360
+ card_context: pl.DataFrame | dict[str, dict] | None = None
346
361
  ) -> pl.DataFrame:
347
362
  col_spec_map = dict(spells.columns.col_spec_map)
348
363
  if extensions is not None:
349
364
  for spec in extensions:
350
365
  col_spec_map[spec.name] = spec
351
366
 
352
- col_def_map = _hydrate_col_defs(set_code, col_spec_map)
367
+ col_def_map = _hydrate_col_defs(set_code, col_spec_map, card_context)
353
368
  m = spells.manifest.create(col_def_map, columns, group_by, filter_spec)
354
369
 
355
370
  calc_fn = functools.partial(_base_agg_df, set_code, m, use_streaming=use_streaming)
@@ -60,6 +60,7 @@ class ColName(StrEnum):
60
60
  PICK_NUM = "pick_num" # pick_number plus 1
61
61
  TAKEN_AT = "taken_at"
62
62
  NUM_TAKEN = "num_taken"
63
+ NUM_DRAFTS = "num_drafts"
63
64
  PICK = "pick"
64
65
  PICK_MAINDECK_RATE = "pick_maindeck_rate"
65
66
  PICK_SIDEBOARD_IN_RATE = "pick_sideboard_in_rate"
@@ -3,13 +3,13 @@ from dataclasses import dataclass
3
3
  import spells.columns
4
4
  import spells.filter
5
5
  from spells.enums import View, ColName, ColType
6
- from spells.columns import ColumnDefinition
6
+ from spells.columns import ColDef
7
7
 
8
8
 
9
9
  @dataclass(frozen=True)
10
10
  class Manifest:
11
11
  columns: tuple[str, ...]
12
- col_def_map: dict[str, ColumnDefinition]
12
+ col_def_map: dict[str, ColDef]
13
13
  base_view_group_by: frozenset[str]
14
14
  view_cols: dict[View, frozenset[str]]
15
15
  group_by: tuple[str, ...]
@@ -85,7 +85,7 @@ class Manifest:
85
85
 
86
86
  def _resolve_view_cols(
87
87
  col_set: frozenset[str],
88
- col_def_map: dict[str, ColumnDefinition],
88
+ col_def_map: dict[str, ColDef],
89
89
  ) -> dict[View, frozenset[str]]:
90
90
  """
91
91
  For each view ('game', 'draft', and 'card'), return the columns
@@ -153,7 +153,7 @@ def _resolve_view_cols(
153
153
 
154
154
 
155
155
  def create(
156
- col_def_map: dict[str, ColumnDefinition],
156
+ col_def_map: dict[str, ColDef],
157
157
  columns: list[str] | None = None,
158
158
  group_by: list[str] | None = None,
159
159
  filter_spec: dict | None = None,
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes