ltc-code 0.2.32__tar.gz → 0.2.34__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 (119) hide show
  1. {ltc_code-0.2.32 → ltc_code-0.2.34}/PKG-INFO +13 -1
  2. ltc_code-0.2.34/README.md +22 -0
  3. {ltc_code-0.2.32 → ltc_code-0.2.34}/pyproject.toml +3 -3
  4. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/nsc/raw/directory.dta +1 -1
  5. ltc_code-0.2.34/src/ltc_code/paper/README.md +114 -0
  6. ltc_code-0.2.34/src/ltc_code/paper/build_latex.py +125 -0
  7. ltc_code-0.2.34/src/ltc_code/paper/config.py +27 -0
  8. ltc_code-0.2.34/src/ltc_code/paper/data/charter_exhibits/disclosed_results.xlsx +0 -0
  9. ltc_code-0.2.34/src/ltc_code/paper/data/charter_exhibits/pending_results.xlsx +0 -0
  10. ltc_code-0.2.34/src/ltc_code/paper/exhibit_selection.py +52 -0
  11. ltc_code-0.2.34/src/ltc_code/paper/latex_figures.py +82 -0
  12. ltc_code-0.2.34/src/ltc_code/paper/latex_requirements.txt +7 -0
  13. ltc_code-0.2.34/src/ltc_code/paper/latex_source/outputs/tables/output/age25_earnings_table.tex +77 -0
  14. ltc_code-0.2.34/src/ltc_code/paper/latex_source/outputs/tables/output/balance_table.tex +68 -0
  15. ltc_code-0.2.34/src/ltc_code/paper/latex_source/outputs/tables/output/cmo_details_table.tex +23 -0
  16. ltc_code-0.2.34/src/ltc_code/paper/latex_source/outputs/tables/output/college_data_comparison_table.tex +33 -0
  17. ltc_code-0.2.34/src/ltc_code/paper/latex_source/outputs/tables/output/college_enrollment_completion_table.tex +54 -0
  18. ltc_code-0.2.34/src/ltc_code/paper/latex_source/outputs/tables/output/college_heterogeneity_table.tex +38 -0
  19. ltc_code-0.2.34/src/ltc_code/paper/latex_source/outputs/tables/output/college_mediation_additional_specs_table.tex +29 -0
  20. ltc_code-0.2.34/src/ltc_code/paper/latex_source/outputs/tables/output/college_test_score_mediation_table.tex +28 -0
  21. ltc_code-0.2.34/src/ltc_code/paper/latex_source/outputs/tables/output/earnings_heterogeneity_table.tex +59 -0
  22. ltc_code-0.2.34/src/ltc_code/paper/latex_source/outputs/tables/output/earnings_robustness_table.tex +25 -0
  23. ltc_code-0.2.34/src/ltc_code/paper/latex_source/outputs/tables/output/earnings_threshold_table.tex +50 -0
  24. ltc_code-0.2.34/src/ltc_code/paper/latex_source/outputs/tables/output/intervention_calculation_table.tex +27 -0
  25. ltc_code-0.2.34/src/ltc_code/paper/latex_source/outputs/tables/output/lottery_sample_selection_table.tex +28 -0
  26. ltc_code-0.2.34/src/ltc_code/paper/latex_source/outputs/tables/output/other_mediators_table.tex +38 -0
  27. ltc_code-0.2.34/src/ltc_code/paper/latex_source/outputs/tables/output/summary_statistics_table.tex +105 -0
  28. ltc_code-0.2.34/src/ltc_code/paper/latex_source/paper/appendix.tex +145 -0
  29. ltc_code-0.2.34/src/ltc_code/paper/latex_source/paper/preamble.tex +556 -0
  30. ltc_code-0.2.34/src/ltc_code/paper/latex_source/paper/tables-and-figures.tex +147 -0
  31. ltc_code-0.2.34/src/ltc_code/paper/run_latex.sh +11 -0
  32. ltc_code-0.2.34/src/ltc_code/paper/split_inputs.py +171 -0
  33. ltc_code-0.2.34/src/ltc_code/paper/table_bindings.json +7183 -0
  34. ltc_code-0.2.34/src/ltc_code/paper/verify_exclusions.py +69 -0
  35. ltc_code-0.2.34/src/ltc_code/paper/verify_latex.py +73 -0
  36. ltc_code-0.2.34/src/ltc_code/tract_covars/README.md +85 -0
  37. ltc_code-0.2.34/src/ltc_code/tract_covars/build_tract_covars.py +171 -0
  38. ltc_code-0.2.34/src/ltc_code/tract_covars/inputs/nhgis0006_ds250_20205_tract_codebook.txt +263 -0
  39. ltc_code-0.2.34/src/ltc_code/tract_covars/inputs/nhgis0006_ds273_20245_tract_codebook.txt +261 -0
  40. ltc_code-0.2.32/README.md +0 -10
  41. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260614_new_build_scripts_update/aspire.py +0 -0
  42. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260614_new_build_scripts_update/check_cmo_apps.do +0 -0
  43. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260614_new_build_scripts_update/christel_house.py +0 -0
  44. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260614_new_build_scripts_update/democracy_prep.py +0 -0
  45. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260614_new_build_scripts_update/green_dot.py +0 -0
  46. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260614_new_build_scripts_update/helpers.py +0 -0
  47. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260614_new_build_scripts_update/ilt.py +0 -0
  48. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260614_new_build_scripts_update/kipp_nj.py +0 -0
  49. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260614_new_build_scripts_update/main.py +0 -0
  50. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260614_new_build_scripts_update/mappings.py +0 -0
  51. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260614_new_build_scripts_update/rocketship.py +0 -0
  52. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260614_new_build_scripts_update/yes_prep.py +0 -0
  53. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260630_census_disclosure/aspire.py +0 -0
  54. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260630_census_disclosure/christel_house.py +0 -0
  55. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260630_census_disclosure/democracy_prep.py +0 -0
  56. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260630_census_disclosure/green_dot.py +0 -0
  57. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260630_census_disclosure/ilt.py +0 -0
  58. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260630_census_disclosure/kipp.py +0 -0
  59. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260630_census_disclosure/kipp_nj.py +0 -0
  60. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260630_census_disclosure/rocketship.py +0 -0
  61. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260630_census_disclosure/yes_prep.py +0 -0
  62. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260706_ceprscripts/BALANCE.do +0 -0
  63. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260706_ceprscripts/FS.do +0 -0
  64. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260706_ceprscripts/ITT.do +0 -0
  65. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260706_ceprscripts/TOT.do +0 -0
  66. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260706_ceprscripts/apps_helpers.py +0 -0
  67. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260706_ceprscripts/harmony.py +0 -0
  68. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260706_ceprscripts/main.py +0 -0
  69. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260712_uncommon_scripts/main.py +0 -0
  70. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260712_uncommon_scripts/mappings.py +0 -0
  71. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/20260712_uncommon_scripts/uncommon.py +0 -0
  72. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/__init__.py +0 -0
  73. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/aspire.py +0 -0
  74. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/check_cmo_apps.do +0 -0
  75. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/christel_house.py +0 -0
  76. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/green_dot.py +0 -0
  77. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/helpers.py +0 -0
  78. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/june13.py +0 -0
  79. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/june2.py +0 -0
  80. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/june30.py +0 -0
  81. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/june5.py +0 -0
  82. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/june7.py +0 -0
  83. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/kipp_nj.py +0 -0
  84. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/kipp_tx.py +0 -0
  85. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/main.py +0 -0
  86. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/make_summary_stats_table.py +0 -0
  87. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/mappings.py +0 -0
  88. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/may27.py +0 -0
  89. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/nsc/EXTENSIONS.md +0 -0
  90. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/nsc/NEW_CROSSWALK.md +0 -0
  91. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/nsc/OUTCOMES.md +0 -0
  92. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/nsc/__init__.py +0 -0
  93. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/nsc/build_nsc_outcomes.py +0 -0
  94. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/nsc/build_nsc_outcomes_new.py +0 -0
  95. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/nsc/dhs_stem/dhs_stem_cip_additions_2024.csv +0 -0
  96. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/nsc/dhs_stem/extract_dhs_stem_cips.R +0 -0
  97. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/nsc/dhs_stem/stemList2024.pdf +0 -0
  98. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/nsc/naics.csv +0 -0
  99. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/nsc/naics.py +0 -0
  100. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/nsc/naics_raw.xlsx +0 -0
  101. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/nsc/raw/CREDENTIAL_LEVEL_LOOKUP_TABLE.xlsx +0 -0
  102. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/nsc/raw/IPEDS_IC_2013.csv +0 -0
  103. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/nsc/raw/IPEDS_IC_manual.xlsx +0 -0
  104. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/nsc/raw/NSC_SCHOOL_CODE_TO_IPEDS_UNIT_ID_XWALK_APR-2023.xlsx +0 -0
  105. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/nsc/raw/chetty/mrc_table11.dta +0 -0
  106. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/nsc/raw/chetty/mrc_table2.dta +0 -0
  107. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/nsc/raw/college_crosswalk.xls +0 -0
  108. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/nsc/raw/ipeds_data.dta +0 -0
  109. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/nsc/run_nsc_outcomes.py +0 -0
  110. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/plot_bars.py +0 -0
  111. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/plot_colleges.py +0 -0
  112. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/polars_dates.py +0 -0
  113. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/rocketship.py +0 -0
  114. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/schema_mapping.py +0 -0
  115. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/school_name_xwalk/__init__.py +0 -0
  116. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/school_name_xwalk/all_schools_with_ccd.csv +0 -0
  117. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/school_name_xwalk/merge_school_ccd.py +0 -0
  118. {ltc_code-0.2.32 → ltc_code-0.2.34}/src/ltc_code/signal_var_calcs.py +0 -0
  119. {ltc_code-0.2.32 → ltc_code-0.2.34}/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.32
