ltc-code 0.1.99__tar.gz → 0.2.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.
Files changed (45) hide show
  1. {ltc_code-0.1.99 → ltc_code-0.2.0}/PKG-INFO +1 -1
  2. {ltc_code-0.1.99 → ltc_code-0.2.0}/pyproject.toml +1 -1
  3. ltc_code-0.2.0/src/ltc_code/20260630_census_disclosure/aspire.py +337 -0
  4. ltc_code-0.2.0/src/ltc_code/20260630_census_disclosure/christel_house.py +598 -0
  5. ltc_code-0.2.0/src/ltc_code/20260630_census_disclosure/democracy_prep.py +947 -0
  6. ltc_code-0.2.0/src/ltc_code/20260630_census_disclosure/green_dot.py +417 -0
  7. ltc_code-0.2.0/src/ltc_code/20260630_census_disclosure/ilt.py +557 -0
  8. ltc_code-0.2.0/src/ltc_code/20260630_census_disclosure/kipp.py +381 -0
  9. ltc_code-0.2.0/src/ltc_code/20260630_census_disclosure/kipp_nj.py +1383 -0
  10. ltc_code-0.2.0/src/ltc_code/20260630_census_disclosure/rocketship.py +962 -0
  11. ltc_code-0.2.0/src/ltc_code/20260630_census_disclosure/yes_prep.py +436 -0
  12. ltc_code-0.2.0/src/ltc_code/june30.py +216 -0
  13. {ltc_code-0.1.99 → ltc_code-0.2.0}/README.md +0 -0
  14. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/.DS_Store +0 -0
  15. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/20260614_new_build_scripts_update/aspire.py +0 -0
  16. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/20260614_new_build_scripts_update/check_cmo_apps.do +0 -0
  17. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/20260614_new_build_scripts_update/christel_house.py +0 -0
  18. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/20260614_new_build_scripts_update/democracy_prep.py +0 -0
  19. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/20260614_new_build_scripts_update/green_dot.py +0 -0
  20. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/20260614_new_build_scripts_update/helpers.py +0 -0
  21. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/20260614_new_build_scripts_update/ilt.py +0 -0
  22. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/20260614_new_build_scripts_update/kipp_nj.py +0 -0
  23. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/20260614_new_build_scripts_update/main.py +0 -0
  24. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/20260614_new_build_scripts_update/mappings.py +0 -0
  25. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/20260614_new_build_scripts_update/rocketship.py +0 -0
  26. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/20260614_new_build_scripts_update/yes_prep.py +0 -0
  27. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/__init__.py +0 -0
  28. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/aspire.py +0 -0
  29. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/check_cmo_apps.do +0 -0
  30. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/christel_house.py +0 -0
  31. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/green_dot.py +0 -0
  32. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/helpers.py +0 -0
  33. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/june13.py +0 -0
  34. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/june2.py +0 -0
  35. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/june5.py +0 -0
  36. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/june7.py +0 -0
  37. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/kipp_nj.py +0 -0
  38. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/kipp_tx.py +0 -0
  39. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/main.py +0 -0
  40. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/mappings.py +0 -0
  41. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/may27.py +0 -0
  42. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/polars_dates.py +0 -0
  43. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/rocketship.py +0 -0
  44. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/schema_mapping.py +0 -0
  45. {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/yes_prep.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ltc-code
3
- Version: 0.1.99
3
+ Version: 0.2.0
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.9
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ltc-code"
3
- version = "0.1.99"
3
+ version = "0.2.0"
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"
@@ -0,0 +1,337 @@
1
+ # --- Import Necessary Libraries ---
2
+ import polars as pl
3
+ import pandas as pd
4
+ import sys
5
+ sys.path.append("/projects/from_research1_arts867_opportunity/ltc/code")
6
+ from paths import *
7
+ from helpers import *
8
+ from mappings import *
9
+ from polars_readstat import scan_readstat
10
+ import glob
11
+ import unicodedata
12
+ import re
13
+ from typing import Union, List, Dict, Optional, Literal, Sequence
14
+ import time
15
+ import subprocess
16
+ import logging
17
+ import shutil
18
+
19
+
20
+ # --- Create log file and start timer ---
21
+ logging.basicConfig(
22
+ filename=APPS_LOGS / "aspire_apps.log",
23
+ filemode="w", # "w"=overwrite existing log
24
+ level=logging.INFO,
25
+ format="%(asctime)s - %(message)s",
26
+ force=True, #reconfigure logging even if already set up
27
+ )
28
+
29
+ # Start timer
30
+ start = time.perf_counter()
31
+
32
+
33
+ ###########################################################################################################################################################################
34
+ # --- Define necessary paths ---
35
+ ###########################################################################################################################################################################
36
+
37
+ # --- Input + Output ---
38
+ INPUT = CMO_IDS / "harvard_ltc_2006_2021_asp_rsch.sas7bdat"
39
+ OUTPUT_LONG = CMO_APPS / "long" / "aspire.dta"
40
+ OUTPUT_WIDE = CMO_APPS / "wide" / "aspire.dta"
41
+ OUTPUT_SCHOOLS = SCHOOLS / "aspire_schools.csv"
42
+
43
+
44
+
45
+ ###########################################################################################################################################################################
46
+ # --- Scan in the cmos data, join on piks, filter to those who are piked, and drop sid_cepr ---
47
+ ###########################################################################################################################################################################
48
+
49
+ # Store full data
50
+ full = (
51
+ scan_readstat(INPUT)
52
+ .join(
53
+ pl.scan_csv(XWALKS / "sid_cepr_pik_xwalk.csv", infer_schema=False),
54
+ how="left",
55
+ on=["sid_cepr", "cmo_name"],
56
+ validate="m:1",
57
+ )
58
+ .filter(
59
+ (pl.col("pik").is_not_null())
60
+ & (pl.col("pik") != "")
61
+ )
62
+ .drop("DQB_SOURCE_ID", "sid_cepr")
63
+ )
64
+
65
+
66
+ ###########################################################################################################################################################################
67
+ # --- Scan in lottery dataset, code up offers/risksets and deduplicate the applications based on rules ---
68
+ ###########################################################################################################################################################################
69
+
70
+ # Define keys for dedupping function
71
+ DEDUP_KEYS = ["pik", "school_year", "entry_grade_clean", "school_name",]
72
+
73
+ # Take the max of these for dupes (ORDER MATTERS HERE MAKE SURE MOST IMPORTANT IS LISTED AT TOP)
74
+ EVER_COLS = ["offer_accepted", "offer", "waitlist", "withdraw",]
75
+
76
+ # Prioritize records with more filled out info for these cols
77
+ INFO_COLS = ["lottery_number", "waitlist_number", "priority_group_name", "status_timestamp",]
78
+
79
+
80
+ # Scan in the lottery data and clean stuff
81
+ lot = (
82
+ full
83
+ .filter(pl.col("data_type")=="lottery") # filter to lotto
84
+ .pipe(drop_all_null)
85
+ .with_columns(
86
+ pl.lit("CALIFORNIA").alias("cmo_region"),
87
+ pl.lit(None, dtype=pl.Int64).alias("late_app"),
88
+ )
89
+ .with_columns(
90
+ apply_status_mapping("status", ASPIRE_STATUS_MAP), # map the statuses to relevant offer/waitlist variables
91
+ )
92
+ .with_columns( # In this context, missing lottery waitlist numbers are offers
93
+ pl.when(pl.col("waitlist_number") == "0")
94
+ .then(1)
95
+ .otherwise(pl.col("offer"))
96
+ .alias("offer")
97
+ )
98
+ .rename({
99
+ "priority_name": "priority_group_name",
100
+ })
101
+ .pipe(resolve_duplicate_applications, DEDUP_KEYS, EVER_COLS, INFO_COLS)
102
+ .pipe( # create priority specific flags based on priority group name
103
+ add_priority_flags,
104
+ priority_col="priority_group_name",
105
+ cmo="aspire",
106
+ )
107
+ .with_columns( # create number representing the priority group (is not ordinally ranked b/c i dont see reason to do so)
108
+ pl.col("priority_group_clean")
109
+ .rank(method="dense")
110
+ .cast(pl.Int32)
111
+ .alias("priority_group"),
112
+ )
113
+ ).collect()
114
+
115
+
116
+ ###########################################################################################################################################################################
117
+ # --- Scan in waitlist, enrollment, and name datasets then join on waitlist/enrollment/name indicators, and years of enrollment ---
118
+ ###########################################################################################################################################################################
119
+
120
+ # Create risk keys
121
+ risk_keys = ["school_year", "entry_grade_clean", "school_name", "priority_group"]
122
+
123
+ # Scan in the waitlist data and collapse to get wl flag
124
+ wl = (
125
+ full
126
+ .filter(pl.col("data_type")=="waitlist") # filter to waitlist
127
+ .select(DEDUP_KEYS)
128
+ .unique()
129
+ .with_columns(
130
+ pl.lit(1).alias("in_wl"),
131
+ )
132
+ ).collect()
133
+
134
+ # Scan in the enrollment data and collapse to get enroll flags
135
+ enr_student = (
136
+ full
137
+ .filter(pl.col("data_type")=="enroll") # filter to waitlist
138
+ .select(["pik", "school_year"])
139
+ .unique()
140
+ .group_by("pik")
141
+ .agg(
142
+ pl.lit(1).alias("enroll"), # flag for if the student ever enrolled in the cmo
143
+ pl.len().alias("enrollment_years"), # number of years the student was enrolled in the cmo
144
+ pl.col("school_year").cast(pl.Int32, strict=False).min().alias("year_enrolled"),
145
+ )
146
+ ).collect()
147
+
148
+
149
+ # Scan in the enrollment data and collapse to get flag for if enrolled at a given school
150
+ enr_school = (
151
+ full
152
+ .filter(pl.col("data_type")=="enroll") # filter to waitlist
153
+ .select(["pik", "school_name"])
154
+ .unique()
155
+ .with_columns(
156
+ pl.lit(1).alias("enroll_school"), # flag for if the student ever enrolled at the school to which they applied
157
+ )
158
+ ).collect()
159
+
160
+ # Scan in the enrollment data and collapse to get flag for if enrolled in a given year
161
+ enr_year = (
162
+ full
163
+ .filter(pl.col("data_type")=="enroll") # filter to waitlist
164
+ .select(["pik", "school_year"])
165
+ .unique()
166
+ .with_columns(
167
+ pl.lit(1).alias("enroll_year"), # flag for if the student enrolled at any schools from the lottery year they applied
168
+ )
169
+ ).collect()
170
+
171
+ # Scan in the enrollment data and collapse to get flag for if enrolled in a given year
172
+ enr_school_year = (
173
+ full
174
+ .filter(pl.col("data_type")=="enroll") # filter to waitlist
175
+ .select(["pik", "school_year", "school_name"])
176
+ .unique()
177
+ .with_columns(
178
+ pl.lit(1).alias("enroll_school_year"), # flag for if the student enrolled in the school to which they applied in the year of the lottery
179
+ )
180
+ ).collect()
181
+
182
+
183
+ # Join on all the useful info from wl/enr, create risk sets/lottery variables, and then keep just the variables in the desired schema
184
+ final = (
185
+ lot
186
+ .join(wl, on=DEDUP_KEYS, how="left", validate="m:1")
187
+ .join(enr_student, on="pik", how="left", validate="m:1")
188
+ .join(enr_school, on=["pik", "school_name"], how="left", validate="m:1")
189
+ .join(enr_year, on=["pik", "school_year"], how="left", validate="m:1")
190
+ .join(enr_school_year, on=["pik", "school_year", "school_name"], how="left", validate="m:1")
191
+ .with_columns(
192
+ pl.coalesce(
193
+ pl.col("in_wl"),
194
+ pl.col("waitlist"),
195
+ ).alias("waitlist"),
196
+
197
+ pl.col("enroll").fill_null(0).alias("enroll"),
198
+ pl.col("enrollment_years").fill_null(0).alias("enrollment_years"),
199
+ pl.col("enroll_school").fill_null(0).alias("enroll_school"),
200
+ pl.col("enroll_year").fill_null(0).alias("enroll_year"),
201
+ pl.col("enroll_school_year").fill_null(0).alias("enroll_school_year"),
202
+ )
203
+ .with_columns(
204
+ waitlist_offer=(
205
+ (pl.col("offer") == 1)
206
+ & (pl.col("waitlist") == 1)
207
+ ).cast(pl.Int8),
208
+
209
+ initial_offer=(
210
+ (pl.col("offer") == 1)
211
+ & (pl.col("waitlist") != 1)
212
+ ).cast(pl.Int8),
213
+ )
214
+ .pipe(keep_final_apps_schema)
215
+ .with_columns(
216
+ pl.concat_str(
217
+ [pl.col(c).cast(pl.String) for c in risk_keys],
218
+ separator="_",
219
+ ).alias("risk_id_name")
220
+ )
221
+ .with_columns( # create number representing the risk id
222
+ pl.col("risk_id_name")
223
+ .rank(method="dense")
224
+ .cast(pl.Int32)
225
+ .alias("risk_id"),
226
+ )
227
+ .pipe(drop_all_null)
228
+ )
229
+
230
+
231
+
232
+ ###########################################################################################################################################################################
233
+ # --- Create collapsed version of the stata dataset ---
234
+ ###########################################################################################################################################################################
235
+
236
+
237
+ # Define cols we want to take the max of and the grouping key
238
+ max_cols = [
239
+ "offer",
240
+ "initial_offer",
241
+ "waitlist_offer",
242
+ "offer_accepted",
243
+ "enroll",
244
+ "sibling",
245
+ "transfer",
246
+ "zoned",
247
+ "staff",
248
+ "sibling_concur",
249
+ ]
250
+
251
+ group_key = ["pik"]
252
+
253
+ first_cols = [
254
+ c for c in final.columns
255
+ if c not in group_key + max_cols + ["risk_id"]
256
+ ]
257
+
258
+ # Create the collapsed dataset
259
+ final_collapsed = (
260
+ final
261
+ .filter(pl.col("application_cancel") !=1)
262
+ .group_by(group_key)
263
+ .agg(
264
+ # max variables
265
+ [pl.max(c).alias(c) for c in max_cols]
266
+
267
+ +
268
+
269
+ # all risk sets student participcated in
270
+ [
271
+ pl.col("risk_id")
272
+ .unique()
273
+ .sort()
274
+ .str.join("_")
275
+ .alias("risk_set")
276
+ ]
277
+
278
+ +
279
+
280
+ # everything else from first row
281
+ [
282
+ pl.first(c).alias(c)
283
+ for c in first_cols
284
+ ]
285
+ )
286
+ )
287
+
288
+
289
+ ###########################################################################################################################################################################
290
+ # --- Create unique schools dataset ---
291
+ ###########################################################################################################################################################################
292
+
293
+
294
+ # Scan in the combined data and collaprse to unique schools
295
+ sch_ren_map = (
296
+ full
297
+ .select(["cmo_name", "school_name", "ren_num"])
298
+ .with_columns(
299
+ pl.lit("CALIFORNIA").alias("cmo_region"),
300
+ )
301
+ .drop_nulls(["school_name"])
302
+ .group_by(["cmo_name", "cmo_region", "school_name"])
303
+ .agg(
304
+ pl.len().alias("n_obs"),
305
+
306
+ pl.col("ren_num")
307
+ .drop_nulls()
308
+ .unique()
309
+ .sort()
310
+ .cast(pl.String)
311
+ .str.join("+")
312
+ .alias("ren_nums")
313
+ )
314
+ .select(["cmo_name", "cmo_region", "school_name", "n_obs", "ren_nums"])
315
+ .sort("n_obs", descending=True)
316
+ ).collect()
317
+
318
+
319
+ ###########################################################################################################################################################################
320
+ # --- Save data ---
321
+ ###########################################################################################################################################################################
322
+
323
+ # Save unique schools dataset
324
+ sch_ren_map.write_csv(OUTPUT_SCHOOLS)
325
+
326
+ # Save student apps dataset to stata
327
+ final.to_pandas().to_stata(OUTPUT_LONG, write_index=False, version=118)
328
+
329
+ # Save collapsed student dataset to stata
330
+ final_collapsed.to_pandas().to_stata(OUTPUT_WIDE, write_index=False, version=118)
331
+
332
+ # End timer
333
+ elapsed = time.perf_counter() - start
334
+ print(f"Finished in {elapsed / 60:.2f} minutes")
335
+ logging.info(f"Finished in {elapsed / 60:.2f} minutes")
336
+
337
+