ltc-code 0.2.1__tar.gz → 0.2.11__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 (55) hide show
  1. {ltc_code-0.2.1 → ltc_code-0.2.11}/PKG-INFO +1 -1
  2. {ltc_code-0.2.1 → ltc_code-0.2.11}/pyproject.toml +1 -1
  3. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/.DS_Store +0 -0
  4. ltc_code-0.2.11/src/ltc_code/20260712_uncommon_scripts/main.py +638 -0
  5. ltc_code-0.2.11/src/ltc_code/20260712_uncommon_scripts/mappings.py +1400 -0
  6. ltc_code-0.2.11/src/ltc_code/20260712_uncommon_scripts/uncommon.py +2029 -0
  7. {ltc_code-0.2.1 → ltc_code-0.2.11}/README.md +0 -0
  8. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260614_new_build_scripts_update/aspire.py +0 -0
  9. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260614_new_build_scripts_update/check_cmo_apps.do +0 -0
  10. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260614_new_build_scripts_update/christel_house.py +0 -0
  11. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260614_new_build_scripts_update/democracy_prep.py +0 -0
  12. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260614_new_build_scripts_update/green_dot.py +0 -0
  13. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260614_new_build_scripts_update/helpers.py +0 -0
  14. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260614_new_build_scripts_update/ilt.py +0 -0
  15. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260614_new_build_scripts_update/kipp_nj.py +0 -0
  16. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260614_new_build_scripts_update/main.py +0 -0
  17. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260614_new_build_scripts_update/mappings.py +0 -0
  18. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260614_new_build_scripts_update/rocketship.py +0 -0
  19. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260614_new_build_scripts_update/yes_prep.py +0 -0
  20. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260630_census_disclosure/aspire.py +0 -0
  21. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260630_census_disclosure/christel_house.py +0 -0
  22. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260630_census_disclosure/democracy_prep.py +0 -0
  23. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260630_census_disclosure/green_dot.py +0 -0
  24. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260630_census_disclosure/ilt.py +0 -0
  25. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260630_census_disclosure/kipp.py +0 -0
  26. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260630_census_disclosure/kipp_nj.py +0 -0
  27. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260630_census_disclosure/rocketship.py +0 -0
  28. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260630_census_disclosure/yes_prep.py +0 -0
  29. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260706_ceprscripts/BALANCE.do +0 -0
  30. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260706_ceprscripts/FS.do +0 -0
  31. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260706_ceprscripts/ITT.do +0 -0
  32. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260706_ceprscripts/TOT.do +0 -0
  33. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260706_ceprscripts/apps_helpers.py +0 -0
  34. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260706_ceprscripts/harmony.py +0 -0
  35. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/20260706_ceprscripts/main.py +0 -0
  36. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/__init__.py +0 -0
  37. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/aspire.py +0 -0
  38. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/check_cmo_apps.do +0 -0
  39. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/christel_house.py +0 -0
  40. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/green_dot.py +0 -0
  41. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/helpers.py +0 -0
  42. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/june13.py +0 -0
  43. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/june2.py +0 -0
  44. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/june30.py +0 -0
  45. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/june5.py +0 -0
  46. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/june7.py +0 -0
  47. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/kipp_nj.py +0 -0
  48. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/kipp_tx.py +0 -0
  49. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/main.py +0 -0
  50. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/mappings.py +0 -0
  51. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/may27.py +0 -0
  52. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/polars_dates.py +0 -0
  53. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/rocketship.py +0 -0
  54. {ltc_code-0.2.1 → ltc_code-0.2.11}/src/ltc_code/schema_mapping.py +0 -0
  55. {ltc_code-0.2.1 → ltc_code-0.2.11}/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.2.1