3
+ Version: 0.2.34
4
4
  Summary: Add your description here
5
5
  Requires-Dist: fastexcel>=0.16,<0.20
6
6
  Requires-Dist: polars>=1.36.1,<1.42
@@ -18,3 +18,15 @@ filters and categorical color groups. Plotting additionally requires
18
18
  `oi-tools`, `plotnine`, and `pyarrow` in the analysis environment; they are
19
19
  not required to run the NSC build. See [NSC extensions](src/ltc_code/nsc/EXTENSIONS.md)
20
20
  for attendance definitions, coarsening names, and plotting examples.
21
+
22
+
23
+ ### Paper exhibits (0.2.33)
24
+
25
+ The complete standalone LaTeX PDF workflow is bundled under
26
+ `src/ltc_code/paper` (installed as `ltc_code/paper`). It includes the
27
+ fixed disclosed and replaceable pending Excel workbooks, Python scripts,
28
+ LaTeX templates, and table/figure exclusion controls. See
29
+ [paper instructions](src/ltc_code/paper/README.md). Copy that whole directory
30
+ to a writable working location and run `bash run_latex.sh`. The paper
31
+ workflow uses its own pinned Python dependencies and requires LaTeX;
32
+ the existing package dependencies are unchanged.
@@ -0,0 +1,22 @@
1
+
2
+
3
+ ### College scatter plots (0.2.32)
4
+
5
+ `from ltc_code.plot_colleges import plot_colleges` provides an OI-themed
6
+ scatter with explicit Polars `x`/`y` aggregations, college grouping, optional
7
+ filters and categorical color groups. Plotting additionally requires
8
+ `oi-tools`, `plotnine`, and `pyarrow` in the analysis environment; they are
9
+ not required to run the NSC build. See [NSC extensions](src/ltc_code/nsc/EXTENSIONS.md)
10
+ for attendance definitions, coarsening names, and plotting examples.
11
+
12
+
13
+ ### Paper exhibits (0.2.33)
14
+
15
+ The complete standalone LaTeX PDF workflow is bundled under
16
+ `src/ltc_code/paper` (installed as `ltc_code/paper`). It includes the
17
+ fixed disclosed and replaceable pending Excel workbooks, Python scripts,
18
+ LaTeX templates, and table/figure exclusion controls. See
19
+ [paper instructions](src/ltc_code/paper/README.md). Copy that whole directory
20
+ to a writable working location and run `bash run_latex.sh`. The paper
21
+ workflow uses its own pinned Python dependencies and requires LaTeX;
22
+ the existing package dependencies are unchanged.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ltc-code"
3
- version = "0.2.32"
3
+ version = "0.2.34"
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"
@@ -19,5 +19,5 @@ requires = ["uv_build>=0.11.16,<0.12.0"]
19
19
  build-backend = "uv_build"
20
20
 
21
21
  [tool.uv.build-backend]
22
- source-exclude = ["**/.DS_Store", "**/__pycache__/**", "**/*.pyc", "src/ltc_code/nsc/input/**", "src/ltc_code/nsc/output/**"]
23
- wheel-exclude = ["**/.DS_Store", "**/__pycache__/**", "**/*.pyc", "ltc_code/nsc/input/**", "ltc_code/nsc/output/**"]
22
+ source-exclude = ["**/.DS_Store", "**/__pycache__/**", "**/*.pyc", "src/ltc_code/nsc/input/**", "src/ltc_code/nsc/output/**", "src/ltc_code/tract_covars/inputs/*.csv", "src/ltc_code/tract_covars/outputs/**"]
23
+ wheel-exclude = ["**/.DS_Store", "**/__pycache__/**", "**/*.pyc", "ltc_code/nsc/input/**", "ltc_code/nsc/output/**", "ltc_code/tract_covars/inputs/*.csv", "ltc_code/tract_covars/outputs/**"]
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
- name: ltc_code-0.2.32/src/ltc_code/nsc/raw/directory.dta
2
+ name: ltc_code-0.2.34/src/ltc_code/nsc/raw/directory.dta
3
3
  size: 256195841 bytes
