ltc-code 0.1.89__tar.gz → 0.1.91__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.
- {ltc_code-0.1.89 → ltc_code-0.1.91}/PKG-INFO +1 -1
- {ltc_code-0.1.89 → ltc_code-0.1.91}/pyproject.toml +1 -1
- {ltc_code-0.1.89 → ltc_code-0.1.91}/src/ltc_code/june7.py +70 -76
- {ltc_code-0.1.89 → ltc_code-0.1.91}/README.md +0 -0
- {ltc_code-0.1.89 → ltc_code-0.1.91}/src/ltc_code/__init__.py +0 -0
- {ltc_code-0.1.89 → ltc_code-0.1.91}/src/ltc_code/green_dot.py +0 -0
- {ltc_code-0.1.89 → ltc_code-0.1.91}/src/ltc_code/june2.py +0 -0
- {ltc_code-0.1.89 → ltc_code-0.1.91}/src/ltc_code/june5.py +0 -0
- {ltc_code-0.1.89 → ltc_code-0.1.91}/src/ltc_code/kipp_nj.py +0 -0
- {ltc_code-0.1.89 → ltc_code-0.1.91}/src/ltc_code/kipp_tx.py +0 -0
- {ltc_code-0.1.89 → ltc_code-0.1.91}/src/ltc_code/may27.py +0 -0
- {ltc_code-0.1.89 → ltc_code-0.1.91}/src/ltc_code/polars_dates.py +0 -0
- {ltc_code-0.1.89 → ltc_code-0.1.91}/src/ltc_code/schema_mapping.py +0 -0
|
@@ -11,20 +11,8 @@ def resolve_rank_choice_duplicate_applications(
|
|
|
11
11
|
) -> Union[pl.DataFrame, pl.LazyFrame]:
|
|
12
12
|
"""Resolve duplicate rank-choice application rows.
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
(``base_keys``), collapses rows that look like the same underlying
|
|
17
|
-
application, and leaves distinct applications separate.
|
|
18
|
-
|
|
19
|
-
Rows are collapsed when they have the exact same school set, or when one
|
|
20
|
-
row's school set is a strict subset of another row's set and the outcomes
|
|
21
|
-
for overlapping schools agree. Collapsed rows keep one representative row
|
|
22
|
-
for record-specific fields and take maxes across ``ever_cols`` among valid
|
|
23
|
-
rows. Rows left separate are flagged as current duplicates if more than
|
|
24
|
-
one application remains for the same ``base_keys``.
|
|
25
|
-
|
|
26
|
-
If ``df`` is a LazyFrame, this helper collects it to perform pairwise
|
|
27
|
-
set/subset comparisons, then returns a LazyFrame built from the result.
|
|
14
|
+
If df is a LazyFrame, this helper collects it to do pairwise set/subset
|
|
15
|
+
comparisons, then returns a LazyFrame built from the result.
|
|
28
16
|
"""
|
|
29
17
|
if not isinstance(df, (pl.DataFrame, pl.LazyFrame)):
|
|
30
18
|
raise TypeError("df must be a polars DataFrame or LazyFrame.")
|
|
@@ -41,10 +29,6 @@ def resolve_rank_choice_duplicate_applications(
|
|
|
41
29
|
|
|
42
30
|
requested = base_keys + school_choice_cols + ever_cols + info_cols
|
|
43
31
|
requested.extend(outcome_school_cols.values())
|
|
44
|
-
if late_col in columns:
|
|
45
|
-
requested.append(late_col)
|
|
46
|
-
if cancel_col in columns:
|
|
47
|
-
requested.append(cancel_col)
|
|
48
32
|
|
|
49
33
|
missing = [column for column in requested if column not in columns]
|
|
50
34
|
if missing:
|
|
@@ -87,12 +71,8 @@ def resolve_rank_choice_duplicate_applications(
|
|
|
87
71
|
|
|
88
72
|
def _outcome_signature(row: Mapping[str, Any], schools: Iterable[str]) -> Tuple[Any, ...]:
|
|
89
73
|
overlap = sorted(set(schools))
|
|
90
|
-
mapped_outcomes = [
|
|
91
|
-
column for column in ever_cols if column in outcome_school_cols
|
|
92
|
-
]
|
|
74
|
+
mapped_outcomes = [column for column in ever_cols if column in outcome_school_cols]
|
|
93
75
|
|
|
94
|
-
# Best case: an outcome flag has a paired school column, e.g.
|
|
95
|
-
# offer + offer_school. Then compare outcomes only for overlap schools.
|
|
96
76
|
if mapped_outcomes:
|
|
97
77
|
values = []
|
|
98
78
|
for school in overlap:
|
|
@@ -108,9 +88,6 @@ def resolve_rank_choice_duplicate_applications(
|
|
|
108
88
|
)
|
|
109
89
|
return tuple(values)
|
|
110
90
|
|
|
111
|
-
# Fallback: without school-specific outcome columns, the helper can only
|
|
112
|
-
# compare row-level outcome flags. This is enough for exact school-set
|
|
113
|
-
# duplicates, but it is a weaker assumption for subset decisions.
|
|
114
91
|
return tuple((column, int(_is_one(row[column]))) for column in ever_cols)
|
|
115
92
|
|
|
116
93
|
def _same_overlap_outcomes(
|
|
@@ -126,18 +103,32 @@ def resolve_rank_choice_duplicate_applications(
|
|
|
126
103
|
def _component_name(base_values: Tuple[Any, ...], school_set: Tuple[str, ...]) -> str:
|
|
127
104
|
return "%r|schools=%r" % (base_values, school_set)
|
|
128
105
|
|
|
106
|
+
def _find(parent: Dict[int, int], row_id: int) -> int:
|
|
107
|
+
while parent[row_id] != row_id:
|
|
108
|
+
parent[row_id] = parent[parent[row_id]]
|
|
109
|
+
row_id = parent[row_id]
|
|
110
|
+
return row_id
|
|
111
|
+
|
|
112
|
+
def _union(parent: Dict[int, int], left_id: int, right_id: int) -> None:
|
|
113
|
+
left_root = _find(parent, left_id)
|
|
114
|
+
right_root = _find(parent, right_id)
|
|
115
|
+
if left_root == right_root:
|
|
116
|
+
return
|
|
117
|
+
parent[max(left_root, right_root)] = min(left_root, right_root)
|
|
118
|
+
|
|
129
119
|
work = frame.with_row_index("_rc_row_id")
|
|
130
120
|
rows = work.to_dicts()
|
|
131
121
|
|
|
132
122
|
metadata = {}
|
|
123
|
+
parent = {}
|
|
133
124
|
for row in rows:
|
|
134
125
|
ordered_schools, school_set = _school_values(row)
|
|
135
126
|
row_id = row["_rc_row_id"]
|
|
127
|
+
parent[row_id] = row_id
|
|
136
128
|
metadata[row_id] = {
|
|
137
129
|
"base": tuple(row[column] for column in base_keys),
|
|
138
130
|
"ordered": ordered_schools,
|
|
139
131
|
"set": school_set,
|
|
140
|
-
"component": "%r|row=%s" % (tuple(row[column] for column in base_keys), row_id),
|
|
141
132
|
"case": 0,
|
|
142
133
|
"school_set_readable": " | ".join(school_set) or None,
|
|
143
134
|
"school_ordered_readable": " | ".join(ordered_schools) or None,
|
|
@@ -152,32 +143,23 @@ def resolve_rank_choice_duplicate_applications(
|
|
|
152
143
|
for row in group_rows:
|
|
153
144
|
rows_by_set.setdefault(metadata[row["_rc_row_id"]]["set"], []).append(row)
|
|
154
145
|
|
|
155
|
-
# Cases 1-2: exact same application school set. These collapse even
|
|
156
|
-
# when row-level outcomes differ, because max(ever_cols) keeps evidence.
|
|
157
146
|
for school_set, set_rows in rows_by_set.items():
|
|
158
147
|
if len(set_rows) <= 1:
|
|
159
148
|
continue
|
|
160
|
-
signatures = {
|
|
161
|
-
_outcome_signature(row, school_set)
|
|
162
|
-
for row in set_rows
|
|
163
|
-
}
|
|
149
|
+
signatures = {_outcome_signature(row, school_set) for row in set_rows}
|
|
164
150
|
case = 1 if len(signatures) == 1 else 2
|
|
165
|
-
|
|
151
|
+
anchor_id = set_rows[0]["_rc_row_id"]
|
|
166
152
|
for row in set_rows:
|
|
167
153
|
row_id = row["_rc_row_id"]
|
|
168
|
-
|
|
154
|
+
_union(parent, anchor_id, row_id)
|
|
169
155
|
metadata[row_id]["case"] = case
|
|
170
156
|
|
|
171
|
-
# Case 3: a strict subset row can collapse into a richer superset row
|
|
172
|
-
# when outcomes agree for the overlapping schools. If more than one
|
|
173
|
-
# superset qualifies, choose the largest/richest one deterministically.
|
|
174
157
|
for row in group_rows:
|
|
175
158
|
row_id = row["_rc_row_id"]
|
|
176
159
|
school_set = set(metadata[row_id]["set"])
|
|
177
160
|
if not school_set:
|
|
178
161
|
continue
|
|
179
162
|
|
|
180
|
-
candidates = []
|
|
181
163
|
for other in group_rows:
|
|
182
164
|
other_id = other["_rc_row_id"]
|
|
183
165
|
if row_id == other_id:
|
|
@@ -187,36 +169,49 @@ def resolve_rank_choice_duplicate_applications(
|
|
|
187
169
|
continue
|
|
188
170
|
if not _same_overlap_outcomes(row, other, school_set):
|
|
189
171
|
continue
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
172
|
+
_union(parent, row_id, other_id)
|
|
173
|
+
if metadata[row_id]["case"] == 0:
|
|
174
|
+
metadata[row_id]["case"] = 3
|
|
175
|
+
if metadata[other_id]["case"] == 0:
|
|
176
|
+
metadata[other_id]["case"] = 3
|
|
194
177
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
178
|
+
component_rows = {}
|
|
179
|
+
for row in rows:
|
|
180
|
+
row_id = row["_rc_row_id"]
|
|
181
|
+
component_rows.setdefault(_find(parent, row_id), []).append(row_id)
|
|
182
|
+
|
|
183
|
+
component_labels = {}
|
|
184
|
+
for component_row_ids in component_rows.values():
|
|
185
|
+
representative_id = max(
|
|
186
|
+
component_row_ids,
|
|
187
|
+
key=lambda row_id: (
|
|
188
|
+
len(metadata[row_id]["set"]),
|
|
189
|
+
_info_count(rows[row_id]),
|
|
190
|
+
-row_id,
|
|
191
|
+
),
|
|
192
|
+
)
|
|
193
|
+
component_school_set = metadata[representative_id]["set"]
|
|
194
|
+
component_base = metadata[representative_id]["base"]
|
|
195
|
+
if len(component_row_ids) == 1:
|
|
196
|
+
component = "%r|row=%s" % (component_base, representative_id)
|
|
197
|
+
else:
|
|
198
|
+
component = _component_name(component_base, component_school_set)
|
|
199
|
+
|
|
200
|
+
component_case = max(metadata[row_id]["case"] for row_id in component_row_ids)
|
|
201
|
+
for row_id in component_row_ids:
|
|
202
|
+
component_labels[row_id] = component
|
|
203
|
+
metadata[row_id]["case"] = component_case
|
|
210
204
|
|
|
211
205
|
assignment_rows = []
|
|
212
206
|
for row_id, values in metadata.items():
|
|
213
207
|
assignment_rows.append(
|
|
214
208
|
{
|
|
215
209
|
"_rc_row_id": row_id,
|
|
216
|
-
"_rc_component":
|
|
210
|
+
"_rc_component": component_labels[row_id],
|
|
217
211
|
"rank_choice_duplicate_case": values["case"],
|
|
218
212
|
"rank_choice_school_set": values["school_set_readable"],
|
|
219
213
|
"rank_choice_school_ordered": values["school_ordered_readable"],
|
|
214
|
+
"_rc_school_count": len(values["set"]),
|
|
220
215
|
}
|
|
221
216
|
)
|
|
222
217
|
|
|
@@ -234,12 +229,11 @@ def resolve_rank_choice_duplicate_applications(
|
|
|
234
229
|
]
|
|
235
230
|
)
|
|
236
231
|
)
|
|
232
|
+
|
|
237
233
|
work = (
|
|
238
234
|
work
|
|
239
235
|
.join(component_sizes, on="_rc_component", how="left")
|
|
240
|
-
.with_columns(
|
|
241
|
-
pl.col("_rc_component_case").alias("rank_choice_duplicate_case")
|
|
242
|
-
)
|
|
236
|
+
.with_columns(pl.col("_rc_component_case").alias("rank_choice_duplicate_case"))
|
|
243
237
|
)
|
|
244
238
|
|
|
245
239
|
outcome_rank = pl.lit(len(ever_cols))
|
|
@@ -255,6 +249,7 @@ def resolve_rank_choice_duplicate_applications(
|
|
|
255
249
|
if late_col in columns
|
|
256
250
|
else pl.lit(0)
|
|
257
251
|
)
|
|
252
|
+
|
|
258
253
|
cancel_rank = (
|
|
259
254
|
pl.when(pl.col(cancel_col) == 1)
|
|
260
255
|
.then(2)
|
|
@@ -264,6 +259,7 @@ def resolve_rank_choice_duplicate_applications(
|
|
|
264
259
|
if cancel_col in columns
|
|
265
260
|
else pl.lit(0)
|
|
266
261
|
)
|
|
262
|
+
|
|
267
263
|
info_count = (
|
|
268
264
|
pl.sum_horizontal([pl.col(c).is_not_null().cast(pl.Int8) for c in info_cols])
|
|
269
265
|
if info_cols
|
|
@@ -271,14 +267,17 @@ def resolve_rank_choice_duplicate_applications(
|
|
|
271
267
|
)
|
|
272
268
|
|
|
273
269
|
existing_date_tiebreakers = [column for column in date_tiebreakers if column in columns]
|
|
270
|
+
|
|
274
271
|
sort_cols = [
|
|
275
272
|
"_rc_component",
|
|
276
273
|
"_late_rank",
|
|
277
274
|
"_cancel_rank",
|
|
278
275
|
"_outcome_rank",
|
|
276
|
+
"_rc_school_count",
|
|
279
277
|
"_info_count",
|
|
280
278
|
] + existing_date_tiebreakers
|
|
281
|
-
|
|
279
|
+
|
|
280
|
+
descending = [False, False, False, False, True, True] + [
|
|
282
281
|
False for _ in existing_date_tiebreakers
|
|
283
282
|
]
|
|
284
283
|
|
|
@@ -290,6 +289,7 @@ def resolve_rank_choice_duplicate_applications(
|
|
|
290
289
|
)
|
|
291
290
|
|
|
292
291
|
untouched = ranked.filter(pl.col("_rc_component_n") == 1)
|
|
292
|
+
|
|
293
293
|
collapsed = (
|
|
294
294
|
ranked
|
|
295
295
|
.filter(pl.col("_rc_component_n") > 1)
|
|
@@ -311,16 +311,6 @@ def resolve_rank_choice_duplicate_applications(
|
|
|
311
311
|
.agg([pl.col(c).max().alias("_%s_collapsed" % c) for c in ever_cols])
|
|
312
312
|
)
|
|
313
313
|
|
|
314
|
-
temp_cols = [
|
|
315
|
-
"_rc_row_id",
|
|
316
|
-
"_rc_component",
|
|
317
|
-
"_rc_component_n",
|
|
318
|
-
"_rc_component_case",
|
|
319
|
-
"_late_rank",
|
|
320
|
-
"_cancel_rank",
|
|
321
|
-
"_outcome_rank",
|
|
322
|
-
"_info_count",
|
|
323
|
-
]
|
|
324
314
|
collapsed_flag_cols = ["_%s_collapsed" % column for column in ever_cols]
|
|
325
315
|
|
|
326
316
|
collapsed_resolved = (
|
|
@@ -328,9 +318,7 @@ def resolve_rank_choice_duplicate_applications(
|
|
|
328
318
|
.join(collapsed_flags, on="_rc_component", how="left")
|
|
329
319
|
.with_columns(
|
|
330
320
|
[
|
|
331
|
-
pl.coalesce(pl.col("_%s_collapsed" % column), pl.col(column)).alias(
|
|
332
|
-
column
|
|
333
|
-
)
|
|
321
|
+
pl.coalesce(pl.col("_%s_collapsed" % column), pl.col(column)).alias(column)
|
|
334
322
|
for column in ever_cols
|
|
335
323
|
]
|
|
336
324
|
+ [pl.lit(1, dtype=pl.Int8).alias("former_dupe")]
|
|
@@ -339,6 +327,7 @@ def resolve_rank_choice_duplicate_applications(
|
|
|
339
327
|
)
|
|
340
328
|
|
|
341
329
|
untouched = untouched.with_columns(pl.lit(0, dtype=pl.Int8).alias("former_dupe"))
|
|
330
|
+
|
|
342
331
|
output_columns = [
|
|
343
332
|
column for column in columns
|
|
344
333
|
if column not in {"former_dupe", "current_dupe"}
|
|
@@ -362,6 +351,7 @@ def resolve_rank_choice_duplicate_applications(
|
|
|
362
351
|
.group_by(base_keys)
|
|
363
352
|
.agg((pl.len() > 1).cast(pl.Int8).alias("current_dupe"))
|
|
364
353
|
)
|
|
354
|
+
|
|
365
355
|
result = result.join(current_dupe_flags, on=base_keys, how="left")
|
|
366
356
|
|
|
367
357
|
final_columns = output_columns + ["current_dupe"]
|
|
@@ -371,6 +361,7 @@ def resolve_rank_choice_duplicate_applications(
|
|
|
371
361
|
(pl.col("rank_choice_duplicate_case").is_in([1, 2, 3]))
|
|
372
362
|
& (pl.col("former_dupe") != 1)
|
|
373
363
|
).height
|
|
364
|
+
|
|
374
365
|
assert unresolved_collapsible == 0, (
|
|
375
366
|
"Found %s rows marked as collapsible cases but not collapsed."
|
|
376
367
|
% unresolved_collapsible
|
|
@@ -380,6 +371,7 @@ def resolve_rank_choice_duplicate_applications(
|
|
|
380
371
|
(pl.len().over(base_keys) > 1)
|
|
381
372
|
& (pl.col("current_dupe") != 1)
|
|
382
373
|
).height
|
|
374
|
+
|
|
383
375
|
assert current_dupe_errors == 0, (
|
|
384
376
|
"Found %s rows remaining duplicated at base_keys without current_dupe=1."
|
|
385
377
|
% current_dupe_errors
|
|
@@ -388,6 +380,8 @@ def resolve_rank_choice_duplicate_applications(
|
|
|
388
380
|
return result.lazy() if is_lazy else result
|
|
389
381
|
|
|
390
382
|
|
|
383
|
+
|
|
384
|
+
|
|
391
385
|
# Example call without school specific outcomes
|
|
392
386
|
final = df.pipe(
|
|
393
387
|
resolve_rank_choice_duplicate_applications,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|