ltc-code 0.1.87__tar.gz → 0.1.88__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.87 → ltc_code-0.1.88}/PKG-INFO +1 -1
- {ltc_code-0.1.87 → ltc_code-0.1.88}/pyproject.toml +1 -1
- ltc_code-0.1.88/src/ltc_code/june5.py +187 -0
- {ltc_code-0.1.87 → ltc_code-0.1.88}/README.md +0 -0
- {ltc_code-0.1.87 → ltc_code-0.1.88}/src/ltc_code/__init__.py +0 -0
- {ltc_code-0.1.87 → ltc_code-0.1.88}/src/ltc_code/green_dot.py +0 -0
- {ltc_code-0.1.87 → ltc_code-0.1.88}/src/ltc_code/june2.py +0 -0
- {ltc_code-0.1.87 → ltc_code-0.1.88}/src/ltc_code/kipp_nj.py +0 -0
- {ltc_code-0.1.87 → ltc_code-0.1.88}/src/ltc_code/kipp_tx.py +0 -0
- {ltc_code-0.1.87 → ltc_code-0.1.88}/src/ltc_code/may27.py +0 -0
- {ltc_code-0.1.87 → ltc_code-0.1.88}/src/ltc_code/polars_dates.py +0 -0
- {ltc_code-0.1.87 → ltc_code-0.1.88}/src/ltc_code/schema_mapping.py +0 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
DEDUP_KEYS = [
|
|
2
|
+
"sid_cepr",
|
|
3
|
+
"school_year",
|
|
4
|
+
"entry_grade_clean",
|
|
5
|
+
"school_name",
|
|
6
|
+
]
|
|
7
|
+
|
|
8
|
+
EVER_COLS = [
|
|
9
|
+
"offer",
|
|
10
|
+
"waitlist",
|
|
11
|
+
"withdraw",
|
|
12
|
+
"offer_accepted",
|
|
13
|
+
"enroll",
|
|
14
|
+
"sibling",
|
|
15
|
+
"staff",
|
|
16
|
+
"zoned",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
INFO_COLS = [
|
|
20
|
+
"lottery_number",
|
|
21
|
+
"waitlist_number",
|
|
22
|
+
"priority_group",
|
|
23
|
+
"priority_group_name",
|
|
24
|
+
"school_accept",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
from typing import List, Union
|
|
31
|
+
|
|
32
|
+
import polars as pl
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def resolve_duplicate_applications(
|
|
36
|
+
df: Union[pl.DataFrame, pl.LazyFrame],
|
|
37
|
+
dedup_keys: List[str],
|
|
38
|
+
ever_cols: List[str],
|
|
39
|
+
info_cols: List[str],
|
|
40
|
+
) -> Union[pl.DataFrame, pl.LazyFrame]:
|
|
41
|
+
columns = df.collect_schema().names() if isinstance(df, pl.LazyFrame) else df.columns
|
|
42
|
+
|
|
43
|
+
missing = [
|
|
44
|
+
c for c in dedup_keys + ever_cols + info_cols
|
|
45
|
+
if c not in columns
|
|
46
|
+
]
|
|
47
|
+
if missing:
|
|
48
|
+
raise ValueError("Missing specified columns: %s" % missing)
|
|
49
|
+
|
|
50
|
+
has_late_app = "late_app" in columns
|
|
51
|
+
has_application_cancel = "application_cancel" in columns
|
|
52
|
+
|
|
53
|
+
# These are optional tie-breakers only. They are not required and they do
|
|
54
|
+
# not affect the collapsed event flags.
|
|
55
|
+
date_tiebreakers = [
|
|
56
|
+
c for c in ["application_date", "offer_date"]
|
|
57
|
+
if c in columns
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
late_rank = (
|
|
61
|
+
pl.when(pl.col("late_app") == 1)
|
|
62
|
+
.then(2)
|
|
63
|
+
.when(pl.col("late_app").is_null())
|
|
64
|
+
.then(1)
|
|
65
|
+
.otherwise(0)
|
|
66
|
+
if has_late_app
|
|
67
|
+
else pl.lit(0)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
cancel_rank = (
|
|
71
|
+
pl.when(pl.col("application_cancel") == 1)
|
|
72
|
+
.then(2)
|
|
73
|
+
.when(pl.col("application_cancel").is_null())
|
|
74
|
+
.then(1)
|
|
75
|
+
.otherwise(0)
|
|
76
|
+
if has_application_cancel
|
|
77
|
+
else pl.lit(0)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Outcome ranking follows the order of ever_cols:
|
|
81
|
+
# earlier columns are treated as stronger representative-row evidence.
|
|
82
|
+
outcome_rank = pl.lit(len(ever_cols))
|
|
83
|
+
for i, col in reversed(list(enumerate(ever_cols))):
|
|
84
|
+
outcome_rank = (
|
|
85
|
+
pl.when(pl.col(col) == 1)
|
|
86
|
+
.then(i)
|
|
87
|
+
.otherwise(outcome_rank)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
sort_cols = (
|
|
91
|
+
dedup_keys
|
|
92
|
+
+ ["_late_rank", "_cancel_rank", "_outcome_rank", "_info_count"]
|
|
93
|
+
+ date_tiebreakers
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
descending = (
|
|
97
|
+
[False] * len(dedup_keys)
|
|
98
|
+
+ [
|
|
99
|
+
False, # prefer not late, if late_app exists
|
|
100
|
+
False, # prefer not cancelled, if application_cancel exists
|
|
101
|
+
False, # prefer earlier/stronger event columns from ever_cols
|
|
102
|
+
True, # prefer rows with more non-null info columns
|
|
103
|
+
]
|
|
104
|
+
+ [False] * len(date_tiebreakers)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
ranked = (
|
|
108
|
+
df
|
|
109
|
+
.with_columns(pl.len().over(dedup_keys).alias("_dup_n"))
|
|
110
|
+
.with_columns(
|
|
111
|
+
_late_rank=late_rank,
|
|
112
|
+
_cancel_rank=cancel_rank,
|
|
113
|
+
_outcome_rank=outcome_rank,
|
|
114
|
+
_info_count=pl.sum_horizontal(
|
|
115
|
+
[pl.col(c).is_not_null().cast(pl.Int8) for c in info_cols]
|
|
116
|
+
),
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
temp_cols = [
|
|
121
|
+
"_dup_n",
|
|
122
|
+
"_late_rank",
|
|
123
|
+
"_cancel_rank",
|
|
124
|
+
"_outcome_rank",
|
|
125
|
+
"_info_count",
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
non_dupes = (
|
|
129
|
+
ranked
|
|
130
|
+
.filter(pl.col("_dup_n") == 1)
|
|
131
|
+
.drop(temp_cols, strict=False)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
dupes = (
|
|
135
|
+
ranked
|
|
136
|
+
.filter(pl.col("_dup_n") > 1)
|
|
137
|
+
.sort(sort_cols, descending=descending, nulls_last=True)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
rep_rows = dupes.group_by(dedup_keys).first()
|
|
141
|
+
|
|
142
|
+
valid_filter = pl.lit(True)
|
|
143
|
+
|
|
144
|
+
if has_late_app:
|
|
145
|
+
valid_filter = valid_filter & (pl.col("late_app") != 1).fill_null(True)
|
|
146
|
+
|
|
147
|
+
if has_application_cancel:
|
|
148
|
+
valid_filter = (
|
|
149
|
+
valid_filter
|
|
150
|
+
& (pl.col("application_cancel") != 1).fill_null(True)
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
valid_dupes = dupes.filter(valid_filter)
|
|
154
|
+
|
|
155
|
+
collapsed_flags = (
|
|
156
|
+
valid_dupes
|
|
157
|
+
.group_by(dedup_keys)
|
|
158
|
+
.agg([pl.col(c).max().alias("_%s_collapsed" % c) for c in ever_cols])
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
dupes_resolved = (
|
|
162
|
+
rep_rows
|
|
163
|
+
.join(collapsed_flags, on=dedup_keys, how="left")
|
|
164
|
+
.with_columns(
|
|
165
|
+
[
|
|
166
|
+
pl.coalesce(pl.col("_%s_collapsed" % c), pl.col(c)).alias(c)
|
|
167
|
+
for c in ever_cols
|
|
168
|
+
]
|
|
169
|
+
)
|
|
170
|
+
.drop(
|
|
171
|
+
temp_cols + ["_%s_collapsed" % c for c in ever_cols],
|
|
172
|
+
strict=False,
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
final = pl.concat([non_dupes, dupes_resolved], how="vertical_relaxed")
|
|
177
|
+
|
|
178
|
+
if isinstance(final, pl.DataFrame):
|
|
179
|
+
duplicate_count = (
|
|
180
|
+
final.select(dedup_keys).height
|
|
181
|
+
- final.select(dedup_keys).unique().height
|
|
182
|
+
)
|
|
183
|
+
assert duplicate_count == 0, (
|
|
184
|
+
"Found %s unresolved duplicate rows." % duplicate_count
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
return final
|
|
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
|