4
4
  sha256: 9fe2a62ad77a3a17f5c8f45df08cc2e8daeb46425eeb062a576353aa2bd463d1
@@ -0,0 +1,114 @@
1
+ # Charter exhibits: two Excel inputs to the original LaTeX PDF
2
+
3
+ The build reads two independent workbooks. It produces the approved 19-page PDF, with the same tables, red/black numbers, figures, captions and notes.
4
+
5
+ ## Run in Census
6
+
7
+ 1. Keep `data/charter_exhibits/disclosed_results.xlsx` fixed.
8
+ 2. Replace `data/charter_exhibits/pending_results.xlsx` with your actual pending results, retaining its sheets, headers and record keys. You can instead change `PENDING_INPUT` in `build_latex.py` to your new file path.
9
+ 3. Set `synthetic` to `false` in the pending workbook's `metadata` tab after replacing its synthetic values and chart inputs. Keep `role=pending` and `schema_version=2`. This only removes the synthetic footer; **it never switches off the fixed workbook**.
10
+ 4. Run `bash run_latex.sh` in the extracted portable folder. Output: `output/charter_exhibits_latex.pdf`.
11
+
12
+ In the sandbox, run `bash code/explore_codex/charter_exhibits/run_latex.sh` from the sandbox root. Input files are under `outputs/charter_exhibits/inputs/` and the PDF is under `outputs/charter_exhibits/`.
13
+
14
+ Python dependencies are listed in `latex_requirements.txt`; the runner uses uv. LaTeX requires `latexmk` and `pdflatex` (TeX Live or MacTeX). With dependencies already installed, `python build_latex.py` also runs the build. No network, live Overleaf connection, or live charter repository is needed. The sandbox charter repository is not modified.
15
+
16
+ ## Exclude tables or figures
17
+
18
+ Edit the two lists near the top of `build_latex.py`. They default to empty, so the complete deck still builds:
19
+
20
+ ```python
21
+ EXCLUDE_TABLES = []
22
+ EXCLUDE_FIGURES = []
23
+ ```
24
+
25
+ For example:
26
+
27
+ ```python
28
+ EXCLUDE_TABLES = ["Table A.4", "Table A.6"]
29
+ EXCLUDE_FIGURES = ["Figure A.1"]
30
+ ```
31
+
32
+ You may use either the printed label or its identifier below. Exclusions skip the complete exhibit, its generation, and inputs used only by that exhibit. Those unused rows or whole tabs may be absent from the workbooks. Shared inputs remain required by any included exhibit. Both workbooks and their `metadata` tabs remain required.
33
+
34
+ Remaining tables and figures retain their original numbers (e.g., Table 4 stays Table 4); page numbers run consecutively. References in the unchanged draft notes retain the original exhibit numbers, even when referring to an omitted exhibit. Clearing both lists restores everything. Invalid names and excluding every exhibit produce a clear error. The audit JSON records the included and excluded exhibits. These switches control whole tables/figures, not sample columns within an exhibit.
35
+
36
+ | Printed label | Identifier |
37
+ | --- | --- |
38
+ | Table 1 | `summary_statistics_table` |
39
+ | Table 2 | `balance_table` |
40
+ | Table 3 | `age25_earnings_table` |
41
+ | Table 4 | `earnings_heterogeneity_table` |
42
+ | Table 5 | `college_enrollment_completion_table` |
43
+ | Table 6 | `college_test_score_mediation_table` |
44
+ | Table 7 | `other_mediators_table` |
45
+ | Figure A.1 | `appendix-credo-distributions` |
46
+ | Figure A.2 | `appendix-college-by-age-placeholder` |
47
+ | Figure A.3 | `appendix-cmo-leave-one-out` |
48
+ | Figure A.4 | `appendix-earnings-by-age-firm-chars` |
49
+ | Table A.1 | `earnings_robustness_table` |
50
+ | Table A.2 | `earnings_threshold_table` |
51
+ | Table A.3 | `college_data_comparison_table` |
52
+ | Table A.4 | `college_heterogeneity_table` |
53
+ | Table A.5 | `college_mediation_additional_specs_table` |
54
+ | Table A.6 | `lottery_sample_selection_table` |
55
+ | Table A.7 | `cmo_details_table` |
56
+ | Table A.8 | `intervention_calculation_table` |
57
+
58
+ ## Fixed disclosed workbook
59
+
60
+ `disclosed_results.xlsx` contains only existing manuscript values from synced Overleaf commit `6ebdd8c1a84bcfd72cabf3c85a6b2a986cc5c416`, using numeric Excel cells rather than display strings for estimates.
61
+
62
+ | Tab | Input columns / purpose |
63
+ | --- | --- |
64
+ | `summary_stats` | `sample, variable, mean, sd, count, unit` |
65
+ | `TOT` | `sample, depvar, spec, coef, se, ccm, N_ppl, unit, coef_stars` |
66
+ | `ITT`, `first_stage` | Regression keys, `coef, se, control_mean, N_ppl, unit, coef_stars` |
67
+ | `OLS` | Regression keys, `coef, se, comparison_mean, N_ppl, unit, coef_stars` |
68
+ | `balance` | `sample, depvar, coef, se, control_mean, N_ppl, unit, coef_stars` |
69
+ | `mediation_conversions` | `depvar, earnings_gain, unit`; exact conversion values on the manuscript's existing scale |
70
+ | `interventions` | `program, estimated_effect, intervention, notes, years, unit`; source effects, durations and calculation notes |
71
+ | `metadata` | Input role, version and source snapshot |
72
+
73
+ Unreported sample sizes are blank. The summary table's `variable=n_ppl` rows store reported total people in `count`; no source sample sizes were invented or copied from synthetic data. Statistical stars supplied by the manuscript are retained. The previously corrected first-stage/ITT SE pairing is retained.
74
+
75
+ ## Pending workbook
76
+
77
+ `pending_results.xlsx` contains the red table entries and synthetic chart data. It currently reproduces the approved demonstration. Replace its data with your actual estimates. No old synthetic workbook or hidden numerical fallback is used.
78
+
79
+ - `summary_stats`, `TOT`, `OLS`, `balance`: the same keyed estimate format as the fixed file, for pending fields only.
80
+ - `predictions`: `sample, depvar, spec, coef, predicted_gain, unit`. Supply precomputed lognormal threshold effects in `coef` and mediation predicted earnings in `predicted_gain`. These are direct inputs, not calculations from the demonstration's old synthetic conversion factors. The sample workbook preserves the exact red predictions you approved.
81
+ - `sample_selection`: `step, applications, N_ppl`.
82
+ - `cmo_details`: `cmo, geographic_area, grades, lottery_years`. Grade and year ranges are text.
83
+ - `credo`: one CMO per row, with `math_effect`, `reading_effect`, `in_study` (0 or 1). Effects are in standard deviations.
84
+ - `TOT_by_cmo`: network regression estimates, SEs and CCMs; supplies the leave-one-network-out plot.
85
+ - `figure_reference`: the separately supplied pooled estimate used for the leave-one-network-out reference line. This preserves the approved synthetic chart independently of the fixed earnings table. Replace it with the reference estimate appropriate to your real chart.
86
+ - Age profiles remain in `TOT`, with `sample=age_profile` and `spec=observed` or `ein_fes`.
87
+ - `metadata`: keep `role=pending`, `schema_version=2`; update `synthetic` and `source` for real inputs.
88
+
89
+ The pending file is a template for the fields that remain to be filled. **A blank field alongside a populated field can belong to the fixed workbook.** For example, a subgroup row may contain only its pending `ccm`, while `coef` and `se` are fixed in the disclosed file. Leave those fixed fields blank in the pending file. The loader rejects conflicting nonblank fields instead of silently overwriting them. It also rejects duplicate keys and missing required rows or columns. Rows can be sorted freely.
90
+
91
+ ## Units, missingness and stars
92
+
93
+ - `unit=proportion`: store probabilities and probability effects on the 0-1 scale. For example, `0.056` displays as `5.6` percentage points. Means, SEs and CCMs use the same scale.
94
+ - `unit=usd`: unscaled dollars. `percentile`: rank points. `years` and `year`: years. `count`: whole people or applications.
95
+ - Threshold prediction inputs are percentage points (`3.3` means 3.3 pp); mediation prediction inputs are dollars (`834` means $834). The fixed mediation conversions retain the manuscript's exact source scale.
96
+ - Keep unknown numerical results empty. Zero is a real result and displays as zero. Missing values display as dashes.
97
+ - `coef_stars` accepts `*`, `**`, `***`, `none`, or `auto`. The supplied demo keeps the exact approved stars. When replacing estimates, supply your corresponding stars or set `auto` to calculate normal-approximation stars from `coef/se`. Table 6 deliberately displays its mediator coefficients without stars, as before.
98
+ - `N_ppl` and summary counts are retained as inputs; they appear only where the original paper layout includes them. Blank fixed Ns do not borrow pending counts.
99
+
100
+ ## Code and verification
101
+
102
+ - `build_latex.py`: two input paths, LaTeX document wrapper and compilation.
103
+ - `split_inputs.py`: keyed input validation for included exhibits, fixed/pending field separation, formatting and table rendering.
104
+ - `exhibit_selection.py`: exclusion names, source-block selection, original numbering and omitted-exhibit references.
105
+ - `table_bindings.json`: stable sample/outcome/specification-to-table-cell mappings and formatting; no fitted numerical estimates.
106
+ - `latex_figures.py`: workbook-driven charts, with the previously approved composition.
107
+ - `latex_source/`: original manuscript LaTeX templates, captions and draft notes.
108
+ - `verify_latex.py`: verifies row-order independence, rejects duplicate/overlapping inputs, and simulates a real pending replacement while checking all fixed cells remain unchanged.
109
+
110
+ `verify_exclusions.py` additionally tests missing excluded input sheets, shared dependencies, table-only and figure-only decks, original numbering, and restoring the full deck.
111
+
112
+ The build writes an audit JSON with both input paths and SHA-256 hashes, consumed keys, and layout hashes. The refactor was checked against the approved PDF: all 19 pages have identical text and the chart PNGs are byte-identical. Replacement testing leaves the fixed workbook byte-identical.
113
+
114
+ Only the two XLSX files and templates are needed at runtime. The earlier single-workbook `overleaf_values` mechanism and prototype synthetic calculation scripts are obsolete and excluded from the portable package. Existing draft caption/figure inconsistencies remain as approved; this refactor changes input ownership and code structure, not the paper's substantive content.
@@ -0,0 +1,125 @@
1
+ # --- Import necessary packages ---
2
+ import hashlib
3
+ import json
4
+ import os
5
+ import re
6
+ import shutil
7
+ import subprocess
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ sys.path.append('/Users/williamratnavale/Projects/codex_sandbox/code')
12
+ from config import HERE, OUTPUT_DIR
13
+ os.environ.setdefault('MPLCONFIGDIR',str(OUTPUT_DIR/'.matplotlib'))
14
+ from split_inputs import SplitInputs, build_tables
15
+ from latex_figures import make_figures
16
+ from pypdf import PdfReader
17
+ from exhibit_selection import select_exhibits
18
+
19
+ ###########################################################
20
+ # Define paths: this build never reads or writes the live charter repository
21
+ ###########################################################
22
+
23
+ LOCAL_INPUTS=HERE/'data/charter_exhibits'
24
+ INPUT_DIR=LOCAL_INPUTS if (LOCAL_INPUTS/'disclosed_results.xlsx').exists() else OUTPUT_DIR/'inputs'
25
+ # Keep DISCLOSED_INPUT fixed. Replace PENDING_INPUT with your pending actual results.
26
+ DISCLOSED_INPUT=INPUT_DIR/'disclosed_results.xlsx'
27
+ PENDING_INPUT=INPUT_DIR/'pending_results.xlsx'
28
+ LATEX_OUTPUT=(HERE/'output' if INPUT_DIR==LOCAL_INPUTS else OUTPUT_DIR)/'charter_exhibits_latex.pdf'
29
+ SOURCE=HERE/'latex_source'
30
+ BUILD=LATEX_OUTPUT.parent/'latex_build'
31
+
32
+ # Toggle whole exhibits here. Empty lists include everything.
33
+ # Examples: EXCLUDE_TABLES = ['Table A.4', 'lottery_sample_selection_table']
34
+ # EXCLUDE_FIGURES = ['Figure A.1', 'appendix-cmo-leave-one-out']
35
+ # All accepted labels/identifiers are listed in README_LATEX.md (README.md in ZIP).
36
+ EXCLUDE_TABLES = []
37
+ EXCLUDE_FIGURES = []
38
+
39
+
40
+ def build_latex(pending_path=PENDING_INPUT, output_path=LATEX_OUTPUT, build_dir=BUILD, disclosed_path=DISCLOSED_INPUT,
41
+ exclude_tables=None, exclude_figures=None):
42
+ main,appendix,included,excluded,references=select_exhibits(
43
+ SOURCE, EXCLUDE_TABLES if exclude_tables is None else exclude_tables,
44
+ EXCLUDE_FIGURES if exclude_figures is None else exclude_figures)
45
+ tables=[e['name'] for e in included if e['kind']=='table']
46
+ figures=[e['name'] for e in included if e['kind']=='figure']
47
+ data=SplitInputs(disclosed_path,pending_path,selected_tables=tables,selected_figures=figures)
48
+ build_dir=Path(build_dir);output_path=Path(output_path)
49
+ build_dir.mkdir(parents=True,exist_ok=True)
50
+ shutil.copytree(SOURCE/'paper',build_dir/'paper',dirs_exist_ok=True)
51
+ (build_dir/'outputs/scalars').mkdir(parents=True,exist_ok=True)
52
+ # The exhibit-only document does not consume paper prose scalars.
53
+ (build_dir/'outputs/scalars/scalars.sty').write_text('% No prose scalars are used by these exhibits.\n')
54
+ for folder in ['tables','figures']:
55
+ shutil.rmtree(build_dir/'outputs'/folder,ignore_errors=True)
56
+ derived=build_tables(data,SOURCE,build_dir)
57
+ make_figures(data,build_dir/'outputs/figures',selected_figures=figures)
58
+ (build_dir/'paper/main-tables.tex').write_text(main)
59
+ (build_dir/'paper/appendix.tex').write_text(appendix)
60
+ # Preserve the original captions, notes, float/page geometry and appendix numbering.
61
+ # Reference-only labels resolve references to the preceding (excluded) figures.
62
+ wrapper=r'''\documentclass[11pt,english]{article}
63
+ \makeatletter
64
+ \def\input@path{{paper/}{outputs/scalars/}}
65
+ \makeatother
66
+ \input{preamble}
67
+ \makeatletter
68
+ \AtBeginDocument{%
69
+ \newlabel{fig:earnings-early-adulthood}{{1}{1}{}{figure.1}{}}%
70
+ \newlabel{fig:earnings-distribution}{{2}{1}{}{figure.2}{}}%
71
+ \newlabel{fig:childhood-program-earnings}{{4}{1}{}{figure.4}{}}%
72
+ \bibcite{chetty2011star}{{2011}{Chetty et~al.}{{Chetty et~al.}}{}}%
73
+ \bibcite{credoCMO}{{2017}{CREDO}{{CREDO}}{}}%
74
+ }
75
+ \makeatother
76
+ \begin{document}
77
+ \input{main-tables}
78
+ \begin{appendices}
79
+ \input{appendix}
80
+ \end{appendices}
81
+ \end{document}
82
+ '''
83
+ if references:
84
+ wrapper=wrapper.replace(r'\AtBeginDocument{%',r'\AtBeginDocument{%'+'\n'+references)
85
+ if not any(e['section']=='appendix' for e in included):
86
+ wrapper=wrapper.replace('\\begin{appendices}\n\\input{appendix}\n\\end{appendices}', '')
87
+ if data.metadata['synthetic'].lower()=='true':
88
+ wrapper=wrapper.replace(r'\begin{document}',r'''\AddToShipoutPictureFG{\AtPageLowerLeft{\put(72,18){\makebox(0,0)[l]{\sffamily\fontsize{7}{8}\selectfont Synthetic inputs -- layout reproduced from manuscript}}}}
89
+ \begin{document}''')
90
+ mixed=data.metadata.get('synthetic','').lower()=='true'
91
+ if mixed:
92
+ wrapper=wrapper.replace('Synthetic inputs -- layout reproduced from manuscript',
93
+ 'Black table values: manuscript; red: synthetic placeholders. Plots: synthetic.')
94
+ (build_dir/'main.tex').write_text(wrapper)
95
+ engine=shutil.which('latexmk') or '/Library/TeX/texbin/latexmk'
96
+ command=[engine,'-pdf','-interaction=nonstopmode','-halt-on-error','-file-line-error','main.tex']
97
+ env=os.environ.copy();env['PATH']='/Library/TeX/texbin:'+env.get('PATH','')
98
+ result=subprocess.run(command,cwd=build_dir,env=env,capture_output=True,text=True)
99
+ (build_dir/'compile-output.txt').write_text(result.stdout+'\n'+result.stderr)
100
+ if result.returncode:
101
+ raise RuntimeError('LaTeX compilation failed:\n'+(result.stdout+result.stderr)[-4500:])
102
+ pdf=PdfReader(build_dir/'main.pdf')
103
+ text='\n'.join(p.extract_text() for p in pdf.pages)
104
+ if len(pdf.pages)!=len(included):
105
+ raise AssertionError(f'Expected {len(included)} exhibit pages, got {len(pdf.pages)}')
106
+ for page,exhibit in zip(pdf.pages,included):
107
+ if exhibit['title'].upper() not in page.extract_text():
108
+ raise AssertionError(f'Missing or renumbered exhibit: {exhibit["title"]}')
109
+ output_path.parent.mkdir(parents=True,exist_ok=True)
110
+ shutil.copyfile(build_dir/'main.pdf',output_path)
111
+ audit={'pages':len(pdf.pages),'input_sha256':{role:hashlib.sha256(path.read_bytes()).hexdigest() for role,path in data.paths.items()},
112
+ 'source_hashes':{str(p.relative_to(SOURCE)):hashlib.sha256(p.read_bytes()).hexdigest() for p in SOURCE.rglob('*.tex')},
113
+ 'derived':derived,'used_keys':[list(k) for k in sorted(data.used)],
114
+ 'input_paths':{role:str(path) for role,path in data.paths.items()},
115
+ 'binding_sha256':hashlib.sha256((HERE/'table_bindings.json').read_bytes()).hexdigest(),
116
+ 'derived_scope':'Fixed disclosed fields and separately supplied pending results',
117
+ 'included_exhibits':[e['title'] for e in included],
118
+ 'excluded_exhibits':[e['title'] for e in excluded]}
119
+ output_path.with_suffix('.audit.json').write_text(json.dumps(audit,indent=2)+'\n')
120
+ print(f'Created {output_path}: {len(pdf.pages)} pages using source LaTeX layouts')
121
+ return audit
122
+
123
+
124
+ if __name__=='__main__':
125
+ build_latex()
@@ -0,0 +1,27 @@
1
+ # --- Import necessary packages ---
2
+ import sys
3
+ from pathlib import Path
4
+
5
+ sys.path.append("/Users/williamratnavale/Projects/codex_sandbox/code")
6
+ try:
7
+ from paths import CODEX_SYNTHETIC, OUTPUTS
8
+ except ModuleNotFoundError:
9
+ # A copied folder also works on the inside without the sandbox paths module.
10
+ CODEX_SYNTHETIC = Path(__file__).resolve().parent / "data"
11
+ OUTPUTS = Path(__file__).resolve().parent / "output"
12
+
13
+ ###########################################################
14
+ # Define necessary paths
15
+ ###########################################################
16
+
17
+ HERE = Path(__file__).resolve().parent
18
+ # Replace this one path with your real workbook. The renderer never generates data.
19
+ BUNDLED_INPUT = HERE / "data" / "charter_exhibits" / "synthetic_estimates.xlsx"
20
+ INPUT_XLSX = BUNDLED_INPUT if BUNDLED_INPUT.exists() else CODEX_SYNTHETIC / "charter_exhibits" / "synthetic_estimates.xlsx"
21
+ OUTPUT_DIR = HERE / "output" if BUNDLED_INPUT.exists() else OUTPUTS / "charter_exhibits"
22
+ OUTPUT_PDF = OUTPUT_DIR / "charter_exhibits.pdf"
23
+ SOURCE_SNAPSHOT = HERE / "source_snapshot.json"
24
+
25
+ # Blank values require an explicit status, rather than becoming zero.
26
+ STATUSES = {"available", "unavailable", "not_applicable"}
27
+ PAGE_SIZE = (960, 540)
@@ -0,0 +1,52 @@
1
+ # --- Import necessary packages ---
2
+ import re
3
+
4
+ ###########################################################
5
+ # Select complete exhibits and retain their manuscript numbering
6
+ ###########################################################
7
+
8
+ FIGURES = (
9
+ 'appendix-credo-distributions',
10
+ 'appendix-college-by-age-placeholder',
11
+ 'appendix-cmo-leave-one-out',
12
+ 'appendix-earnings-by-age-firm-chars',
13
+ )
14
+ FLOAT = re.compile(r'\\begin\{(table|figure)\}.*?\\end\{\1\}',re.S)
15
+
16
+
17
+ def select_exhibits(source_root, exclude_tables, exclude_figures):
18
+ main=(source_root/'paper/tables-and-figures.tex').read_text()
19
+ main=r'\begin{spacing}{1}'+'\n'+main[main.index(r'\begin{table}'):]
20
+ appendix=(source_root/'paper/appendix.tex').read_text()
21
+ catalog=[]
22
+ for section,text in [('main',main),('appendix',appendix)]:
23
+ counters={'table':0,'figure':0}
24
+ for match in FLOAT.finditer(text):
25
+ kind=match[1];counters[kind]+=1;block=match[0]
26
+ label=re.search(r'\\label\{([^}]+)\}',block)[1]
27
+ name=re.search(r'\\input\{[^}]*?/([^/}]+)\}',block)[1] if kind=='table' else label.removeprefix('fig:')
28
+ number=('A.' if section=='appendix' else '')+str(counters[kind])
29
+ catalog.append(dict(section=section,kind=kind,name=name,label=label,number=number,
30
+ title=kind.title()+' '+number,counter=counters[kind]-1))
31
+ excluded=set()
32
+ for kind,requested in [('table',exclude_tables),('figure',exclude_figures)]:
33
+ if isinstance(requested,str):raise ValueError(f'EXCLUDE_{kind.upper()}S must be a list, not a string')
34
+ aliases={alias:e['name'] for e in catalog if e['kind']==kind for alias in [e['name'],e['title']]}
35
+ for name in requested:
36
+ if name not in aliases:raise ValueError(f'Unknown excluded {kind}: {name!r}. Choose from: {sorted(aliases)}')
37
+ excluded.add(aliases[name])
38
+ retained=[e for e in catalog if e['name'] not in excluded]
39
+ if not retained:raise ValueError('All exhibits are excluded. Include at least one table or figure.')
40
+ if excluded:
41
+ for section,text in [('main',main),('appendix',appendix)]:
42
+ entries=iter(e for e in catalog if e['section']==section)
43
+ def replace(match):
44
+ entry=next(entries)
45
+ if entry['name'] in excluded:return ''
46
+ return '\\setcounter{'+entry['kind']+'}{'+str(entry['counter'])+'}\n'+match[0]
47
+ filtered=FLOAT.sub(replace,text)
48
+ if section=='main':main=filtered
49
+ else:appendix=filtered
50
+ references='\n'.join('\\newlabel{'+e['label']+'}{{'+e['number']+'}{}{}{'+e['kind']+'.'+e['number']+'}{}}%'
51
+ for e in catalog if e['name'] in excluded)
52
+ return main,appendix,retained,[e for e in catalog if e['name'] in excluded],references
@@ -0,0 +1,82 @@
1
+ # --- Import necessary packages ---
2
+ import math
3
+ import os
4
+ import numpy as np
5
+ import matplotlib
6
+ matplotlib.use('Agg')
7
+ import matplotlib.pyplot as plt
8
+ from matplotlib.ticker import FuncFormatter
9
+ from exhibit_selection import FIGURES
10
+
11
+ ###########################################################
12
+ # Source-style plots inserted into the original LaTeX figure environments
13
+ ###########################################################
14
+
15
+ def make_figures(data, folder, selected_figures=None):
16
+ selected=set(FIGURES if selected_figures is None else selected_figures)
17
+ folder.mkdir(parents=True,exist_ok=True)
18
+ plt.rcParams.update({'font.family':'DejaVu Sans','font.size':17,'axes.labelsize':20,
19
+ 'axes.spines.top':False,'axes.spines.right':False,'axes.linewidth':.7,
20
+ 'legend.frameon':False,'savefig.facecolor':'white'})
21
+ teal='#21B5A5';orange='#FFA21A';gray='#BBBBBB'
22
+ if FIGURES[0] in selected:
23
+ vals=[];study=[]
24
+ for item in data.rows['credo']:
25
+ r=data.get('credo',item['cmo']);v=np.mean([r[k] for k in ['math_effect','reading_effect'] if r[k] is not None])
26
+ vals.append(v)
27
+ if r['in_study']:study.append(v)
28
+ vals=np.asarray(vals);study=np.asarray(study);grid=np.linspace(-.5,.5,600)
29
+ densities=[]
30
+ for x in [vals,study]:
31
+ if len(x)<2 or np.std(x,ddof=1)<=0:raise ValueError('CREDO needs two distinct values in each group')
32
+ scale=min(np.std(x,ddof=1),(np.quantile(x,.75)-np.quantile(x,.25))/1.34)
33
+ if scale<=0:scale=np.std(x,ddof=1)
34
+ bw=.9*scale*len(x)**(-.2)
35
+ densities.append(np.exp(-.5*((grid[:,None]-x[None,:])/bw)**2).mean(axis=1)/(bw*math.sqrt(2*math.pi)))
36
+ for index in [0,1]:
37
+ fig,ax=plt.subplots(figsize=(12,6))
38
+ ax.fill_between(grid,densities[0],color=gray,alpha=.85,edgecolor='#888888',linewidth=.8,label='All CMOs')
39
+ if index==0:
40
+ ax.fill_between(grid,densities[0],where=grid>=0,color=teal,alpha=.8,edgecolor='#167B73',linewidth=.8)
41
+ mean=vals[vals>=0].mean();label=f'Mean, Test-Effective Charters: {mean:+.2f} SD'
42
+ ax.text(.26,max(densities[0])*.30,'Test-Effective\nCharter Schools',ha='center',color=teal)
43
+ else:
44
+ ax.fill_between(grid,densities[1],color=teal,alpha=.8,edgecolor='#888888',linewidth=.8,label='Study CMOs')
45
+ mean=study.mean();label=f'Mean, Study CMOs: {mean:.2f}'
46
+ ax.legend(loc='upper left',fontsize=14)
47
+ ceiling=max(densities[0].max(),densities[index].max())*1.13
48
+ ax.axvline(mean,color='#167B73',lw=.9)
49
+ ax.text(mean+.01,ceiling*.95,label,color=teal,va='top',fontsize=15)
50
+ ax.set(xlim=(-.51,.52),ylim=(0,ceiling),ylabel='Density',xlabel='Mean Difference in Math + Reading Scores: Charter vs Neighborhood Public Schools (SD)')
51
+ ax.set_xticks([-.5,-.25,0,.25,.5],['-0.50','-0.25','0','0.25','0.50']);ax.set_yticks([])
52
+ ax.xaxis.label.set_size(14)
53
+ fig.tight_layout();fig.savefig(folder/f'f_distribution_slide{index+4}.png',dpi=250);plt.close(fig)
54
+ for extension in [False,True]:
55
+ if FIGURES[3 if extension else 1] not in selected:continue
56
+ fig,ax=plt.subplots(figsize=(12,6))
57
+ series=[('observed',range(18,27),teal,'Observed Earnings')]
58
+ if extension:series.append(('ein_fes',range(25,32),orange,'Surrogate Index Prediction'))
59
+ for spec,ages,color,label in series:
60
+ rec=[data.get('TOT','age_profile',f'wages{age}',spec,unit='usd') for age in ages]
61
+ coef=np.array([np.nan if r['coef'] is None else r['coef']/1000 for r in rec]);se=np.array([np.nan if r['se'] is None else r['se']/1000 for r in rec])
62
+ ax.plot(list(ages),coef,'o-',color=color,lw=1.4,ms=7,label=label)
63
+ ax.plot(list(ages),coef+1.96*se,ls=(0,(1,3)),color=color,lw=2)
64
+ ax.plot(list(ages),coef-1.96*se,ls=(0,(1,3)),color=color,lw=2)
65
+ ax.axhline(0,color='#888888',ls=(0,(3,3)),lw=.8)
66
+ ax.set(xlabel='Age',ylabel='Local Average Treatment Effect on\nEarnings')
67
+ ax.set_xticks(list(range(18,32 if extension else 27)))
68
+ ax.yaxis.set_major_formatter(FuncFormatter(lambda v,p:f'${v:.1f}K'))
69
+ if extension:ax.legend(loc='lower right',fontsize=16)
70
+ fig.tight_layout();fig.savefig(folder/('tot_overall_wages_by_age_surr.png' if extension else 'tot_overall_wages_by_age.png'),dpi=250);plt.close(fig)
71
+ if FIGURES[2] not in selected:return
72
+ rec=sorted([data.get('TOT_by_cmo',r['sample'],r['depvar'],r['spec'],unit='usd') for r in data.rows['TOT_by_cmo'] if r['depvar']=='wages25' and r['spec']=='baseline'],key=lambda r:r['sample'])
73
+ if len(rec)<3 or any(r['status']!='available' or r['se']<=0 for r in rec):raise ValueError('LOO requires complete network estimates and positive SE')
74
+ values=np.array([r['coef'] for r in rec]);weights=1/np.array([r['se'] for r in rec])**2
75
+ loo=(sum(values*weights)-values*weights)/(sum(weights)-weights)
76
+ fig,ax=plt.subplots(figsize=(12,6));ax.bar(range(len(rec)),loo/1000,color=teal,width=.75)
77
+ overall=data.get('figure_reference','overall_25plus','wages25','baseline',unit='usd')
78
+ if overall['coef'] is not None:ax.axhline(overall['coef']/1000,color='#777777',ls='--',lw=1)
79
+ ax.set_xticks(range(len(rec)),[r['sample'] for r in rec],rotation=45,ha='right',fontsize=12)
80
+ ax.set(xlabel='Omitted Charter Network',ylabel='Local Average Treatment Effect on\nEarnings at Age 25')
81
+ ax.yaxis.set_major_formatter(FuncFormatter(lambda v,p:f'${v:.1f}K'))
82
+ fig.tight_layout();fig.savefig(folder/'tot_cmo_loo_bar_wages25.png',dpi=250);plt.close(fig)
@@ -0,0 +1,7 @@
1
+ polars==1.42.1
2
+ fastexcel==0.20.2
3
+ openpyxl==3.1.5
4
+ numpy>=2.0,<3
5
+ matplotlib==3.11.2
6
+ pypdf==6.19.0
7
+ pillow==12.3.0
@@ -0,0 +1,77 @@
1
+ % Table layout backed by outputs/tables/input/age25_earnings_table_input.csv.
2
+ \DTLloaddb
3
+ [keys={column,coefficient,control_mean,ccm,se}]
4
+ {ageearningsdata}
5
+ {outputs/tables/input/age25_earnings_table_input.csv}
6
+
7
+ \setlength{\tabcolsep}{3pt}
8
+ \begin{tabular}{L{3.5cm} *{7}{C{1.55cm}}}
9
+ \toprule
10
+ & \shortstack{First\\Stage}
11
+ & \shortstack{Earnings\\ITT}
12
+ & \shortstack{Earnings\\LATE}
13
+ & \shortstack{Earnings\\LATE with\\Controls}
14
+ & \shortstack{OLS with\\Tract FEs}
15
+ & AGI
16
+ & \shortstack{Median\\Poverty\\Rate} \\
17
+ & (1) & (2) & (3) & (4) & (5) & (6) & (7) \\
18
+ \midrule
19
+
20
+ Offered vs. Control
21
+ & \DTLfetch{ageearningsdata}{column}{first_stage}{coefficient}
22
+ & \DTLfetch{ageearningsdata}{column}{itt}{coefficient}
23
+ &
24
+ &
25
+ &
26
+ &
27
+ & \\
28
+
29
+ & \DTLfetch{ageearningsdata}{column}{itt}{se}
30
+ & \DTLfetch{ageearningsdata}{column}{first_stage}{se}
31
+ &
32
+ &
33
+ &
34
+ &
35
+ & \\
36
+
37
+ Control Mean
38
+ & \DTLfetch{ageearningsdata}{column}{first_stage}{control_mean}
39
+ & \DTLfetch{ageearningsdata}{column}{itt}{control_mean}
40
+ &
41
+ &
42
+ &
43
+ &
44
+ & \\
45
+
46
+ Enrolled vs. Control
47
+ &
48
+ &
49
+ & \DTLfetch{ageearningsdata}{column}{late}{coefficient}
50
+ & \DTLfetch{ageearningsdata}{column}{late_controls}{coefficient}
51
+ & \DTLfetch{ageearningsdata}{column}{ols_tract_fe}{coefficient}
52
+ & --
53
+ & -- \\
54
+
55
+ &
56
+ &
57
+ & \DTLfetch{ageearningsdata}{column}{late}{se}
58
+ & \DTLfetch{ageearningsdata}{column}{late_controls}{se}
59
+ & \DTLfetch{ageearningsdata}{column}{ols_tract_fe}{se}
60
+ &
61
+ & \\
62
+
63
+ Control Complier Mean
64
+ &
65
+ &
66
+ & \DTLfetch{ageearningsdata}{column}{late}{ccm}
67
+ & \DTLfetch{ageearningsdata}{column}{late_controls}{ccm}
68
+ &
69
+ & --
70
+ & -- \\
71
+
72
+ \bottomrule
73
+ \end{tabular}
74
+
75
+ % Notes: Columns (1), (3)--(7) report dollars; column (2) reports percentage
76
+ % points. Standard errors are in parentheses. *, **, and *** denote
77
+ % significance at the 10, 5, and 1 percent levels, respectively.
@@ -0,0 +1,68 @@
1
+ % Table layout backed by outputs/tables/input/balance_table_input.csv.
2
+ % Edit labels, row order, panels, and column structure here.
3
+ \DTLloaddb
4
+ [keys={depvar_sample,control_mean,coefficient,se}]
5
+ {balancedata}
6
+ {outputs/tables/input/balance_table_input.csv}
7
+
8
+ \begin{tabular}{L{6.4cm} *{4}{C{2.2cm}}}
9
+ \toprule
10
+ & \multicolumn{2}{c}{Age 25+ Sample}
11
+ & \multicolumn{2}{c}{Age 20+ Sample} \\
12
+ \cmidrule(lr){2-3}\cmidrule(lr){4-5}
13
+ & Control Group Mean & Offered vs. Control
14
+ & Control Group Mean & Offered vs. Control \\
15
+ & (1) & (2) & (3) & (4) \\
16
+ \midrule
17
+
18
+ \hspace{0.1cm}Linked to tax data (\%) & -- & -- & -- & -- \\
19
+
20
+ \addlinespace
21
+ \textit{A. Demographic Characteristics} & & & & \\
22
+ \hspace{0.1cm}Black (\%)
23
+ & \DTLfetch{balancedata}{depvar_sample}{black_overall_25plus}{control_mean}
24
+ & \DTLfetch{balancedata}{depvar_sample}{black_overall_25plus}{coefficient}
25
+ & -- & -- \\
26
+ & & (\DTLfetch{balancedata}{depvar_sample}{black_overall_25plus}{se}) & & \\
27
+ \hspace{0.1cm}Hispanic (\%)
28
+ & \DTLfetch{balancedata}{depvar_sample}{hispanic_overall_25plus}{control_mean}
29
+ & \DTLfetch{balancedata}{depvar_sample}{hispanic_overall_25plus}{coefficient}
30
+ & -- & -- \\
31
+ & & (\DTLfetch{balancedata}{depvar_sample}{hispanic_overall_25plus}{se}) & & \\
32
+ \hspace{0.1cm}White (\%)
33
+ & \DTLfetch{balancedata}{depvar_sample}{white_overall_25plus}{control_mean}
34
+ & \DTLfetch{balancedata}{depvar_sample}{white_overall_25plus}{coefficient}
35
+ & -- & -- \\
36
+ & & (\DTLfetch{balancedata}{depvar_sample}{white_overall_25plus}{se}) & & \\
37
+ \hspace{0.1cm}Female (\%)
38
+ & \DTLfetch{balancedata}{depvar_sample}{sex_overall_25plus}{control_mean}
39
+ & \DTLfetch{balancedata}{depvar_sample}{sex_overall_25plus}{coefficient}
40
+ & -- & -- \\
41
+ & & (\DTLfetch{balancedata}{depvar_sample}{sex_overall_25plus}{se}) & & \\
42
+
43
+ \addlinespace
44
+ \textit{B. Family and Neighborhood Characteristics} & & & & \\
45
+ \hspace{0.1cm}Parent income rank (percentile)
46
+ & \DTLfetch{balancedata}{depvar_sample}{parent_inc_rank_overall_25plus}{control_mean}
47
+ & \DTLfetch{balancedata}{depvar_sample}{parent_inc_rank_overall_25plus}{coefficient}
48
+ & -- & -- \\
49
+ & & (\DTLfetch{balancedata}{depvar_sample}{parent_inc_rank_overall_25plus}{se}) & & \\
50
+ \hspace{0.1cm}Median tract household income (\$)
51
+ & \DTLfetch{balancedata}{depvar_sample}{med_hhinc2000_overall_25plus}{control_mean}
52
+ & \DTLfetch{balancedata}{depvar_sample}{med_hhinc2000_overall_25plus}{coefficient}
53
+ & -- & -- \\
54
+ & & (\DTLfetch{balancedata}{depvar_sample}{med_hhinc2000_overall_25plus}{se}) & & \\
55
+ \hspace{0.1cm}Predicted age 25 earnings (\$)
56
+ & \DTLfetch{balancedata}{depvar_sample}{wages25_pre_treatment_overall_25plus}{control_mean}
57
+ & \DTLfetch{balancedata}{depvar_sample}{wages25_pre_treatment_overall_25plus}{coefficient}
58
+ & -- & -- \\
59
+ & & (\DTLfetch{balancedata}{depvar_sample}{wages25_pre_treatment_overall_25plus}{se}) & & \\
60
+
61
+ \addlinespace
62
+ Number of applicants
63
+ &
64
+ & \DTLfetch{balancedata}{depvar_sample}{n_ppl_overall_25plus}{control_mean}
65
+ & & -- \\
66
+
67
+ \bottomrule
68
+ \end{tabular}