3
+ Version: 0.2.11
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.2.1"
3
+ version = "0.2.11"
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"
@@ -0,0 +1,638 @@
1
+ # --- Import Necessary Libraries ---
2
+ import polars as pl
3
+ import sys
4
+ sys.path.append(r"P:\Long_Term_Charter\programs\new_build")
5
+ from paths import *
6
+ from helpers import *
7
+ from mappings import *
8
+ from polars_readstat import scan_readstat
9
+ import glob
10
+ import unicodedata
11
+ import re
12
+ from typing import Union, List, Dict, Optional
13
+ import time
14
+ from datetime import datetime
15
+ import logging
16
+
17
+ # For parallel processing
18
+ import subprocess
19
+ from concurrent.futures import ThreadPoolExecutor, as_completed
20
+
21
+ # set row/col config
22
+ pl.Config.set_tbl_cols(13)
23
+ pl.Config.set_tbl_rows(15)
24
+
25
+ # --- Create log file and start timer ---
26
+ logging.basicConfig(
27
+ filename=LOGS / "main.log",
28
+ filemode="w", # "w"=overwrite existing log
29
+ level=logging.INFO,
30
+ format="%(asctime)s - %(message)s",
31
+ force=True, #reconfigure logging even if already set up
32
+ )
33
+
34
+
35
+
36
+ ###########################################################################################################################################################################
37
+ # --- Define necessary paths and scripts ---
38
+ ###########################################################################################################################################################################
39
+
40
+ BASE = Path("P:/Long_Term_Charter/programs/new_build")
41
+
42
+ # Paths to code files
43
+ CMO_ID_DIR = BASE / "cmo_ids"
44
+ CMO_APPS_DIR = BASE / "cmo_apps"
45
+
46
+ # Paths to input data
47
+ OLD_SAMPLE = OLD_BUILD / "old_cmo_apps.parquet"
48
+ KIPP_ENR = CMO_APPS / "kipp_enroll" / "kipp.dta"
49
+
50
+ # Paths to output data
51
+ OUTPUT_LONG_DTA = CMO_APPS / "stata" / "cmo_apps.dta"
52
+ OUTPUT_LONG_PARQUET = CMO_APPS / "stata" / "cmo_apps.parquet"
53
+ OUTPUT_WIDE_SID_DTA = CMO_APPS/ "stata_collapsed" / "cmo_apps_sid.dta"
54
+ OUTPUT_WIDE_SID_PARQUET = CMO_APPS / "stata_collapsed" / "cmo_apps_sid.parquet"
55
+ OUTPUT_NSC_DTA = CMO_ANALYSIS / "cmo_analysis.dta"
56
+ OUTPUT_NSC_PARQUET = CMO_ANALYSIS / "cmo_analysis.parquet"
57
+ OUTPUT_WIDE_NSC_DTA = CMO_ANALYSIS / "cmo_analysis_wide.dta"
58
+ OUTPUT_WIDE_NSC_PARQUET = CMO_ANALYSIS / "cmo_analysis_wide.parquet"
59
+ OUTPUT_SCHOOLS_CSV = CLEAN / "new_build" / "schools" / "all_schools.csv"
60
+
61
+
62
+
63
+ # Define which apps scripts we want to run
64
+ apps_scripts = [
65
+ #CMO_APPS_DIR / "aspire.py",
66
+ #CMO_APPS_DIR / "christel_house.py",
67
+ #CMO_APPS_DIR / "democracy_prep.py",
68
+ #CMO_APPS_DIR / "green_dot.py",
69
+ #CMO_APPS_DIR / "harmony.py",
70
+ #CMO_APPS_DIR / "ilt.py",
71
+ #CMO_APPS_DIR / "kipp.py",
72
+ #CMO_APPS_DIR / "kipp_nj.py",
73
+ #CMO_APPS_DIR / "kipp_tx.py",
74
+ #CMO_APPS_DIR / "rocketship.py",
75
+ CMO_APPS_DIR / "uncommon.py",
76
+ #CMO_APPS_DIR / "yes_prep.py",
77
+ ]
78
+
79
+ ###########################################################################################################################################################################
80
+ # --- Run scripts ---
81
+ ###########################################################################################################################################################################
82
+
83
+ # Runs cmo scripts
84
+ def run_cmo(script):
85
+ # Start timer
86
+ start = time.perf_counter()
87
+ logging.info(f"Starting {script.name}")
88
+ subprocess.run(
89
+ [sys.executable, str(script.relative_to(BASE))],
90
+ cwd=BASE,
91
+ check=True,
92
+ )
93
+ # log elapsed time
94
+ elapsed_min = (time.perf_counter() - start) / 60
95
+ logging.info(f"Finished {script.name}: took {elapsed_min:.2f} minutes")
96
+
97
+ # Appends all the cmos together
98
+ def append_cmos(*cmos):
99
+ # Scan in old build data
100
+ old_data = pl.scan_parquet(OLD_SAMPLE)
101
+ # Read in KIPP enrollment data
102
+ enr = (
103
+ scan_readstat(KIPP_ENR)
104
+ .select("enrollment_years", "enroll",
105
+ "sid_cepr_kipp_nj", "sid_cepr_kipp_ma",
106
+ "sid_cepr_kipp_nc", "sid_cepr_kipp_tx", )
107
+ .rename({
108
+ "enroll": "enroll_kipp",
109
+ })
110
+ )
111
+ dfs = []
112
+ for cmo in cmos:
113
+ """
114
+ if cmo == "harmony":
115
+ df = (
116
+ old_data
117
+ .filter(pl.col("cmo_name") == "HARMONY PUBLIC SCHOOLS")
118
+ .filter(pl.col("sid_cepr") != "")
119
+ .filter(pl.col("sample") == 1)
120
+ .filter(
121
+ (pl.col("application_date").is_not_null())
122
+ &
123
+ (pl.col("application_date") != "")
124
+ )
125
+ # Create better late applicant variable specifically for harmony
126
+ .with_columns(
127
+ pl.when(
128
+ (
129
+ pl.col("application_date")
130
+ .str.strptime(pl.Date, "%m/%d/%Y", strict=False)
131
+ .dt.year() == pl.col("school_year").cast(pl.Int64, strict=False)
132
+ )
133
+ &
134
+ (
135
+ (
136
+ pl.col("application_date")
137
+ .str.strptime(pl.Date, "%m/%d/%Y", strict=False)
138
+ .dt.month() > 2
139
+ )
140
+ |
141
+ (
142
+ (
143
+ pl.col("application_date")
144
+ .str.strptime(pl.Date, "%m/%d/%Y", strict=False)
145
+ .dt.month() == 2
146
+ )
147
+ &
148
+ (
149
+ pl.col("application_date")
150
+ .str.strptime(pl.Date, "%m/%d/%Y", strict=False)
151
+ .dt.day() >= 11
152
+ )
153
+ )
154
+ )
155
+ )
156
+ .then(1)
157
+ .otherwise(0)
158
+ .alias("late_applicant_clean")
159
+ )
160
+ .filter(pl.col("late_applicant_clean") == 0)
161
+ )
162
+
163
+ elif cmo == "uncommon":
164
+ df = (
165
+ old_data
166
+ .filter(pl.col("cmo_name") == "UNCOMMON SCHOOLS")
167
+ .filter(pl.col("sid_cepr") != "")
168
+ .filter(pl.col("sample") == 1)
169
+ # Drop troublesome cohorts
170
+ .filter(
171
+ ~(
172
+ (pl.col("cohort_lottery").is_not_null())
173
+ & (pl.col("cohort_lottery").is_in([1990, 1991]))
174
+ )
175
+ )
176
+ .filter(
177
+ (pl.col("late_applicant") == "0")
178
+ |
179
+ (pl.col("late_applicant") == "")
180
+ |
181
+ (pl.col("late_applicant").is_null())
182
+ )
183
+ )
184
+ """
185
+ if cmo == "kipp_tx":
186
+ enr_filt = (
187
+ enr
188
+ .select("sid_cepr_kipp_tx", "enrollment_years", "enroll_kipp")
189
+ .unique("sid_cepr_kipp_tx")
190
+ .rename({
191
+ "sid_cepr_kipp_tx": "sid_cepr",
192
+ })
193
+ )
194
+ df = (
195
+ old_data
196
+ .filter(pl.col("cmo_name") == "KIPP TX")
197
+ .filter(pl.col("sid_cepr") != "")
198
+ .filter(pl.col("sample") == 1)
199
+ # Restrict to the randomized years
200
+ .filter(pl.col("school_year").cast(pl.Int32, strict=False).is_between(2012, 2016))
201
+ # Create better filter out late apps
202
+ .with_columns(
203
+ pl.when(
204
+ (
205
+ pl.col("application_date")
206
+ .str.strptime(pl.Date, "%m/%d/%Y", strict=False)
207
+ .dt.year() == pl.col("school_year").cast(pl.Int64, strict=False)
208
+ )
209
+ &
210
+ (
211
+ pl.col("application_date")
212
+ .str.strptime(pl.Date, "%m/%d/%Y", strict=False)
213
+ .dt.month() > 1
214
+ )
215
+ )
216
+ .then(1)
217
+ .otherwise(0)
218
+ .alias("late_applicant_clean")
219
+ )
220
+ .filter(pl.col("late_applicant_clean") == 0)
221
+ # Join on KIPP foundation enrollment
222
+ .join(enr_filt, how = "left", on = "sid_cepr", validate = "m:1")
223
+ .rename({
224
+ "enroll": "enroll_old"
225
+ })
226
+ .with_columns(
227
+ pl.when(pl.col("cohort_lottery").cast(pl.Int32) <= 2003)
228
+ .then(pl.col("enroll_kipp").fill_null(0))
229
+ .alias("enroll_kipp")
230
+ )
231
+ .with_columns(
232
+ pl.col("enrollment_years").fill_null(0).alias("enrollment_years"),
233
+
234
+ pl.when(pl.col("cohort_lottery").cast(pl.Int32) <= 2003)
235
+ .then(pl.col("enroll_kipp"))
236
+ .otherwise(pl.col("enroll_old"))
237
+ .alias("enroll")
238
+ )
239
+ )
240
+ elif cmo == "ascend":
241
+ df = (
242
+ old_data
243
+ .filter(pl.col("cmo_name") == "ASCEND PUBLIC CHARTER SCHOOLS")
244
+ .filter(pl.col("sid_cepr") != "")
245
+ .filter(pl.col("sample") == 1)
246
+ )
247
+ elif cmo == "kipp_nc":
248
+ enr_filt = (
249
+ enr
250
+ .select("sid_cepr_kipp_nc", "enrollment_years", "enroll_kipp")
251
+ .unique("sid_cepr_kipp_nc")
252
+ .rename({
253
+ "sid_cepr_kipp_nc": "sid_cepr",
254
+ })
255
+ )
256
+ df = (
257
+ old_data
258
+ .filter(pl.col("cmo_name") == "KIPP NC")
259
+ .filter(pl.col("sid_cepr") != "")
260
+ .filter(pl.col("sample") == 1)
261
+ # Join on KIPP foundation enrollment
262
+ .join(enr_filt, how = "left", on = "sid_cepr", validate = "m:1")
263
+ .rename({
264
+ "enroll": "enroll_old"
265
+ })
266
+ .with_columns(
267
+ pl.when(pl.col("cohort_lottery").cast(pl.Int32) <= 2003)
268
+ .then(pl.col("enroll_kipp").fill_null(0))
269
+ .alias("enroll_kipp")
270
+ )
271
+ .with_columns(
272
+ pl.col("enrollment_years").fill_null(0).alias("enrollment_years"),
273
+
274
+ pl.when(pl.col("cohort_lottery").cast(pl.Int32) <= 2003)
275
+ .then(pl.col("enroll_kipp"))
276
+ .otherwise(pl.col("enroll_old"))
277
+ .alias("enroll")
278
+ )
279
+ )
280
+ elif cmo == "kipp_ma":
281
+ enr_filt = (
282
+ enr
283
+ .select("sid_cepr_kipp_ma", "enrollment_years", "enroll_kipp")
284
+ .unique("sid_cepr_kipp_ma")
285
+ .rename({
286
+ "sid_cepr_kipp_ma": "sid_cepr",
287
+ })
288
+ )
289
+ df = (
290
+ old_data
291
+ .filter(pl.col("cmo_name") == "KIPP MA")
292
+ .filter(pl.col("sid_cepr") != "")
293
+ .filter(pl.col("sample") == 1)
294
+ .filter(
295
+ (pl.col("late_applicant") == "0")
296
+ |
297
+ (pl.col("late_applicant") == "")
298
+ |
299
+ (pl.col("late_applicant").is_null())
300
+ )
301
+ # Join on KIPP foundation enrollment
302
+ .join(enr_filt, how = "left", on = "sid_cepr", validate = "m:1")
303
+ .rename({
304
+ "enroll": "enroll_old"
305
+ })
306
+ .with_columns(
307
+ pl.when(pl.col("cohort_lottery").cast(pl.Int32) <= 2003)
308
+ .then(pl.col("enroll_kipp").fill_null(0))
309
+ .alias("enroll_kipp")
310
+ )
311
+ .with_columns(
312
+ pl.col("enrollment_years").fill_null(0).alias("enrollment_years"),
313
+
314
+ pl.when(pl.col("cohort_lottery").cast(pl.Int32) <= 2003)
315
+ .then(pl.col("enroll_kipp"))
316
+ .otherwise(pl.col("enroll_old"))
317
+ .alias("enroll")
318
+ )
319
+ )
320
+ elif cmo == "great_oaks":
321
+ df = (
322
+ old_data
323
+ .filter(pl.col("cmo_name") == "GREAT OAKS")
324
+ .filter(pl.col("sid_cepr") != "")
325
+ .filter(pl.col("sample") == 1)
326
+ )
327
+ elif cmo == "ilt":
328
+ df = (
329
+ old_data
330
+ .filter(pl.col("cmo_name") == "INTERNATIONAL LEADERSHIP OF TEXAS")
331
+ .filter(pl.col("sid_cepr") != "")
332
+ .filter(pl.col("sample") == 1)
333
+ .filter(
334
+ (pl.col("late_applicant") == "0")
335
+ |
336
+ (pl.col("late_applicant") == "")
337
+ |
338
+ (pl.col("late_applicant").is_null())
339
+ )
340
+ )
341
+ else:
342
+ df = (
343
+ scan_readstat(CMO_APPS / "stata" / "{0}.dta".format(cmo))
344
+ )
345
+ dfs.append(df)
346
+ # Create stata version
347
+ out_stata = (
348
+ pl.concat(dfs, how = "diagonal_relaxed")
349
+ # Do some cleaning
350
+ .with_columns(
351
+ pl.col("entry_grade_clean").replace(grade_map).alias("entry_grade_clean"),
352
+ pl.col("late_app").fill_null(0),
353
+ pl.col("application_cancel").fill_null(0),
354
+ # Replace the null risk id names with the risk ids for CMOs we didn't change
355
+ pl.coalesce(["risk_id_name", "risk_id"]).alias("risk_id_name"),
356
+ # Fix initial_offer and waitlist_offer
357
+ pl.coalesce(["initial_offer", "offer_initial"]).alias("initial_offer"),
358
+ pl.coalesce(["waitlist_offer", "offer_waitlist"]).alias("waitlist_offer"),
359
+ )
360
+ .drop("cohort_lottery")
361
+ .with_columns([
362
+ pl.col("dob_clean")
363
+ .str
364
+ .strip_chars()
365
+ .str
366
+ .slice(-4)
367
+ .cast(pl.String)
368
+ .alias("cohort_lottery")
369
+ ])
370
+ .with_columns( # fix sid cepr (weirdly sometmes a cmo will have a decimanl in the string leading to errors later on) must convert 1010.0 -> 1010
371
+ pl.col("sid_cepr").cast(pl.Float64, strict=False).cast(pl.Int64).cast(pl.String).str.strip_chars().alias("sid_cepr"),
372
+ )
373
+ # Drop non randomized apps
374
+ .filter(
375
+ (pl.col("late_app") != 1)
376
+ &
377
+ (pl.col("application_cancel") != 1)
378
+ )
379
+ ).collect()
380
+ # Create parquet version
381
+ out_parquet = (
382
+ out_stata
383
+ .filter(
384
+ (pl.col("late_app") != 1)
385
+ &
386
+ (pl.col("application_cancel") != 1)
387
+ )
388
+ )
389
+ # Write datasets
390
+ out_stata.to_pandas().to_stata(OUTPUT_LONG_DTA, write_index=False, version = 118)
391
+ out_parquet.write_parquet(OUTPUT_LONG_PARQUET)
392
+ return out_stata, out_parquet
393
+
394
+ # Collapse to wide
395
+ def collapse_to_wide(
396
+ input_parquet_path,
397
+ group_key,
398
+ output_parquet_path,
399
+ output_dta_path,
400
+ ):
401
+ # Read in long parquet data (which already drops late apps)
402
+ long_input = pl.scan_parquet(input_parquet_path)
403
+ print(f"Starting N (Long): {long_input.select(pl.len()).collect().item()}")
404
+ # Define cols we need to take max of and the grouping key
405
+ max_cols = [
406
+ "enroll",
407
+ "enroll_kipp",
408
+ "enrollment_years",
409
+ "offer_accepted",
410
+ "offer",
411
+ "offer_imputed_enr",
412
+ "offer_imputed_ln",
413
+ "offer_imputed_wn",
414
+ "initial_offer",
415
+ "waitlist_offer",
416
+ "sibling",
417
+ "transfer",
418
+ "zoned",
419
+ "staff",
420
+ "sibling_concur",
421
+ ]
422
+ # Within each CMO, group to unique group_key level
423
+ first_cols = [
424
+ c for c in long_input.collect_schema().names()
425
+ if c not in group_key + max_cols + ["risk_id_name"]
426
+ ]
427
+ # Create collapsed dataset
428
+ collapsed = (
429
+ long_input
430
+ .group_by(group_key)
431
+ .agg(
432
+ # Max variables
433
+ [pl.max(c).alias(c) for c in max_cols]
434
+ +
435
+ # All risk ids concatenated together
436
+ [
437
+ pl.col("risk_id_name")
438
+ .unique()
439
+ .sort()
440
+ .str.join("_")
441
+ .alias("risk_set")
442
+ ]
443
+ +
444
+ # Everything else from first row
445
+ [
446
+ pl.first(c).alias(c) for c in first_cols
447
+ ]
448
+ )
449
+ )
450
+ collapsed_final = collapsed.collect()
451
+ print(f"Ending N (Wide): {len(collapsed_final)}")
452
+ # Write datasets
453
+ collapsed_final.to_pandas().to_stata(output_dta_path, write_index=False, version = 118)
454
+ collapsed_final.write_parquet(output_parquet_path)
455
+ return collapsed_final
456
+
457
+ # Merge on nsc
458
+ def merge_nsc(
459
+ input_parquet_path,
460
+ output_parquet_path,
461
+ output_dta_path,
462
+ ):
463
+ # Read in parquet data (assuming it already drops late apps)
464
+ input = (
465
+ pl.scan_parquet(input_parquet_path)
466
+ )
467
+ # Read in NSC outcomes
468
+ nsc = scan_readstat(NSC).with_columns(pl.col("sid_cepr").cast(pl.String))
469
+ output = (
470
+ input
471
+ .with_columns(
472
+ pl.col("sid_cepr").cast(pl.Float64, strict=False).cast(pl.Int64).cast(pl.String).str.strip_chars().alias("sid_cepr"),
473
+ )
474
+ .join(nsc, on="sid_cepr", how="left", validate="m:1")
475
+ .collect()
476
+ )
477
+ # Write datasets
478
+ output.to_pandas().to_stata(output_dta_path, write_index=False, version = 118)
479
+ output.write_parquet(output_parquet_path)
480
+ return output
481
+
482
+
483
+
484
+ # Creates kiteworks folder corresponding to current date
485
+ def kiteworks():
486
+ today = datetime.today().strftime("%Y%m%d")
487
+ # kiteworks folders
488
+ KITEWORKS_DATE = KITEWORKS_FOLDER / today
489
+ KITEWORKS_DATE.mkdir(parents=True, exist_ok=True)
490
+ # define data and codebook output
491
+ DATA_OUTPUT = KITEWORKS_DATE / "combined_data.csv"
492
+ CODEBOOK_OUTPUT = KITEWORKS_DATE / "combined_codebook.csv"
493
+ # Create csv and codebook files set
494
+ # 7/13: subset
495
+ csv_files = [
496
+ CMO_ID_NO_PII / "uncommon.csv",
497
+ ]
498
+ codebook_files = [
499
+ CMO_ID_NO_PII / "uncommon_codebook.csv",
500
+ ]
501
+ # csv_files = [
502
+ # f
503
+ # for f in sorted(CMO_ID_NO_PII.glob("*.csv"))
504
+ # if "codebook" not in f.stem
505
+ # ]
506
+ # codebook_files = [
507
+ # f
508
+ # for f in sorted(CMO_ID_NO_PII.glob("*.csv"))
509
+ # if "codebook" in f.stem
510
+ # ]
511
+ # output combined data
512
+ if csv_files:
513
+ combined_lf = pl.concat(
514
+ [pl.scan_csv(f, infer_schema=False) for f in csv_files],
515
+ how = "diagonal",
516
+ )
517
+ combined_lf.collect().write_csv(DATA_OUTPUT)
518
+ # output combined codebook
519
+ if codebook_files:
520
+ combined_codebook = pl.concat(
521
+ [
522
+ pl.scan_csv(f, infer_schema=False)
523
+ for f in codebook_files
524
+ ],
525
+ how = "diagonal",
526
+ )
527
+ # De-dupe common vars
528
+ final_codebook = (
529
+ combined_codebook
530
+ .group_by("Variable Name")
531
+ .agg(
532
+ pl.col("Type").first(),
533
+ pl.col("Count Non-Null").cast(pl.Int64).sum().alias("Count Non-Null"),
534
+ pl.col("Description").first(),
535
+ )
536
+ .sort("Count Non-Null", descending=True)
537
+ .drop("Count Non-Null")
538
+ )
539
+ # Save it
540
+ final_codebook.collect().write_csv(CODEBOOK_OUTPUT)
541
+
542
+ # Collapse to unique schools
543
+ def summarize_schools(
544
+ input_parquet_path,
545
+ output_csv_path
546
+ ):
547
+ # Read in long parquet data (which already drops late apps)
548
+ long_input = pl.scan_parquet(input_parquet_path)
549
+ # Collapse to unique cmo, region, school_name combos
550
+ # NOTE: Old build CMOs may not have cmo_region and ren_num consistently filled
551
+ sch_ren_map = (
552
+ long_input
553
+ .filter(
554
+ pl.col("school_name").is_not_null()
555
+ & (pl.col("school_name") != "")
556
+ )
557
+ .select(["cmo_name", "cmo_region", "ren_num", "school_name"])
558
+ # unique row-school-ren_num combos
559
+ .group_by(["cmo_name", "cmo_region", "school_name"])
560
+ .agg(
561
+ pl.len().alias("n_obs"),
562
+ pl.col("ren_num")
563
+ .drop_nulls()
564
+ .unique()
565
+ .sort()
566
+ .cast(pl.String)
567
+ .str.join("+")
568
+ .alias("ren_nums")
569
+ )
570
+ .select(["cmo_name", "cmo_region", "school_name", "n_obs", "ren_nums"])
571
+ .sort(["cmo_name", "cmo_region", "school_name"])
572
+ )
573
+ # Save
574
+ sch_ren_map.collect().write_csv(output_csv_path)
575
+
576
+
577
+ if __name__ == "__main__":
578
+ """
579
+
580
+ # Create kiteworks output for cmo-id transfers
581
+ kiteworks()
582
+
583
+ # Run cmo apps scripts
584
+ batch_start = time.perf_counter()
585
+ logging.info("Starting applications build")
586
+ with ThreadPoolExecutor(max_workers=2) as pool:
587
+ futures = [pool.submit(run_cmo, s) for s in apps_scripts]
588
+ for fut in as_completed(futures):
589
+ fut.result()
590
+ batch_elapsed_min_apps = (time.perf_counter() - batch_start) / 60
591
+ logging.info(f"Finished applications build; total runtime was {batch_elapsed_min_apps: .2f} minutes")
592
+ """
593
+ # Append the CMO apps builds together
594
+ batch_start = time.perf_counter()
595
+ logging.info("Starting CMO appending")
596
+
597
+ append_cmos(
598
+ "aspire",
599
+ "yes_prep",
600
+ "christel_house",
601
+ "green_dot",
602
+ "democracy_prep",
603
+ "ilt",
604
+ "rocketship",
605
+ "kipp_nj",
606
+ "kipp_tx",
607
+ "uncommon",
608
+ "kipp_ma",
609
+ "great_oaks",
610
+ "harmony",
611
+ "ascend",
612
+ "kipp_nc",
613
+ )
614
+ batch_elapsed_min_cmo = (time.perf_counter() - batch_start) / 60
615
+ logging.info(f"Finished CMO appending; total runtime was {batch_elapsed_min_cmo: .2f} minutes")
616
+
617
+ # Collapse to wide
618
+ batch_start = time.perf_counter()
619
+ logging.info("Starting wide collapse")
620
+
621
+
622
+ collapse_to_wide(OUTPUT_LONG_PARQUET, ["cmo_name", "sid_cepr"], OUTPUT_WIDE_SID_PARQUET, OUTPUT_WIDE_SID_DTA)
623
+
624
+ batch_elapsed_min_wide = (time.perf_counter() - batch_start) / 60
625
+ logging.info(f"Finished wide collapse; total runtime was {batch_elapsed_min_wide: .2f} minutes")
626
+
627
+ # Merge on nsc
628
+ batch_start = time.perf_counter()
629
+ logging.info("Starting NSC merge")
630
+ merge_nsc(OUTPUT_LONG_PARQUET, OUTPUT_NSC_PARQUET, OUTPUT_NSC_DTA)
631
+ merge_nsc(OUTPUT_WIDE_SID_PARQUET, OUTPUT_WIDE_NSC_PARQUET, OUTPUT_WIDE_NSC_DTA)
632
+ batch_elapsed_min_nsc = (time.perf_counter() - batch_start) / 60
633
+ logging.info(f"Finished NSC merge; total runtime was {batch_elapsed_min_nsc: .2f} minutes")
634
+
635
+
636
+
637
+
638
+