From a769ff02e9e70ea1afbe124c80922610165f1871 Mon Sep 17 00:00:00 2001 From: Nicholas Karlson Date: Thu, 30 Jul 2026 10:37:49 -0700 Subject: [PATCH] Drop-in 033: build psych-design companion v0.1 release candidate --- .github/workflows/ci.yml | 61 +- Makefile | 22 + README.md | 28 + ...SYCH_DESIGN_COMPANION_RELEASE_CANDIDATE.md | 96 ++ docs/source/release_notes.rst | 31 + .../COMMAND_INTERFACE_CONTRACT.json | 14 +- .../COMPANION_RELEASE_CONTRACT.json | 13 +- .../CROSS_REPOSITORY_GAP_MATRIX.json | 54 +- .../TARGET_STUDY_INDEX.json | 626 +++++++++-- .../COMPANION_PROVENANCE.json | 13 + .../LICENSE_NOTES.md | 9 + .../Makefile | 47 + .../README.md | 38 + .../SOURCE_LINEAGE.json | 280 +++++ .../STUDY_INDEX.json | 695 ++++++++++++ .../VERIFICATION_POLICY.json | 346 ++++++ .../swl_s01_student_well_being_survey.csv | 121 +++ .../data/swl_s02_structured_study_routine.csv | 65 ++ .../data/swl_s03_skills_workshop_pre_post.csv | 97 ++ .../data/swl_s04_three_condition_support.csv | 91 ++ .../data/swl_s05_strategy_feedback.csv | 97 ++ .../data/swl_s06_confidence_trajectory.csv | 181 ++++ ...swl_s07_baseline_adjusted_intervention.csv | 91 ++ .../data/swl_s08_longitudinal_treatment.csv | 169 +++ ...swl_s09_outcome_distribution_extension.csv | 161 +++ .../data/swl_s10_replication_program.csv | 7 + .../design/SWL-S01_DESIGN_CONTRACT.json | 34 + .../design/SWL-S02_DESIGN_CONTRACT.json | 32 + .../design/SWL-S03_DESIGN_CONTRACT.json | 33 + .../design/SWL-S04_DESIGN_CONTRACT.json | 33 + .../design/SWL-S05_DESIGN_CONTRACT.json | 36 + .../design/SWL-S06_DESIGN_CONTRACT.json | 34 + .../design/SWL-S07_DESIGN_CONTRACT.json | 36 + .../design/SWL-S08_DESIGN_CONTRACT.json | 35 + .../design/SWL-S09_DESIGN_CONTRACT.json | 28 + .../design/SWL-S10_DESIGN_CONTRACT.json | 23 + .../SWL-S01/FIGURE_SPEC_CORRELATION.json | 15 + .../specs/SWL-S01/FIGURE_SPEC_REGRESSION.json | 15 + .../figures/specs/SWL-S02/FIGURE_SPEC.json | 13 + .../figures/specs/SWL-S03/FIGURE_SPEC.json | 13 + .../figures/specs/SWL-S04/FIGURE_SPEC.json | 13 + .../figures/specs/SWL-S05/FIGURE_SPEC.json | 13 + .../figures/specs/SWL-S06/FIGURE_SPEC.json | 14 + .../figures/specs/SWL-S07/FIGURE_SPEC.json | 14 + .../figures/specs/SWL-S08/FIGURE_SPEC.json | 14 + .../SWL-S09/FIGURE_SPEC_CATEGORICAL.json | 14 + .../specs/SWL-S09/FIGURE_SPEC_RANK.json | 14 + .../figures/specs/SWL-S10/FIGURE_SPEC.json | 14 + .../measurement/SWL-S01_MEASUREMENT_MAP.json | 35 + .../measurement/SWL-S02_MEASUREMENT_MAP.json | 31 + .../measurement/SWL-S03_MEASUREMENT_MAP.json | 32 + .../measurement/SWL-S04_MEASUREMENT_MAP.json | 33 + .../measurement/SWL-S05_MEASUREMENT_MAP.json | 36 + .../measurement/SWL-S06_MEASUREMENT_MAP.json | 33 + .../measurement/SWL-S07_MEASUREMENT_MAP.json | 35 + .../measurement/SWL-S08_MEASUREMENT_MAP.json | 45 + .../measurement/SWL-S09_MEASUREMENT_MAP.json | 38 + .../measurement/SWL-S10_MEASUREMENT_MAP.json | 34 + .../APA_RESULT_SOURCE_MAP_CORRELATION.json | 38 + .../APA_RESULT_SOURCE_MAP_REGRESSION.json | 70 ++ .../SWL-S02/APA_RESULT_SOURCE_MAP.json | 53 + .../SWL-S03/APA_RESULT_SOURCE_MAP.json | 57 + .../SWL-S04/APA_RESULT_SOURCE_MAP.json | 57 + .../SWL-S05/APA_RESULT_SOURCE_MAP.json | 61 ++ .../SWL-S06/APA_RESULT_SOURCE_MAP.json | 61 ++ .../SWL-S07/APA_RESULT_SOURCE_MAP.json | 53 + .../SWL-S08/APA_RESULT_SOURCE_MAP.json | 58 + .../APA_RESULT_SOURCE_MAP_CATEGORICAL.json | 34 + .../SWL-S09/APA_RESULT_SOURCE_MAP_RANK.json | 34 + .../SWL-S10/APA_RESULT_SOURCE_MAP_META.json | 54 + .../SWL-S10/APA_RESULT_SOURCE_MAP_POWER.json | 26 + .../MATCHED_LIMITATION_CORRELATION.json | 12 + .../MATCHED_LIMITATION_REGRESSION.json | 12 + .../SWL-S02/MATCHED_LIMITATION.json | 10 + .../SWL-S03/MATCHED_LIMITATION.json | 10 + .../SWL-S04/MATCHED_LIMITATION.json | 10 + .../SWL-S05/MATCHED_LIMITATION.json | 10 + .../SWL-S06/MATCHED_LIMITATION.json | 11 + .../SWL-S07/MATCHED_LIMITATION.json | 11 + .../SWL-S08/MATCHED_LIMITATION.json | 7 + .../MATCHED_LIMITATION_CATEGORICAL.json | 7 + .../SWL-S09/MATCHED_LIMITATION_RANK.json | 7 + .../SWL-S10/MATCHED_LIMITATION_META.json | 7 + .../SWL-S10/MATCHED_LIMITATION_POWER.json | 7 + .../SWL-S01_PYTHON_RESULT_RECEIPT.json | 53 + .../SWL-S02_PYTHON_RESULT_RECEIPT.json | 29 + .../SWL-S03_PYTHON_RESULT_RECEIPT.json | 27 + .../SWL-S04_PYTHON_RESULT_RECEIPT.json | 36 + .../SWL-S05_PYTHON_RESULT_RECEIPT.json | 40 + .../SWL-S06_PYTHON_RESULT_RECEIPT.json | 35 + .../SWL-S07_PYTHON_RESULT_RECEIPT.json | 44 + .../SWL-S08_PYTHON_RESULT_RECEIPT.json | 68 ++ .../SWL-S09_PYTHON_RESULT_RECEIPT.json | 40 + .../SWL-S10_PYTHON_RESULT_RECEIPT.json | 34 + .../SWL-S08/PYTHON_R_COMPARISON_POLICY.json | 25 + ...7_v0_1_PYTHON_R_VERIFICATION_CONTRACT.json | 117 ++ ...5_v0_1_PYTHON_R_VERIFICATION_CONTRACT.json | 104 ++ ...0_v0_1_PYTHON_R_VERIFICATION_CONTRACT.json | 120 +++ .../requirements-psych-design-companion.txt | 5 + .../scripts/__init__.py | 0 .../scripts/python/__init__.py | 1 + .../python/analysis_batches/__init__.py | 0 .../analysis_batches/batch_swl_s01_s06_s07.py | 738 +++++++++++++ .../analysis_batches/batch_swl_s02_s05.py | 999 ++++++++++++++++++ .../analysis_batches/batch_swl_s08_s10.py | 967 +++++++++++++++++ .../scripts/python/build_reviewer_packet.py | 140 +++ .../scripts/python/companion_runtime.py | 115 ++ .../scripts/python/design_audit.py | 16 + .../scripts/python/generate_figures.py | 158 +++ .../scripts/python/list_studies.py | 5 + .../scripts/python/run_all.py | 6 + .../scripts/python/run_study.py | 9 + .../swl_s01_survey_association_prediction.py | 14 + .../python/swl_s02_independent_groups.py | 14 + .../scripts/python/swl_s03_paired_change.py | 14 + .../python/swl_s04_planned_contrasts.py | 14 + .../python/swl_s05_factorial_interaction.py | 14 + .../python/swl_s06_repeated_measures.py | 14 + .../scripts/python/swl_s07_ancova.py | 14 + .../scripts/python/swl_s08_mixed_model.py | 14 + .../python/swl_s09_distribution_extension.py | 14 + .../python/swl_s10_power_replication.py | 14 + .../r/batches/swl_s01_s06_s07_v0_1/common.R | 34 + .../r/batches/swl_s01_s06_s07_v0_1/swl_s01.R | 47 + .../r/batches/swl_s01_s06_s07_v0_1/swl_s06.R | 57 + .../r/batches/swl_s01_s06_s07_v0_1/swl_s07.R | 55 + .../r/batches/swl_s02_s05_v0_1/common.R | 31 + .../r/batches/swl_s02_s05_v0_1/swl_s02.R | 30 + .../r/batches/swl_s02_s05_v0_1/swl_s03.R | 27 + .../r/batches/swl_s02_s05_v0_1/swl_s04.R | 90 ++ .../r/batches/swl_s02_s05_v0_1/swl_s05.R | 143 +++ .../r/batches/swl_s08_s10_v0_1/common.R | 19 + .../r/batches/swl_s08_s10_v0_1/swl_s08.R | 62 ++ .../r/batches/swl_s08_s10_v0_1/swl_s09.R | 64 ++ .../r/batches/swl_s08_s10_v0_1/swl_s10.R | 52 + .../scripts/r/run_all.R | 9 + .../scripts/r/run_study.R | 30 + .../scripts/verification/__init__.py | 1 + .../scripts/verification/verify_all.py | 6 + .../scripts/verification/verify_study.py | 56 + .../scripts/verification/verify_swl_s01.py | 4 + .../scripts/verification/verify_swl_s02.py | 4 + .../scripts/verification/verify_swl_s03.py | 4 + .../scripts/verification/verify_swl_s04.py | 4 + .../scripts/verification/verify_swl_s05.py | 4 + .../scripts/verification/verify_swl_s06.py | 4 + .../scripts/verification/verify_swl_s07.py | 4 + .../scripts/verification/verify_swl_s08.py | 4 + .../scripts/verification/verify_swl_s09.py | 4 + .../scripts/verification/verify_swl_s10.py | 4 + .../templates/ADAPTATION_DELTA_LEDGER.yaml | 12 + .../templates/ANALYSIS_PLAN.yaml | 6 + .../templates/CLAIM_MAP.yaml | 4 + .../templates/DATA_LAYOUT_CONTRACT.yaml | 7 + .../templates/MATCHED_LIMITATION.md | 3 + .../templates/MEASUREMENT_MAP.yaml | 5 + .../templates/REPORTING_SOURCE_MAP.yaml | 4 + .../templates/STUDY_MAP.yaml | 6 + .../templates/VERIFICATION_CONTRACT.yaml | 5 + .../tests/test_companion_contract.py | 12 + pyproject.toml | 11 +- ...al_statistics_by_design_companion_v0_1.zip | Bin 0 -> 136801 bytes src/pystatsv1/cli.py | 146 +++ src/pystatsv1/psych_design.py | 270 +++++ tests/test_book1_launcher.py | 2 +- tests/test_psych_design_release_candidate.py | 366 +++++++ tests/test_psych_design_release_contract.py | 7 +- tests/test_release_025_metadata.py | 7 +- tools/build_psych_design_companion_asset.py | 211 ++++ tools/check_psych_design_release_candidate.py | 339 ++++++ tools/check_psych_design_release_contract.py | 233 ++-- 171 files changed, 11436 insertions(+), 216 deletions(-) create mode 100644 docs/PYSTATSV1_DROPIN_033_UNIFIED_PSYCH_DESIGN_COMPANION_RELEASE_CANDIDATE.md create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/COMPANION_PROVENANCE.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/LICENSE_NOTES.md create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/Makefile create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/README.md create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/SOURCE_LINEAGE.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/STUDY_INDEX.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/VERIFICATION_POLICY.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s01_student_well_being_survey.csv create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s02_structured_study_routine.csv create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s03_skills_workshop_pre_post.csv create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s04_three_condition_support.csv create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s05_strategy_feedback.csv create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s06_confidence_trajectory.csv create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s07_baseline_adjusted_intervention.csv create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s08_longitudinal_treatment.csv create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s09_outcome_distribution_extension.csv create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s10_replication_program.csv create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S01_DESIGN_CONTRACT.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S02_DESIGN_CONTRACT.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S03_DESIGN_CONTRACT.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S04_DESIGN_CONTRACT.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S05_DESIGN_CONTRACT.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S06_DESIGN_CONTRACT.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S07_DESIGN_CONTRACT.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S08_DESIGN_CONTRACT.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S09_DESIGN_CONTRACT.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S10_DESIGN_CONTRACT.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S01/FIGURE_SPEC_CORRELATION.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S01/FIGURE_SPEC_REGRESSION.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S02/FIGURE_SPEC.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S03/FIGURE_SPEC.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S04/FIGURE_SPEC.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S05/FIGURE_SPEC.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S06/FIGURE_SPEC.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S07/FIGURE_SPEC.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S08/FIGURE_SPEC.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S09/FIGURE_SPEC_CATEGORICAL.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S09/FIGURE_SPEC_RANK.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S10/FIGURE_SPEC.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S01_MEASUREMENT_MAP.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S02_MEASUREMENT_MAP.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S03_MEASUREMENT_MAP.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S04_MEASUREMENT_MAP.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S05_MEASUREMENT_MAP.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S06_MEASUREMENT_MAP.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S07_MEASUREMENT_MAP.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S08_MEASUREMENT_MAP.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S09_MEASUREMENT_MAP.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S10_MEASUREMENT_MAP.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S01/APA_RESULT_SOURCE_MAP_CORRELATION.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S01/APA_RESULT_SOURCE_MAP_REGRESSION.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S02/APA_RESULT_SOURCE_MAP.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S03/APA_RESULT_SOURCE_MAP.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S04/APA_RESULT_SOURCE_MAP.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S05/APA_RESULT_SOURCE_MAP.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S06/APA_RESULT_SOURCE_MAP.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S07/APA_RESULT_SOURCE_MAP.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S08/APA_RESULT_SOURCE_MAP.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S09/APA_RESULT_SOURCE_MAP_CATEGORICAL.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S09/APA_RESULT_SOURCE_MAP_RANK.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S10/APA_RESULT_SOURCE_MAP_META.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S10/APA_RESULT_SOURCE_MAP_POWER.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S01/MATCHED_LIMITATION_CORRELATION.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S01/MATCHED_LIMITATION_REGRESSION.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S02/MATCHED_LIMITATION.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S03/MATCHED_LIMITATION.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S04/MATCHED_LIMITATION.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S05/MATCHED_LIMITATION.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S06/MATCHED_LIMITATION.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S07/MATCHED_LIMITATION.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S08/MATCHED_LIMITATION.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S09/MATCHED_LIMITATION_CATEGORICAL.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S09/MATCHED_LIMITATION_RANK.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S10/MATCHED_LIMITATION_META.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S10/MATCHED_LIMITATION_POWER.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S01_PYTHON_RESULT_RECEIPT.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S02_PYTHON_RESULT_RECEIPT.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S03_PYTHON_RESULT_RECEIPT.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S04_PYTHON_RESULT_RECEIPT.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S05_PYTHON_RESULT_RECEIPT.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S06_PYTHON_RESULT_RECEIPT.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S07_PYTHON_RESULT_RECEIPT.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S08_PYTHON_RESULT_RECEIPT.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S09_PYTHON_RESULT_RECEIPT.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S10_PYTHON_RESULT_RECEIPT.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/verification/SWL-S08/PYTHON_R_COMPARISON_POLICY.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/verification/swl_s01_s06_s07_v0_1_PYTHON_R_VERIFICATION_CONTRACT.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/verification/swl_s02_s05_v0_1_PYTHON_R_VERIFICATION_CONTRACT.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/verification/swl_s08_s10_v0_1_PYTHON_R_VERIFICATION_CONTRACT.json create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/requirements-psych-design-companion.txt create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/__init__.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/__init__.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/analysis_batches/__init__.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/analysis_batches/batch_swl_s01_s06_s07.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/analysis_batches/batch_swl_s02_s05.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/analysis_batches/batch_swl_s08_s10.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/build_reviewer_packet.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/companion_runtime.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/design_audit.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/generate_figures.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/list_studies.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/run_all.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/run_study.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s01_survey_association_prediction.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s02_independent_groups.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s03_paired_change.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s04_planned_contrasts.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s05_factorial_interaction.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s06_repeated_measures.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s07_ancova.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s08_mixed_model.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s09_distribution_extension.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s10_power_replication.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s01_s06_s07_v0_1/common.R create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s01_s06_s07_v0_1/swl_s01.R create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s01_s06_s07_v0_1/swl_s06.R create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s01_s06_s07_v0_1/swl_s07.R create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s02_s05_v0_1/common.R create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s02_s05_v0_1/swl_s02.R create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s02_s05_v0_1/swl_s03.R create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s02_s05_v0_1/swl_s04.R create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s02_s05_v0_1/swl_s05.R create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s08_s10_v0_1/common.R create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s08_s10_v0_1/swl_s08.R create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s08_s10_v0_1/swl_s09.R create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s08_s10_v0_1/swl_s10.R create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/run_all.R create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/run_study.R create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/__init__.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_all.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_study.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s01.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s02.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s03.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s04.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s05.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s06.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s07.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s08.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s09.py create mode 100755 psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s10.py create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/ADAPTATION_DELTA_LEDGER.yaml create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/ANALYSIS_PLAN.yaml create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/CLAIM_MAP.yaml create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/DATA_LAYOUT_CONTRACT.yaml create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/MATCHED_LIMITATION.md create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/MEASUREMENT_MAP.yaml create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/REPORTING_SOURCE_MAP.yaml create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/STUDY_MAP.yaml create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/VERIFICATION_CONTRACT.yaml create mode 100644 psych_design_companion/psychological_statistics_by_design_companion_v0_1/tests/test_companion_contract.py create mode 100644 src/pystatsv1/assets/psychological_statistics_by_design_companion_v0_1.zip create mode 100644 src/pystatsv1/psych_design.py create mode 100644 tests/test_psych_design_release_candidate.py create mode 100755 tools/build_psych_design_companion_asset.py create mode 100755 tools/check_psych_design_release_candidate.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c134854..6e31753 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,10 +67,11 @@ jobs: with: python-version: "3.10" - - name: Build wheel + - name: Build deterministic companion assets and wheel run: | python -m pip install -U pip - python -m pip install build + python -m pip install build pandas + python tools/build_psych_design_companion_asset.py python -m build --wheel - name: Install wheel + smoke test (posix) @@ -85,6 +86,15 @@ jobs: python -c "from importlib import resources; p = resources.files('pystatsv1.assets') / 'workbook_starter.zip'; assert p.is_file(), p" python -c "from importlib import resources; p = resources.files('pystatsv1.assets') / 'psych_stats_with_python_companion_v0_2.zip'; assert p.is_file(), p" python -c "from importlib import resources; p = resources.files('pystatsv1.assets') / 'psych_stats_with_python_companion_v0_2_1.zip'; assert p.is_file(), p" + python -c "from importlib import resources; p = resources.files('pystatsv1.assets') / 'psychological_statistics_by_design_companion_v0_1.zip'; assert p.is_file(), p" + pystatsv1 psych-design info + pystatsv1 psych-design list-studies | grep SWL-S10 + pystatsv1 psych-design init --dest /tmp/psych_design_smoke + pystatsv1 psych-design verify --dest /tmp/psych_design_smoke + pystatsv1 psych-design doctor --dest /tmp/psych_design_smoke + python /tmp/psych_design_smoke/scripts/python/design_audit.py --check-only + python /tmp/psych_design_smoke/scripts/python/swl_s02_independent_groups.py + test -f /tmp/psych_design_smoke/outputs/swl_s02/python_result.json pystatsv1 book1 init --dest /tmp/book1_smoke pystatsv1 book1 verify --dest /tmp/book1_smoke test -f /tmp/book1_smoke/scripts/python/generate_figures.py @@ -114,6 +124,15 @@ jobs: .\wheeltest\Scripts\python -c "from importlib import resources; p = resources.files('pystatsv1.assets') / 'workbook_starter.zip'; assert p.is_file(), p" .\wheeltest\Scripts\python -c "from importlib import resources; p = resources.files('pystatsv1.assets') / 'psych_stats_with_python_companion_v0_2.zip'; assert p.is_file(), p" .\wheeltest\Scripts\python -c "from importlib import resources; p = resources.files('pystatsv1.assets') / 'psych_stats_with_python_companion_v0_2_1.zip'; assert p.is_file(), p" + .\wheeltest\Scripts\python -c "from importlib import resources; p = resources.files('pystatsv1.assets') / 'psychological_statistics_by_design_companion_v0_1.zip'; assert p.is_file(), p" + .\wheeltest\Scripts\pystatsv1.exe psych-design info + .\wheeltest\Scripts\pystatsv1.exe psych-design list-studies + .\wheeltest\Scripts\pystatsv1.exe psych-design init --dest psych_design_smoke + .\wheeltest\Scripts\pystatsv1.exe psych-design verify --dest psych_design_smoke + .\wheeltest\Scripts\pystatsv1.exe psych-design doctor --dest psych_design_smoke + .\wheeltest\Scripts\python psych_design_smoke\scripts\python\design_audit.py --check-only + .\wheeltest\Scripts\python psych_design_smoke\scripts\python\swl_s02_independent_groups.py + if (!(Test-Path psych_design_smoke\outputs\swl_s02\python_result.json)) { throw 'Missing psych-design SWL-S02 result' } .\wheeltest\Scripts\pystatsv1.exe book1 init --dest book1_smoke .\wheeltest\Scripts\pystatsv1.exe book1 verify --dest book1_smoke if (!(Test-Path book1_smoke\scripts\python\generate_figures.py)) { throw 'Missing Book 1 figure generator' } @@ -133,6 +152,44 @@ jobs: .\wheeltest\Scripts\pystatsv1.exe workbook check ch10 --workdir wb_smoke + psych-design-companion-release-candidate-smoke: + name: psych-design-companion-release-candidate-smoke (ubuntu) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Build wheel and run clean Python release-candidate proof + run: | + set -euo pipefail + python -m pip install -U pip build pandas + python tools/build_psych_design_companion_asset.py + python tools/check_psych_design_release_contract.py + python tools/check_psych_design_release_candidate.py + python -m build --wheel + python -m venv /tmp/psych-design-release-smoke + source /tmp/psych-design-release-smoke/bin/activate + python -m pip install -U pip + WHEEL=$(ls dist/*.whl) + python -m pip install "${WHEEL}[psych-design]" + pystatsv1 psych-design info + pystatsv1 psych-design init --dest /tmp/psych-design-public + pystatsv1 psych-design verify --dest /tmp/psych-design-public + pystatsv1 psych-design list-studies | grep SWL-S10 + pystatsv1 psych-design doctor --dest /tmp/psych-design-public + make -C /tmp/psych-design-public design-audit + make -C /tmp/psych-design-public all-python + make -C /tmp/psych-design-public figures + test -f /tmp/psych-design-public/outputs/swl_s02/python_result.json + test -f /tmp/psych-design-public/outputs/swl_s08/python_result.json + test -f /tmp/psych-design-public/outputs/swl_s10/python_result.json + test -f /tmp/psych-design-public/outputs/swl_s02/figures/FIGURE_MANIFEST.json + pystatsv1 psych-design verify --dest /tmp/psych-design-public + book1-companion-release-smoke: name: book1-companion-release-smoke (ubuntu) runs-on: ubuntu-latest diff --git a/Makefile b/Makefile index 9c5ce13..7681bfd 100644 --- a/Makefile +++ b/Makefile @@ -510,6 +510,28 @@ business-ch23: $(PYTHON) -m scripts.business_ch23_communicating_results_governance --datadir $(OUT_NSO_V1) --outdir $(OUT_TRACK_D) --seed $(SEED) + +# --- Psychological Statistics by Design companion release candidate --- +PSYCH_DESIGN_SOURCE := psych_design_companion/psychological_statistics_by_design_companion_v0_1 +PSYCH_DESIGN_ASSET := src/pystatsv1/assets/psychological_statistics_by_design_companion_v0_1.zip + +.PHONY: psych-design-companion-build +psych-design-companion-build: + $(PYTHON) tools/build_psych_design_companion_asset.py + +.PHONY: psych-design-companion-check +psych-design-companion-check: psych-design-companion-build + $(PYTHON) tools/check_psych_design_release_contract.py + $(PYTHON) tools/check_psych_design_release_candidate.py + +.PHONY: psych-design-companion-python-proof +psych-design-companion-python-proof: psych-design-companion-build + $(PYTHON) $(PSYCH_DESIGN_SOURCE)/scripts/python/design_audit.py --check-only + $(PYTHON) $(PSYCH_DESIGN_SOURCE)/scripts/python/run_all.py + $(PYTHON) $(PSYCH_DESIGN_SOURCE)/scripts/python/generate_figures.py + $(PYTHON) -m pytest -q $(PSYCH_DESIGN_SOURCE)/tests/test_companion_contract.py + rm -rf $(PSYCH_DESIGN_SOURCE)/outputs + # --- Quality gates --- .PHONY: lint lint: diff --git a/README.md b/README.md index c65a7a6..1ce87e2 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,34 @@ python scripts/psych_ch10_problem_set.py pytest -q ``` +## Psychological Statistics by Design companion — v0.26.0 release candidate + +The source tree now contains a **non-public release candidate** for +*Psychological Statistics by Design — Executable Companion v0.1*. It unifies +ten synthetic Student Well-Being and Learning studies behind one registry, ten +visible Python entry scripts, independent R implementations, verification +receipts, figure specifications, reviewer packets, and synthetic-safe adaptation +templates. PyStatsV1 remains the versioned bridge and launcher; it does not +choose a method, validate an instrument, authorize data, or establish causality. + +The candidate interface is implemented for local and CI proof: + +```bash +python -m pip install -e '.[psych-design]' +python tools/build_psych_design_companion_asset.py +pystatsv1 psych-design info +pystatsv1 psych-design init +cd psychological_statistics_by_design_companion_v0_1 +pystatsv1 psych-design verify --dest . +python -m pip install -r requirements-psych-design-companion.txt +make first-result # complete SWL-S02 Python/R V2 chain; requires Rscript +``` + +This is **not yet a PyPI or portal reader route**. Public release, portal +deployment, book binding, and real-data use remain unauthorized. The current +public foundations companion continues to use the separately pinned Book 1 +route below. + ## Psych Stats with Python — Book 1 companion PyStatsV1 v0.25.2 packages Companion v0.2.1, the corrected synthetic-only diff --git a/docs/PYSTATSV1_DROPIN_033_UNIFIED_PSYCH_DESIGN_COMPANION_RELEASE_CANDIDATE.md b/docs/PYSTATSV1_DROPIN_033_UNIFIED_PSYCH_DESIGN_COMPANION_RELEASE_CANDIDATE.md new file mode 100644 index 0000000..7070a35 --- /dev/null +++ b/docs/PYSTATSV1_DROPIN_033_UNIFIED_PSYCH_DESIGN_COMPANION_RELEASE_CANDIDATE.md @@ -0,0 +1,96 @@ +# PyStatsV1 Drop-in 033 — Unified Psych-Design Companion v0.1 Release Candidate + +## Boundary + +This unit changes only the PyStatsV1 repository. It does not edit the +*Psychological Statistics by Design* book repository or the LearnToProgram.ca +portal. It does not publish to PyPI, deploy a website, bind manuscript commands, +authorize real data, or create a final public release tag. + +## Baseline and candidate identity + +- Baseline main: `e88ff0f6f1aa1c4a946c72b97387f47b3c5311ae` +- Baseline contract tag: `psych-design-companion-release-contract-v0-1` +- Feature branch: `feat/psych-design-companion-v0-1-release-candidate` +- Candidate package version: `0.26.0` +- Companion version: `v0.1` +- Candidate tag: `psych-design-companion-v0-1-candidate` +- Final release tag `v0.26.0`: reserved and not created by this unit + +## Implemented reader surface + +The candidate implements the frozen command family: + +```text +pystatsv1 psych-design info +pystatsv1 psych-design init +pystatsv1 psych-design verify --dest . +pystatsv1 psych-design list-studies +pystatsv1 psych-design doctor --dest . +``` + +The extracted companion supports: + +```text +make first-result +make list-studies +make design-audit +make all-python +make all-r +make parity +make figures +make reviewer-packets +make all +make study STUDY=SWL-S02 +make verify-study STUDY=SWL-S02 +make packet STUDY=SWL-S02 +``` + +Make orchestrates visible scripts; it does not contain statistical methods or +select a method from data. Python is the primary workflow. The copied and +path-normalized base-R scripts remain independently implemented verification +paths. + +## Unified source architecture + +`psych_design_companion/psychological_statistics_by_design_companion_v0_1/` +contains a no-placeholder central `STUDY_INDEX.json`, all ten synthetic datasets, +design contracts, dedicated measurement maps, visible Python entry scripts, R +launchers, verification policies, reference receipts, figure specifications, +APA source maps, matched limitations, reviewer-packet generation, and nine +synthetic-safe adaptation templates. + +`SOURCE_LINEAGE.json` binds each unified artifact to one of the three preserved +source batches. The Drop-in 032 preservation manifest continues to verify all +136 governed source-batch files byte for byte. + +## First-result proof + +SWL-S02 is the first-result study. `make first-result` verifies the source and +data layout, runs the transparent Python analysis, runs the independent R path, +compares the registered V2 fields, regenerates the source-bound figure, and +constructs a bounded reviewer packet with its own manifest. Each packet includes the study registry record, a source-lineage manifest record, design and measurement contracts, the data-layout audit, Python and R results, the verification receipt, figure specifications and generated-figure manifest, the APA source map, and the matched limitation. The registered +contrast is always `structured_routine - standard_routine`; mutation coverage +rejects reversed or stale bindings. + +## Source verification boundary + +`pystatsv1 psych-design verify` checks only immutable source files listed in the +packaged manifest. Reader-generated `outputs/` are intentionally outside that +manifest. Output receipts contain hashes of their inputs and are regenerated by +the study commands. `make clean` deletes outputs without touching governed +source. + +## Release gates + +The release candidate records all of the following as false: + +- public companion released +- PyPI publication authorized +- portal deployment authorized +- book binding authorized +- real data authorized + +A later clean-reader unit must prove an installed wheel in fresh Ubuntu and WSL +environments before the owner considers the final release tag or PyPI +publication. diff --git a/docs/source/release_notes.rst b/docs/source/release_notes.rst index c9a991e..84d6aab 100644 --- a/docs/source/release_notes.rst +++ b/docs/source/release_notes.rst @@ -1,6 +1,37 @@ Release notes ============= +v0.26.0 release candidate — Psychological Statistics by Design companion v0.1 +--------------------------------------------------------------------------------- + +PyStatsV1 v0.26.0 is prepared as a non-public release candidate for the +synthetic-only *Psychological Statistics by Design* executable companion. +Publication to PyPI, portal deployment, and book binding remain separate, +explicitly unauthorized actions. + +Highlights +~~~~~~~~~~ + +* Adds ``pystatsv1 psych-design info``, ``init``, ``verify``, + ``list-studies``, and ``doctor`` behind the release-candidate source tree. +* Packages all ten SWL studies in one deterministic companion with a central + ``STUDY_INDEX.json``, ten visible Python entry scripts, independent base-R + paths, V2/V3/V4 comparison policies, figures, APA source maps, matched + limitations, and bounded reviewer packets. +* Makes SWL-S02 the first-result vertical slice from source identity and row + audit through Python, R, parity receipt, figure, source map, limitation, and + packet. +* Preserves the three governed source batches byte for byte and records their + lineage in the unified companion. +* Keeps immutable source verification separate from generated ``outputs/`` so + readers can clean and regenerate results without invalidating the released + source bundle. +* Includes synthetic-safe adaptation templates and states that adaptation is a + new research project rather than replacement of a CSV. +* Retains all public-release, PyPI, portal, book-binding, and real-data + authorization gates as false pending clean Ubuntu and WSL installed-wheel + proofs. + v0.25.2 — Book 1 Companion v0.2.1 identity correction ---------------------------------------------------------------- diff --git a/psych_design_companion/psych_design_release_contract_v0_1/COMMAND_INTERFACE_CONTRACT.json b/psych_design_companion/psych_design_release_contract_v0_1/COMMAND_INTERFACE_CONTRACT.json index 603e0b7..672878c 100644 --- a/psych_design_companion/psych_design_release_contract_v0_1/COMMAND_INTERFACE_CONTRACT.json +++ b/psych_design_companion/psych_design_release_contract_v0_1/COMMAND_INTERFACE_CONTRACT.json @@ -2,27 +2,27 @@ "commands": [ { "command": "pystatsv1 psych-design info", - "implemented": false, + "implemented": true, "purpose": "show packaged companion identity" }, { "command": "pystatsv1 psych-design init", - "implemented": false, + "implemented": true, "purpose": "create a new local synthetic companion folder" }, { "command": "pystatsv1 psych-design verify --dest .", - "implemented": false, + "implemented": true, "purpose": "verify immutable source files against the packaged manifest" }, { "command": "pystatsv1 psych-design list-studies", - "implemented": false, + "implemented": true, "purpose": "list all ten studies and chapter mappings" }, { "command": "pystatsv1 psych-design doctor --dest .", - "implemented": false, + "implemented": true, "purpose": "report environment, companion, manifest, dependency, R, and platform facts" } ], @@ -41,8 +41,8 @@ "packet STUDY=SWL-S02" ], "package_install": "python -m pip install \"pystatsv1[psych-design]==0.26.0\"", - "release_boundary": "No command in this contract may be printed as a live reader route until PyPI, asset, portal, and clean-reader proofs are complete.", + "release_boundary": "Commands are implemented and tested in the 0.26.0 release candidate but are not a live PyPI or portal reader route until explicit release authorization and clean-reader proofs pass.", "schema_version": "psych-design-command-interface-contract-v0.1", - "status": "frozen_proposed_not_implemented_not_public", + "status": "release_candidate_implemented_not_public", "visible_script_rule": "Make targets orchestrate visible scripts and may not hide statistical calculations." } diff --git a/psych_design_companion/psych_design_release_contract_v0_1/COMPANION_RELEASE_CONTRACT.json b/psych_design_companion/psych_design_release_contract_v0_1/COMPANION_RELEASE_CONTRACT.json index 0b5bedd..d76a01c 100644 --- a/psych_design_companion/psych_design_release_contract_v0_1/COMPANION_RELEASE_CONTRACT.json +++ b/psych_design_companion/psych_design_release_contract_v0_1/COMPANION_RELEASE_CONTRACT.json @@ -4,8 +4,8 @@ "package_version": "0.25.2", "repository": "PyStatsV1" }, - "expected_branch": "feat/psych-design-companion-release-contract", - "expected_tag": "psych-design-companion-release-contract-v0-1", + "expected_branch": "feat/psych-design-companion-v0-1-release-candidate", + "expected_tag": "psych-design-companion-v0-1-candidate", "first_result_study": "SWL-S02", "lifecycle": { "book_binding_authorized": false, @@ -13,9 +13,10 @@ "portal_release_authorized": false, "public_companion_released": false, "pypi_release_authorized": false, - "real_data_authorized": false + "real_data_authorized": false, + "release_candidate_implemented": true }, - "next_unit": "Build the unified synthetic companion asset, launcher implementation, per-study visible scripts, reviewer packets, and release-candidate tests without publishing.", + "next_unit": "Run clean Ubuntu and WSL installed-wheel release proofs; do not publish, edit the portal, or bind the book without explicit authorization.", "planned_release": { "asset_name": "psychological_statistics_by_design_companion_v0_1.zip", "cli_namespace": "psych-design", @@ -44,6 +45,9 @@ ], "python_primary": true, "r_independent_verification": true, + "release_candidate_asset": "src/pystatsv1/assets/psychological_statistics_by_design_companion_v0_1.zip", + "release_candidate_baseline_main": "e88ff0f6f1aa1c4a946c72b97387f47b3c5311ae", + "release_candidate_source": "psych_design_companion/psychological_statistics_by_design_companion_v0_1", "schema_version": "psych-design-companion-release-contract-v0.1", "source_batches": [ "psych_design_companion/swl_s02_s05_v0_1", @@ -63,5 +67,6 @@ "SWL-S10" ], "unit": "PyStatsV1 Drop-in 032 - Psychological Statistics by Design Companion Release Contract and Unified Study Registry", + "unit_033": "PyStatsV1 Drop-in 033 - Unified Psych-Design Companion v0.1 Release Candidate", "verification_marker": "PYSTATSV1_PSYCH_DESIGN_RELEASE_CONTRACT_VERIFY_OK" } diff --git a/psych_design_companion/psych_design_release_contract_v0_1/CROSS_REPOSITORY_GAP_MATRIX.json b/psych_design_companion/psych_design_release_contract_v0_1/CROSS_REPOSITORY_GAP_MATRIX.json index 5dfd809..e971b03 100644 --- a/psych_design_companion/psych_design_release_contract_v0_1/CROSS_REPOSITORY_GAP_MATRIX.json +++ b/psych_design_companion/psych_design_release_contract_v0_1/CROSS_REPOSITORY_GAP_MATRIX.json @@ -2,65 +2,65 @@ "rows": [ { "area": "Source studies", - "current": "All ten SWL studies exist in three governed private source-candidate batches.", - "gap": "Unification and public packaging absent.", - "next_action": "copy without scientific drift into a unified release-candidate source tree and prove preservation", + "current": "Unified release-candidate source tree and deterministic asset implemented; three governed batches remain preserved.", + "gap": "Clean reader and installed-wheel proof pending.", + "next_action": "Run Drop-in 034 proof without scientific drift.", "owner_repo": "PyStatsV1", "target": "One public synthetic companion with a stable registry." }, { "area": "CLI and PyPI extra", - "current": "Only workbook, book1, trackd, docs, and general doctor commands exist; package is 0.25.2.", - "gap": "No launcher, verify, list-studies, or companion-specific doctor.", - "next_action": "implement CLI and immutable asset launcher in the next unit", + "current": "psych-design namespace, launcher, verify, list-studies, doctor, and 0.26.0 extra implemented in source.", + "gap": "PyPI publication remains unauthorized.", + "next_action": "Prove installed wheel in clean Ubuntu and WSL before release authorization.", "owner_repo": "PyStatsV1", "target": "psych-design namespace and psych-design extra in planned 0.26.0." }, { "area": "Visible per-study Python scripts", - "current": "Current batch calculations are concentrated in shared studies.py modules and batch run_all.py orchestrators.", - "gap": "Reader-facing script granularity absent.", - "next_action": "create thin transparent study entry scripts that call no hidden method selector", + "current": "Ten visible reader entry scripts implemented and registry-bound.", + "gap": "Clean reader command proof pending.", + "next_action": "Execute every printed command from extracted wheel asset.", "owner_repo": "PyStatsV1", "target": "One visible script per study plus Make orchestration." }, { "area": "R verification", - "current": "Independent R scripts exist for all ten studies; V3/V4 policy exists for SWL-S08.", - "gap": "Paths and outputs differ by batch and are not reader-packaged.", - "next_action": "normalize orchestration while preserving field policies", + "current": "Unified R launcher and per-study comparison policy implemented; SWL-S08 V3/V4 boundary preserved.", + "gap": "Full R proof requires supported reader environments.", + "next_action": "Run all ten independent R paths in Drop-in 034.", "owner_repo": "PyStatsV1", "target": "Unified commands and receipts." }, { "area": "Study registry", - "current": "Batch contracts contain partial mappings.", - "gap": "No single source of truth.", - "next_action": "materialize the target index from governed assets", + "current": "Central no-placeholder STUDY_INDEX.json implemented and consumed by scripts, Make, launcher, and packets.", + "gap": "Book command-card binding intentionally deferred.", + "next_action": "Bind only after public package release.", "owner_repo": "PyStatsV1", "target": "Central STUDY_INDEX.json used by CLI, Make, book cards, and packets." }, { "area": "Measurement maps", - "current": "Measurement information is embedded in design/source artifacts but no dedicated public map is registered for every study.", - "gap": "Dedicated artifact missing.", - "next_action": "extract bounded maps without changing results", + "current": "Dedicated synthetic-safe measurement map implemented for every study.", + "gap": "Reader adaptation review remains pending.", + "next_action": "Review in clean-reader proof.", "owner_repo": "PyStatsV1", "target": "measurement/ map per study." }, { "area": "Reviewer packets", - "current": "Book explains packets; current batches retain evidence pieces.", - "gap": "No packet generator or manifest.", - "next_action": "implement deterministic packet builder", + "current": "Deterministic bounded packet builder and manifest implemented.", + "gap": "Full ten-packet R-backed proof pending.", + "next_action": "Generate all packets in Drop-in 034.", "owner_repo": "PyStatsV1", "target": "make packet STUDY=... with bounded manifest." }, { "area": "Licensing", - "current": "Repository software is MIT licensed; existing Book 1 bundles carry boundary notes; psych-design batches have no public release license note.", - "gap": "Companion-specific distribution notice absent.", - "next_action": "add LICENSE_NOTES.md and package audit before release", + "current": "Companion-specific MIT and synthetic-only boundary note implemented.", + "gap": "Public distribution authorization remains false.", + "next_action": "Re-audit packaged asset before release.", "owner_repo": "PyStatsV1", "target": "Explicit public asset license and synthetic-only boundary." }, @@ -82,9 +82,9 @@ }, { "area": "Clean reader proofs", - "current": "No advanced-companion Ubuntu or WSL reader proof.", - "gap": "Not possible before companion implementation.", - "next_action": "perform after portal release candidate and before final interior re-freeze", + "current": "Release candidate now makes clean proof possible.", + "gap": "Ubuntu and WSL installed-wheel proof not yet completed.", + "next_action": "Perform next in Drop-in 034.", "owner_repo": "cross-repository", "target": "Exact printed-command proofs in clean Ubuntu and WSL." } diff --git a/psych_design_companion/psych_design_release_contract_v0_1/TARGET_STUDY_INDEX.json b/psych_design_companion/psych_design_release_contract_v0_1/TARGET_STUDY_INDEX.json index 0667e3d..c5d2237 100644 --- a/psych_design_companion/psych_design_release_contract_v0_1/TARGET_STUDY_INDEX.json +++ b/psych_design_companion/psych_design_release_contract_v0_1/TARGET_STUDY_INDEX.json @@ -1,266 +1,690 @@ { + "actual_registry": "psych_design_companion/psychological_statistics_by_design_companion_v0_1/STUDY_INDEX.json", + "actual_registry_sha256": "1c2525fa1ef4b720d758a32f5e3ad987264e8dfeb28f3967201efca575ef6df7", "schema_version": "psych-design-target-study-index-v0.1", - "status": "contract_not_implemented", + "status": "release_candidate_implemented_not_public", "studies": [ { - "apa_source_map": "outputs/swl_s01/apa_source_map.json", + "apa_source_maps": [ + "reference/apa_source_maps/SWL-S01/APA_RESULT_SOURCE_MAP_CORRELATION.json", + "reference/apa_source_maps/SWL-S01/APA_RESULT_SOURCE_MAP_REGRESSION.json" + ], "book_chapters": [ "11", "12" ], - "claim_type": "to be registered from the governed book/design contract", + "claim_type": "association and within-domain prediction", "comparison_receipt": "outputs/swl_s01/verification_receipt.json", "dataset_path": "data/swl_s01_student_well_being_survey.csv", "design_contract_path": "design/SWL-S01_DESIGN_CONTRACT.json", - "figure_source": "figures/swl_s01.json", - "matched_limitation": "outputs/swl_s01/matched_limitation.md", + "estimands_and_direction": [ + "Pearson association between sleep quality and academic confidence", + "regression coefficients predicting assessment performance within the observed predictor domain" + ], + "expected_rows": 120, + "expected_unique_keys": 120, + "expected_unique_units": 120, + "factor_levels": {}, + "figure_specs": [ + "figures/specs/SWL-S01/FIGURE_SPEC_CORRELATION.json", + "figures/specs/SWL-S01/FIGURE_SPEC_REGRESSION.json" + ], + "first_fields_to_inspect": [ + "n", + "correlation_r_sleep_confidence", + "regression_r_squared", + "study_hours_b", + "confidence_b" + ], + "generated_figure_manifest": "outputs/swl_s01/figures/FIGURE_MANIFEST.json", + "main_prohibited_inference": "No intervention or causal claim is authorized from the cross-sectional survey.", + "matched_limitations": [ + "reference/matched_limitations/SWL-S01/MATCHED_LIMITATION_CORRELATION.json", + "reference/matched_limitations/SWL-S01/MATCHED_LIMITATION_REGRESSION.json" + ], "measurement_map_path": "measurement/SWL-S01_MEASUREMENT_MAP.json", + "packet_command": "make packet STUDY=SWL-S01", "python_result_receipt": "outputs/swl_s01/python_result.json", "python_script": "scripts/python/swl_s01_survey_association_prediction.py", - "r_result": "outputs/swl_s01/r_result.json", - "r_script": "scripts/r/swl_s01.R", + "r_launcher": "scripts/r/run_study.R", + "r_result": "outputs/swl_s01/r_result.csv", + "r_script": "scripts/r/batches/swl_s01_s06_s07_v0_1/swl_s01.R", "reader_command": "make study STUDY=SWL-S01", - "research_question_source": "to be copied from the governed design contract without scientific drift", + "reference_python_result": "reference/results/SWL-S01_PYTHON_RESULT_RECEIPT.json", + "research_questions": [ + "How are sleep quality and academic confidence associated in the synthetic survey records?", + "How well do weekly study hours and academic confidence predict synthetic assessment performance within the registered predictor domain?" + ], "reviewer_packet": "outputs/swl_s01/reviewer_packet/MANIFEST.json", - "status": "target_contract_not_yet_implemented", + "runtime_apa_source_map": "outputs/swl_s01/apa_source_map.json", + "runtime_matched_limitation": "outputs/swl_s01/matched_limitation.md", + "status": "release_candidate_implemented_not_public", "study_id": "SWL-S01", - "unique_key": "to be copied from the governed design contract", - "unit_of_analysis": "to be copied from the governed design contract", + "unique_key": [ + "participant_id" + ], + "unit_of_analysis": "student", "verification_category": "V2", + "verification_command": "make verify-study STUDY=SWL-S01", "verification_script": "scripts/verification/verify_swl_s01.py" }, { - "apa_source_map": "outputs/swl_s02/apa_source_map.json", + "apa_source_maps": [ + "reference/apa_source_maps/SWL-S02/APA_RESULT_SOURCE_MAP.json" + ], "book_chapters": [ "6" ], - "claim_type": "to be registered from the governed book/design contract", + "claim_type": "independent-group mean difference with conditional causal scope", "comparison_receipt": "outputs/swl_s02/verification_receipt.json", "dataset_path": "data/swl_s02_structured_study_routine.csv", "design_contract_path": "design/SWL-S02_DESIGN_CONTRACT.json", - "figure_source": "figures/swl_s02.json", - "matched_limitation": "outputs/swl_s02/matched_limitation.md", + "estimands_and_direction": [ + "structured_routine minus standard_routine mean post-session performance" + ], + "expected_rows": 64, + "expected_unique_keys": 64, + "expected_unique_units": 64, + "factor_levels": { + "study_routine_group": [ + "standard_routine", + "structured_routine" + ] + }, + "figure_specs": [ + "figures/specs/SWL-S02/FIGURE_SPEC.json" + ], + "first_fields_to_inspect": [ + "n_standard", + "n_structured", + "mean_standard", + "mean_structured", + "mean_difference_structured_minus_standard", + "ci_95_low", + "ci_95_high", + "welch_t", + "p_value_two_sided", + "cohen_d_pooled" + ], + "generated_figure_manifest": "outputs/swl_s02/figures/FIGURE_MANIFEST.json", + "main_prohibited_inference": "Do not generalize beyond the synthetic pilot or ignore assignment and implementation conditions.", + "matched_limitations": [ + "reference/matched_limitations/SWL-S02/MATCHED_LIMITATION.json" + ], "measurement_map_path": "measurement/SWL-S02_MEASUREMENT_MAP.json", + "packet_command": "make packet STUDY=SWL-S02", "python_result_receipt": "outputs/swl_s02/python_result.json", "python_script": "scripts/python/swl_s02_independent_groups.py", - "r_result": "outputs/swl_s02/r_result.json", - "r_script": "scripts/r/swl_s02.R", + "r_launcher": "scripts/r/run_study.R", + "r_result": "outputs/swl_s02/r_result.csv", + "r_script": "scripts/r/batches/swl_s02_s05_v0_1/swl_s02.R", "reader_command": "make study STUDY=SWL-S02", - "research_question_source": "to be copied from the governed design contract without scientific drift", + "reference_python_result": "reference/results/SWL-S02_PYTHON_RESULT_RECEIPT.json", + "research_questions": [ + "Do students assigned to a structured study routine differ in post-session performance from students using a standard routine?" + ], "reviewer_packet": "outputs/swl_s02/reviewer_packet/MANIFEST.json", - "status": "target_contract_not_yet_implemented", + "runtime_apa_source_map": "outputs/swl_s02/apa_source_map.json", + "runtime_matched_limitation": "outputs/swl_s02/matched_limitation.md", + "status": "release_candidate_implemented_not_public", "study_id": "SWL-S02", - "unique_key": "to be copied from the governed design contract", - "unit_of_analysis": "to be copied from the governed design contract", + "unique_key": [ + "participant_id" + ], + "unit_of_analysis": "student", "verification_category": "V2", + "verification_command": "make verify-study STUDY=SWL-S02", "verification_script": "scripts/verification/verify_swl_s02.py" }, { - "apa_source_map": "outputs/swl_s03/apa_source_map.json", + "apa_source_maps": [ + "reference/apa_source_maps/SWL-S03/APA_RESULT_SOURCE_MAP.json" + ], "book_chapters": [ "7" ], - "claim_type": "to be registered from the governed book/design contract", + "claim_type": "within-person paired change without a unique causal attribution", "comparison_receipt": "outputs/swl_s03/verification_receipt.json", "dataset_path": "data/swl_s03_skills_workshop_pre_post.csv", "design_contract_path": "design/SWL-S03_DESIGN_CONTRACT.json", - "figure_source": "figures/swl_s03.json", - "matched_limitation": "outputs/swl_s03/matched_limitation.md", + "estimands_and_direction": [ + "post minus pre mean paired change" + ], + "expected_rows": 96, + "expected_unique_keys": 96, + "expected_unique_units": 48, + "factor_levels": { + "occasion": [ + "pre", + "post" + ] + }, + "figure_specs": [ + "figures/specs/SWL-S03/FIGURE_SPEC.json" + ], + "first_fields_to_inspect": [ + "n_paired", + "mean_pre", + "mean_post", + "mean_change_post_minus_pre", + "sd_change", + "paired_t", + "p_value_two_sided", + "cohen_dz" + ], + "generated_figure_manifest": "outputs/swl_s03/figures/FIGURE_MANIFEST.json", + "main_prohibited_inference": "Do not identify the workshop as the unique cause without a control condition.", + "matched_limitations": [ + "reference/matched_limitations/SWL-S03/MATCHED_LIMITATION.json" + ], "measurement_map_path": "measurement/SWL-S03_MEASUREMENT_MAP.json", + "packet_command": "make packet STUDY=SWL-S03", "python_result_receipt": "outputs/swl_s03/python_result.json", "python_script": "scripts/python/swl_s03_paired_change.py", - "r_result": "outputs/swl_s03/r_result.json", - "r_script": "scripts/r/swl_s03.R", + "r_launcher": "scripts/r/run_study.R", + "r_result": "outputs/swl_s03/r_result.csv", + "r_script": "scripts/r/batches/swl_s02_s05_v0_1/swl_s03.R", "reader_command": "make study STUDY=SWL-S03", - "research_question_source": "to be copied from the governed design contract without scientific drift", + "reference_python_result": "reference/results/SWL-S03_PYTHON_RESULT_RECEIPT.json", + "research_questions": [ + "How does academic confidence change from before to after a skills workshop for students with linked observations?" + ], "reviewer_packet": "outputs/swl_s03/reviewer_packet/MANIFEST.json", - "status": "target_contract_not_yet_implemented", + "runtime_apa_source_map": "outputs/swl_s03/apa_source_map.json", + "runtime_matched_limitation": "outputs/swl_s03/matched_limitation.md", + "status": "release_candidate_implemented_not_public", "study_id": "SWL-S03", - "unique_key": "to be copied from the governed design contract", - "unit_of_analysis": "to be copied from the governed design contract", + "unique_key": [ + "participant_id", + "occasion" + ], + "unit_of_analysis": "student", "verification_category": "V2", + "verification_command": "make verify-study STUDY=SWL-S03", "verification_script": "scripts/verification/verify_swl_s03.py" }, { - "apa_source_map": "outputs/swl_s04/apa_source_map.json", + "apa_source_maps": [ + "reference/apa_source_maps/SWL-S04/APA_RESULT_SOURCE_MAP.json" + ], "book_chapters": [ "8" ], - "claim_type": "to be registered from the governed book/design contract", + "claim_type": "omnibus several-group comparison and two registered planned contrasts", "comparison_receipt": "outputs/swl_s04/verification_receipt.json", "dataset_path": "data/swl_s04_three_condition_support.csv", "design_contract_path": "design/SWL-S04_DESIGN_CONTRACT.json", - "figure_source": "figures/swl_s04.json", - "matched_limitation": "outputs/swl_s04/matched_limitation.md", + "estimands_and_direction": [ + "guided-condition average minus standard support", + "guided practice plus feedback minus guided practice" + ], + "expected_rows": 90, + "expected_unique_keys": 90, + "expected_unique_units": 90, + "factor_levels": { + "support_condition": [ + "standard_support", + "guided_practice", + "guided_practice_plus_feedback" + ] + }, + "figure_specs": [ + "figures/specs/SWL-S04/FIGURE_SPEC.json" + ], + "first_fields_to_inspect": [ + "n_total", + "mean_standard_support", + "mean_guided_practice", + "mean_guided_practice_plus_feedback", + "anova_f", + "contrast_guided_average_minus_standard_estimate", + "contrast_feedback_increment_estimate" + ], + "generated_figure_manifest": "outputs/swl_s04/figures/FIGURE_MANIFEST.json", + "main_prohibited_inference": "Do not replace the registered comparison family with post-result pairwise fishing.", + "matched_limitations": [ + "reference/matched_limitations/SWL-S04/MATCHED_LIMITATION.json" + ], "measurement_map_path": "measurement/SWL-S04_MEASUREMENT_MAP.json", + "packet_command": "make packet STUDY=SWL-S04", "python_result_receipt": "outputs/swl_s04/python_result.json", "python_script": "scripts/python/swl_s04_planned_contrasts.py", - "r_result": "outputs/swl_s04/r_result.json", - "r_script": "scripts/r/swl_s04.R", + "r_launcher": "scripts/r/run_study.R", + "r_result": "outputs/swl_s04/r_result.csv", + "r_script": "scripts/r/batches/swl_s02_s05_v0_1/swl_s04.R", "reader_command": "make study STUDY=SWL-S04", - "research_question_source": "to be copied from the governed design contract without scientific drift", + "reference_python_result": "reference/results/SWL-S04_PYTHON_RESULT_RECEIPT.json", + "research_questions": [ + "Do assessment outcomes differ across standard support, guided practice, and guided practice plus feedback, and which planned contrasts answer the substantive questions?" + ], "reviewer_packet": "outputs/swl_s04/reviewer_packet/MANIFEST.json", - "status": "target_contract_not_yet_implemented", + "runtime_apa_source_map": "outputs/swl_s04/apa_source_map.json", + "runtime_matched_limitation": "outputs/swl_s04/matched_limitation.md", + "status": "release_candidate_implemented_not_public", "study_id": "SWL-S04", - "unique_key": "to be copied from the governed design contract", - "unit_of_analysis": "to be copied from the governed design contract", + "unique_key": [ + "participant_id" + ], + "unit_of_analysis": "student", "verification_category": "V2", + "verification_command": "make verify-study STUDY=SWL-S04", "verification_script": "scripts/verification/verify_swl_s04.py" }, { - "apa_source_map": "outputs/swl_s05/apa_source_map.json", + "apa_source_maps": [ + "reference/apa_source_maps/SWL-S05/APA_RESULT_SOURCE_MAP.json" + ], "book_chapters": [ "9" ], - "claim_type": "to be registered from the governed book/design contract", + "claim_type": "factorial interaction and registered simple strategy effects", "comparison_receipt": "outputs/swl_s05/verification_receipt.json", "dataset_path": "data/swl_s05_strategy_feedback.csv", "design_contract_path": "design/SWL-S05_DESIGN_CONTRACT.json", - "figure_source": "figures/swl_s05.json", - "matched_limitation": "outputs/swl_s05/matched_limitation.md", + "estimands_and_direction": [ + "retrieval-practice effect with feedback minus retrieval-practice effect without feedback" + ], + "expected_rows": 96, + "expected_unique_keys": 96, + "expected_unique_units": 96, + "factor_levels": { + "feedback_condition": [ + "no_feedback", + "explanatory_feedback" + ], + "study_strategy": [ + "rereading", + "retrieval_practice" + ] + }, + "figure_specs": [ + "figures/specs/SWL-S05/FIGURE_SPEC.json" + ], + "first_fields_to_inspect": [ + "n_total", + "mean_rereading_no_feedback", + "mean_retrieval_no_feedback", + "mean_rereading_explanatory_feedback", + "mean_retrieval_explanatory_feedback", + "interaction_difference_in_differences", + "interaction_f" + ], + "generated_figure_manifest": "outputs/swl_s05/figures/FIGURE_MANIFEST.json", + "main_prohibited_inference": "Do not report only averaged main effects when the interaction is the registered target.", + "matched_limitations": [ + "reference/matched_limitations/SWL-S05/MATCHED_LIMITATION.json" + ], "measurement_map_path": "measurement/SWL-S05_MEASUREMENT_MAP.json", + "packet_command": "make packet STUDY=SWL-S05", "python_result_receipt": "outputs/swl_s05/python_result.json", "python_script": "scripts/python/swl_s05_factorial_interaction.py", - "r_result": "outputs/swl_s05/r_result.json", - "r_script": "scripts/r/swl_s05.R", + "r_launcher": "scripts/r/run_study.R", + "r_result": "outputs/swl_s05/r_result.csv", + "r_script": "scripts/r/batches/swl_s02_s05_v0_1/swl_s05.R", "reader_command": "make study STUDY=SWL-S05", - "research_question_source": "to be copied from the governed design contract without scientific drift", + "reference_python_result": "reference/results/SWL-S05_PYTHON_RESULT_RECEIPT.json", + "research_questions": [ + "Does the effect of study strategy on assessment performance depend on the feedback condition?" + ], "reviewer_packet": "outputs/swl_s05/reviewer_packet/MANIFEST.json", - "status": "target_contract_not_yet_implemented", + "runtime_apa_source_map": "outputs/swl_s05/apa_source_map.json", + "runtime_matched_limitation": "outputs/swl_s05/matched_limitation.md", + "status": "release_candidate_implemented_not_public", "study_id": "SWL-S05", - "unique_key": "to be copied from the governed design contract", - "unit_of_analysis": "to be copied from the governed design contract", + "unique_key": [ + "participant_id" + ], + "unit_of_analysis": "student", "verification_category": "V2", + "verification_command": "make verify-study STUDY=SWL-S05", "verification_script": "scripts/verification/verify_swl_s05.py" }, { - "apa_source_map": "outputs/swl_s06/apa_source_map.json", + "apa_source_maps": [ + "reference/apa_source_maps/SWL-S06/APA_RESULT_SOURCE_MAP.json" + ], "book_chapters": [ "10" ], - "claim_type": "to be registered from the governed book/design contract", + "claim_type": "complete-trajectory occasion comparison with missing-visit disclosure", "comparison_receipt": "outputs/swl_s06/verification_receipt.json", "dataset_path": "data/swl_s06_confidence_trajectory.csv", "design_contract_path": "design/SWL-S06_DESIGN_CONTRACT.json", - "figure_source": "figures/swl_s06.json", - "matched_limitation": "outputs/swl_s06/matched_limitation.md", + "estimands_and_direction": [ + "occasion effect across baseline, post, and follow_up among complete trajectories" + ], + "expected_rows": 180, + "expected_unique_keys": 180, + "expected_unique_units": 60, + "factor_levels": { + "occasion": [ + "baseline", + "post", + "follow_up" + ] + }, + "figure_specs": [ + "figures/specs/SWL-S06/FIGURE_SPEC.json" + ], + "first_fields_to_inspect": [ + "n_scheduled_participants", + "observed_outcomes", + "missing_outcomes", + "n_complete_case", + "mean_baseline_complete_case", + "mean_post_complete_case", + "mean_follow_up_complete_case", + "rm_anova_f" + ], + "generated_figure_manifest": "outputs/swl_s06/figures/FIGURE_MANIFEST.json", + "main_prohibited_inference": "Do not treat complete-case occasion differences as a unique causal effect or hide missing visits.", + "matched_limitations": [ + "reference/matched_limitations/SWL-S06/MATCHED_LIMITATION.json" + ], "measurement_map_path": "measurement/SWL-S06_MEASUREMENT_MAP.json", + "packet_command": "make packet STUDY=SWL-S06", "python_result_receipt": "outputs/swl_s06/python_result.json", "python_script": "scripts/python/swl_s06_repeated_measures.py", - "r_result": "outputs/swl_s06/r_result.json", - "r_script": "scripts/r/swl_s06.R", + "r_launcher": "scripts/r/run_study.R", + "r_result": "outputs/swl_s06/r_result.csv", + "r_script": "scripts/r/batches/swl_s01_s06_s07_v0_1/swl_s06.R", "reader_command": "make study STUDY=SWL-S06", - "research_question_source": "to be copied from the governed design contract without scientific drift", + "reference_python_result": "reference/results/SWL-S06_PYTHON_RESULT_RECEIPT.json", + "research_questions": [ + "Does mean academic confidence differ across baseline, post, and follow-up among complete synthetic trajectories?" + ], "reviewer_packet": "outputs/swl_s06/reviewer_packet/MANIFEST.json", - "status": "target_contract_not_yet_implemented", + "runtime_apa_source_map": "outputs/swl_s06/apa_source_map.json", + "runtime_matched_limitation": "outputs/swl_s06/matched_limitation.md", + "status": "release_candidate_implemented_not_public", "study_id": "SWL-S06", - "unique_key": "to be copied from the governed design contract", - "unit_of_analysis": "to be copied from the governed design contract", + "unique_key": [ + "participant_id", + "occasion" + ], + "unit_of_analysis": "student", "verification_category": "V2", + "verification_command": "make verify-study STUDY=SWL-S06", "verification_script": "scripts/verification/verify_swl_s06.py" }, { - "apa_source_map": "outputs/swl_s07/apa_source_map.json", + "apa_source_maps": [ + "reference/apa_source_maps/SWL-S07/APA_RESULT_SOURCE_MAP.json" + ], "book_chapters": [ "13" ], - "claim_type": "to be registered from the governed book/design contract", + "claim_type": "baseline-adjusted group comparison under the registered ANCOVA model", "comparison_receipt": "outputs/swl_s07/verification_receipt.json", "dataset_path": "data/swl_s07_baseline_adjusted_intervention.csv", "design_contract_path": "design/SWL-S07_DESIGN_CONTRACT.json", - "figure_source": "figures/swl_s07.json", - "matched_limitation": "outputs/swl_s07/matched_limitation.md", + "estimands_and_direction": [ + "intervention minus comparison adjusted post-performance difference at the overall baseline mean" + ], + "expected_rows": 90, + "expected_unique_keys": 90, + "expected_unique_units": 90, + "factor_levels": { + "intervention_group": [ + "comparison", + "intervention" + ] + }, + "figure_specs": [ + "figures/specs/SWL-S07/FIGURE_SPEC.json" + ], + "first_fields_to_inspect": [ + "n_total", + "raw_post_mean_comparison", + "raw_post_mean_intervention", + "adjusted_mean_comparison_at_overall_baseline", + "adjusted_mean_intervention_at_overall_baseline", + "adjusted_group_difference_intervention_minus_comparison", + "group_effect_f" + ], + "generated_figure_manifest": "outputs/swl_s07/figures/FIGURE_MANIFEST.json", + "main_prohibited_inference": "Do not claim that covariate adjustment creates randomization or removes unmeasured confounding.", + "matched_limitations": [ + "reference/matched_limitations/SWL-S07/MATCHED_LIMITATION.json" + ], "measurement_map_path": "measurement/SWL-S07_MEASUREMENT_MAP.json", + "packet_command": "make packet STUDY=SWL-S07", "python_result_receipt": "outputs/swl_s07/python_result.json", "python_script": "scripts/python/swl_s07_ancova.py", - "r_result": "outputs/swl_s07/r_result.json", - "r_script": "scripts/r/swl_s07.R", + "r_launcher": "scripts/r/run_study.R", + "r_result": "outputs/swl_s07/r_result.csv", + "r_script": "scripts/r/batches/swl_s01_s06_s07_v0_1/swl_s07.R", "reader_command": "make study STUDY=SWL-S07", - "research_question_source": "to be copied from the governed design contract without scientific drift", + "reference_python_result": "reference/results/SWL-S07_PYTHON_RESULT_RECEIPT.json", + "research_questions": [ + "Do the intervention and comparison groups differ in post performance after adjustment to the overall baseline mean?" + ], "reviewer_packet": "outputs/swl_s07/reviewer_packet/MANIFEST.json", - "status": "target_contract_not_yet_implemented", + "runtime_apa_source_map": "outputs/swl_s07/apa_source_map.json", + "runtime_matched_limitation": "outputs/swl_s07/matched_limitation.md", + "status": "release_candidate_implemented_not_public", "study_id": "SWL-S07", - "unique_key": "to be copied from the governed design contract", - "unit_of_analysis": "to be copied from the governed design contract", + "unique_key": [ + "participant_id" + ], + "unit_of_analysis": "student", "verification_category": "V2", + "verification_command": "make verify-study STUDY=SWL-S07", "verification_script": "scripts/verification/verify_swl_s07.py" }, { - "apa_source_map": "outputs/swl_s08/apa_source_map.json", + "apa_source_maps": [ + "reference/apa_source_maps/SWL-S08/APA_RESULT_SOURCE_MAP.json" + ], "book_chapters": [ "14" ], - "claim_type": "to be registered from the governed book/design contract", + "claim_type": "longitudinal treatment-versus-control differential change", "comparison_receipt": "outputs/swl_s08/verification_receipt.json", "dataset_path": "data/swl_s08_longitudinal_treatment.csv", "design_contract_path": "design/SWL-S08_DESIGN_CONTRACT.json", - "figure_source": "figures/swl_s08.json", - "matched_limitation": "outputs/swl_s08/matched_limitation.md", + "estimands_and_direction": [ + "additional treatment-group change from baseline to post", + "additional treatment-group change from baseline to follow-up" + ], + "expected_rows": 168, + "expected_unique_keys": 168, + "expected_unique_units": 60, + "factor_levels": { + "occasion": [ + "baseline", + "post", + "followup" + ], + "treatment_group": [ + "control", + "treatment" + ] + }, + "figure_specs": [ + "figures/specs/SWL-S08/FIGURE_SPEC.json" + ], + "first_fields_to_inspect": [ + "n_participants", + "observed_rows", + "missing_visits", + "treatment_post_b", + "treatment_post_se", + "treatment_followup_b", + "treatment_followup_se" + ], + "generated_figure_manifest": "outputs/swl_s08/figures/FIGURE_MANIFEST.json", + "main_prohibited_inference": "Do not claim that a mixed model makes outcome-dependent missingness harmless or proves a real treatment effect.", + "matched_limitations": [ + "reference/matched_limitations/SWL-S08/MATCHED_LIMITATION.json" + ], "measurement_map_path": "measurement/SWL-S08_MEASUREMENT_MAP.json", + "packet_command": "make packet STUDY=SWL-S08", "python_result_receipt": "outputs/swl_s08/python_result.json", "python_script": "scripts/python/swl_s08_mixed_model.py", - "r_result": "outputs/swl_s08/r_result.json", - "r_script": "scripts/r/swl_s08.R", + "r_launcher": "scripts/r/run_study.R", + "r_result": "outputs/swl_s08/r_result.csv", + "r_script": "scripts/r/batches/swl_s08_s10_v0_1/swl_s08.R", "reader_command": "make study STUDY=SWL-S08", - "research_question_source": "to be copied from the governed design contract without scientific drift", + "reference_python_result": "reference/results/SWL-S08_PYTHON_RESULT_RECEIPT.json", + "research_questions": [ + "Does change in synthetic well-being from baseline differ between treatment and control when participants have unequal numbers of observed visits?" + ], "reviewer_packet": "outputs/swl_s08/reviewer_packet/MANIFEST.json", - "status": "target_contract_not_yet_implemented", + "runtime_apa_source_map": "outputs/swl_s08/apa_source_map.json", + "runtime_matched_limitation": "outputs/swl_s08/matched_limitation.md", + "status": "release_candidate_implemented_not_public", "study_id": "SWL-S08", - "unique_key": "to be copied from the governed design contract", - "unit_of_analysis": "to be copied from the governed design contract", + "unique_key": [ + "participant_id", + "occasion" + ], + "unit_of_analysis": "student", "verification_category": "V3_V4", + "verification_command": "make verify-study STUDY=SWL-S08", "verification_script": "scripts/verification/verify_swl_s08.py" }, { - "apa_source_map": "outputs/swl_s09/apa_source_map.json", + "apa_source_maps": [ + "reference/apa_source_maps/SWL-S09/APA_RESULT_SOURCE_MAP_CATEGORICAL.json", + "reference/apa_source_maps/SWL-S09/APA_RESULT_SOURCE_MAP_RANK.json" + ], "book_chapters": [ "15" ], - "claim_type": "to be registered from the governed book/design contract", + "claim_type": "categorical association and rank-based distributional shift", "comparison_receipt": "outputs/swl_s09/verification_receipt.json", "dataset_path": "data/swl_s09_outcome_distribution_extension.csv", "design_contract_path": "design/SWL-S09_DESIGN_CONTRACT.json", - "figure_source": "figures/swl_s09.json", - "matched_limitation": "outputs/swl_s09/matched_limitation.md", + "estimands_and_direction": [ + "intervention minus comparison improvement-risk difference", + "distributional shift in recovery burden with intervention oriented as lower burden" + ], + "expected_rows": 160, + "expected_unique_keys": 160, + "expected_unique_units": 160, + "factor_levels": { + "clinically_improved": [ + "yes", + "no" + ], + "support_condition": [ + "comparison", + "intervention" + ] + }, + "figure_specs": [ + "figures/specs/SWL-S09/FIGURE_SPEC_CATEGORICAL.json", + "figures/specs/SWL-S09/FIGURE_SPEC_RANK.json" + ], + "first_fields_to_inspect": [ + "n_total", + "comparison_improved_proportion", + "intervention_improved_proportion", + "chi_square", + "cramers_v", + "comparison_burden_median", + "intervention_burden_median", + "mann_whitney_u_intervention" + ], + "generated_figure_manifest": "outputs/swl_s09/figures/FIGURE_MANIFEST.json", + "main_prohibited_inference": "Do not treat either registered outcome analysis as a generic repair for failed normality.", + "matched_limitations": [ + "reference/matched_limitations/SWL-S09/MATCHED_LIMITATION_CATEGORICAL.json", + "reference/matched_limitations/SWL-S09/MATCHED_LIMITATION_RANK.json" + ], "measurement_map_path": "measurement/SWL-S09_MEASUREMENT_MAP.json", + "packet_command": "make packet STUDY=SWL-S09", "python_result_receipt": "outputs/swl_s09/python_result.json", "python_script": "scripts/python/swl_s09_distribution_extension.py", - "r_result": "outputs/swl_s09/r_result.json", - "r_script": "scripts/r/swl_s09.R", + "r_launcher": "scripts/r/run_study.R", + "r_result": "outputs/swl_s09/r_result.csv", + "r_script": "scripts/r/batches/swl_s08_s10_v0_1/swl_s09.R", "reader_command": "make study STUDY=SWL-S09", - "research_question_source": "to be copied from the governed design contract without scientific drift", + "reference_python_result": "reference/results/SWL-S09_PYTHON_RESULT_RECEIPT.json", + "research_questions": [ + "Does clinical-improvement status differ by support condition, and does positive-skew recovery burden shift between the two independent conditions?" + ], "reviewer_packet": "outputs/swl_s09/reviewer_packet/MANIFEST.json", - "status": "target_contract_not_yet_implemented", + "runtime_apa_source_map": "outputs/swl_s09/apa_source_map.json", + "runtime_matched_limitation": "outputs/swl_s09/matched_limitation.md", + "status": "release_candidate_implemented_not_public", "study_id": "SWL-S09", - "unique_key": "to be copied from the governed design contract", - "unit_of_analysis": "to be copied from the governed design contract", + "unique_key": [ + "participant_id" + ], + "unit_of_analysis": "student", "verification_category": "V2", + "verification_command": "make verify-study STUDY=SWL-S09", "verification_script": "scripts/verification/verify_swl_s09.py" }, { - "apa_source_map": "outputs/swl_s10/apa_source_map.json", + "apa_source_maps": [ + "reference/apa_source_maps/SWL-S10/APA_RESULT_SOURCE_MAP_META.json", + "reference/apa_source_maps/SWL-S10/APA_RESULT_SOURCE_MAP_POWER.json" + ], "book_chapters": [ "16" ], - "claim_type": "to be registered from the governed book/design contract", + "claim_type": "prospective power planning and fixed-effect cumulative evidence", "comparison_receipt": "outputs/swl_s10/verification_receipt.json", "dataset_path": "data/swl_s10_replication_program.csv", "design_contract_path": "design/SWL-S10_DESIGN_CONTRACT.json", - "figure_source": "figures/swl_s10.json", - "matched_limitation": "outputs/swl_s10/matched_limitation.md", + "estimands_and_direction": [ + "per-group sample size for d=0.40 at 80% power", + "fixed-effect pooled standardized mean difference across six replications" + ], + "expected_rows": 6, + "expected_unique_keys": 6, + "expected_unique_units": 6, + "factor_levels": { + "replication_type": [ + "conceptual", + "direct" + ] + }, + "figure_specs": [ + "figures/specs/SWL-S10/FIGURE_SPEC.json" + ], + "first_fields_to_inspect": [ + "planning_n_per_group_ceiling_80", + "study_count", + "fixed_effect_pooled_d", + "fixed_effect_se", + "fixed_effect_ci_95_low", + "fixed_effect_ci_95_high", + "cochran_q", + "i_squared_percent" + ], + "generated_figure_manifest": "outputs/swl_s10/figures/FIGURE_MANIFEST.json", + "main_prohibited_inference": "Do not interpret planned power or a fixed-effect synthesis as proof against bias, heterogeneity, or selective evidence.", + "matched_limitations": [ + "reference/matched_limitations/SWL-S10/MATCHED_LIMITATION_META.json", + "reference/matched_limitations/SWL-S10/MATCHED_LIMITATION_POWER.json" + ], "measurement_map_path": "measurement/SWL-S10_MEASUREMENT_MAP.json", + "packet_command": "make packet STUDY=SWL-S10", "python_result_receipt": "outputs/swl_s10/python_result.json", "python_script": "scripts/python/swl_s10_power_replication.py", - "r_result": "outputs/swl_s10/r_result.json", - "r_script": "scripts/r/swl_s10.R", + "r_launcher": "scripts/r/run_study.R", + "r_result": "outputs/swl_s10/r_result.csv", + "r_script": "scripts/r/batches/swl_s08_s10_v0_1/swl_s10.R", "reader_command": "make study STUDY=SWL-S10", - "research_question_source": "to be copied from the governed design contract without scientific drift", + "reference_python_result": "reference/results/SWL-S10_PYTHON_RESULT_RECEIPT.json", + "research_questions": [ + "What per-group sample size follows from the registered d=0.40 power target, and what fixed-effect estimate summarizes the six completed synthetic replications?" + ], "reviewer_packet": "outputs/swl_s10/reviewer_packet/MANIFEST.json", - "status": "target_contract_not_yet_implemented", + "runtime_apa_source_map": "outputs/swl_s10/apa_source_map.json", + "runtime_matched_limitation": "outputs/swl_s10/matched_limitation.md", + "status": "release_candidate_implemented_not_public", "study_id": "SWL-S10", - "unique_key": "to be copied from the governed design contract", - "unit_of_analysis": "to be copied from the governed design contract", + "unique_key": [ + "study_id" + ], + "unit_of_analysis": "completed synthetic replication", "verification_category": "V2", + "verification_command": "make verify-study STUDY=SWL-S10", "verification_script": "scripts/verification/verify_swl_s10.py" } ] diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/COMPANION_PROVENANCE.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/COMPANION_PROVENANCE.json new file mode 100644 index 0000000..00bcbff --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/COMPANION_PROVENANCE.json @@ -0,0 +1,13 @@ +{ + "book_binding_authorized": false, + "companion_version": "v0.1", + "contract_tag": "psych-design-companion-release-contract-v0-1", + "package_version": "0.26.0", + "portal_release_authorized": false, + "pypi_release_authorized": false, + "real_data_authorized": false, + "release_status": "release_candidate_not_public", + "schema_version": "psych-design-companion-provenance-v0.1", + "source_main": "e88ff0f6f1aa1c4a946c72b97387f47b3c5311ae", + "synthetic_data_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/LICENSE_NOTES.md b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/LICENSE_NOTES.md new file mode 100644 index 0000000..6c8bb66 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/LICENSE_NOTES.md @@ -0,0 +1,9 @@ +# License and data boundary + +The executable code in this companion is distributed under the PyStatsV1 MIT License. +The included CSV files are synthetic teaching fixtures. They do not describe real people. + +This release candidate does **not** authorize participant, student, client, clinical, +institutional, thesis, confidential, or restricted data. Do not replace a teaching CSV +and assume that the design, measurement, model, verification, reporting, privacy, or +causal contract remains valid. diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/Makefile b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/Makefile new file mode 100644 index 0000000..1dfbab6 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/Makefile @@ -0,0 +1,47 @@ +PYTHON ?= python3 +STUDY ?= + +.PHONY: first-result list-studies design-audit study verify-study packet all-python all-r parity figures reviewer-packets all clean + +list-studies: + $(PYTHON) scripts/python/list_studies.py + +design-audit: + $(PYTHON) scripts/python/design_audit.py + +study: + @test -n "$(STUDY)" || (echo "Set STUDY=SWL-S01 through SWL-S10"; exit 2) + $(PYTHON) scripts/python/run_study.py --study "$(STUDY)" + +verify-study: study + Rscript scripts/r/run_study.R "$(CURDIR)" "$(STUDY)" + $(PYTHON) scripts/verification/verify_study.py --study "$(STUDY)" + +figures: + $(PYTHON) scripts/python/generate_figures.py $(if $(STUDY),--study "$(STUDY)",) + +packet: verify-study + $(PYTHON) scripts/python/generate_figures.py --study "$(STUDY)" + $(PYTHON) scripts/python/build_reviewer_packet.py --study "$(STUDY)" + +first-result: + $(MAKE) packet STUDY=SWL-S02 + @echo "PYSTATSV1_PSYCH_DESIGN_FIRST_RESULT_OK" + +all-python: design-audit + $(PYTHON) scripts/python/run_all.py + +all-r: + Rscript scripts/r/run_all.R "$(CURDIR)" + +parity: all-python all-r + $(PYTHON) scripts/verification/verify_all.py + +reviewer-packets: parity figures + $(PYTHON) scripts/python/build_reviewer_packet.py + +all: design-audit reviewer-packets + @echo "PYSTATSV1_PSYCH_DESIGN_COMPANION_ALL_OK" + +clean: + rm -rf outputs diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/README.md b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/README.md new file mode 100644 index 0000000..a39766d --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/README.md @@ -0,0 +1,38 @@ +# Psychological Statistics by Design — Executable Companion v0.1 + +This is the **PyStatsV1 0.26.0 release candidate**, not a public release. It contains ten governed synthetic studies and no real participant data. + +## First complete proof chain + +```bash +python -m pip install -r requirements-psych-design-companion.txt +make first-result +``` + +The command runs SWL-S02 through source and layout checks, visible Python analysis, independent R analysis, V2 comparison, figure generation, APA source binding, matched limitation, and reviewer-packet construction. + +## Visible routes + +```bash +make list-studies +make design-audit +make study STUDY=SWL-S02 +make verify-study STUDY=SWL-S02 +make packet STUDY=SWL-S02 +make all-python +make all-r +make parity +make figures +make reviewer-packets +make all +``` + +Python is the primary workflow. R is an independent verification path. PyStatsV1 initializes and verifies the source bundle; it does not choose a method, validate an instrument, authorize data, establish causality, or repair a confused design. + +Reviewer packets include a bounded study-index record, source-manifest record, design and measurement contracts, data-layout audit, Python and R results, verification receipt, figure specifications and manifest, APA source map, and matched limitation. Every packet member is hash-recorded in `MANIFEST.json`. + +Generated evidence is written under `outputs/` and is intentionally excluded from the immutable source manifest. `pystatsv1 psych-design verify --dest .` verifies the distributed source even after outputs have been generated. + +## Adaptation boundary + +`templates/` contains blank planning records. They are not a “replace the CSV” recipe. Adapting an authorized project requires a new study map, measurement map, data-layout contract, analysis plan, verification contract, source map, matched limitation, and delta ledger. Stop when authorization, privacy, construct definition, unit, clustering, missingness, model selection, multiplicity, or causal scope is unresolved. diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/SOURCE_LINEAGE.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/SOURCE_LINEAGE.json new file mode 100644 index 0000000..4936d5d --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/SOURCE_LINEAGE.json @@ -0,0 +1,280 @@ +{ + "companion_version": "v0.1", + "package_release_candidate": "0.26.0", + "schema_version": "psych-design-source-lineage-v0.1", + "source_anchor": "e88ff0f6f1aa1c4a946c72b97387f47b3c5311ae", + "source_batches_preserved_in_place": true, + "studies": [ + { + "origins": [ + { + "companion_path": "data/swl_s01_student_well_being_survey.csv", + "companion_sha256": "46c4334a443924d27ddcdbf0c5c3f5cf06eeb27323e1aebb0ebc021e4fbbd44d", + "source_path": "psych_design_companion/swl_s01_s06_s07_v0_1/data/swl_s01_student_well_being_survey.csv", + "source_sha256": "46c4334a443924d27ddcdbf0c5c3f5cf06eeb27323e1aebb0ebc021e4fbbd44d", + "transformation": "byte-for-byte copy" + }, + { + "companion_path": "design/SWL-S01_DESIGN_CONTRACT.json", + "companion_sha256": "0a8888bf6693e3652bf5f076d22004bbfc4d3a3ec85b4c7bce73e3f645291955", + "source_path": "psych_design_companion/swl_s01_s06_s07_v0_1/contracts/SWL-S01_DESIGN_CONTRACT.json", + "source_sha256": "0a8888bf6693e3652bf5f076d22004bbfc4d3a3ec85b4c7bce73e3f645291955", + "transformation": "byte-for-byte copy" + }, + { + "companion_path": "reference/results/SWL-S01_PYTHON_RESULT_RECEIPT.json", + "companion_sha256": "b2678e7085d3f2811441adf677b94bc373b627746c30ca83da6f2f922b0db48b", + "source_path": "psych_design_companion/swl_s01_s06_s07_v0_1/evidence/SWL-S01/PYTHON_RESULT_RECEIPT.json", + "source_sha256": "b2678e7085d3f2811441adf677b94bc373b627746c30ca83da6f2f922b0db48b", + "transformation": "byte-for-byte copy" + } + ], + "source_batch": "swl_s01_s06_s07_v0_1", + "study_id": "SWL-S01" + }, + { + "origins": [ + { + "companion_path": "data/swl_s02_structured_study_routine.csv", + "companion_sha256": "42b977f231609300049524d19e64ed6223f50d0cdb1881b857f34b49884dd88d", + "source_path": "psych_design_companion/swl_s02_s05_v0_1/data/swl_s02_structured_study_routine.csv", + "source_sha256": "42b977f231609300049524d19e64ed6223f50d0cdb1881b857f34b49884dd88d", + "transformation": "byte-for-byte copy" + }, + { + "companion_path": "design/SWL-S02_DESIGN_CONTRACT.json", + "companion_sha256": "4c90d374c7318bf0a45e0afa465f4d924362286fb40092bfb5af569541d46619", + "source_path": "psych_design_companion/swl_s02_s05_v0_1/contracts/SWL-S02_DESIGN_CONTRACT.json", + "source_sha256": "4c90d374c7318bf0a45e0afa465f4d924362286fb40092bfb5af569541d46619", + "transformation": "byte-for-byte copy" + }, + { + "companion_path": "reference/results/SWL-S02_PYTHON_RESULT_RECEIPT.json", + "companion_sha256": "4dcd4f5889ab0c56574a34d0d5dc9e3e5bd7c03adb9bdbe7da21884b93fbf4ff", + "source_path": "psych_design_companion/swl_s02_s05_v0_1/evidence/SWL-S02/PYTHON_RESULT_RECEIPT.json", + "source_sha256": "4dcd4f5889ab0c56574a34d0d5dc9e3e5bd7c03adb9bdbe7da21884b93fbf4ff", + "transformation": "byte-for-byte copy" + } + ], + "source_batch": "swl_s02_s05_v0_1", + "study_id": "SWL-S02" + }, + { + "origins": [ + { + "companion_path": "data/swl_s03_skills_workshop_pre_post.csv", + "companion_sha256": "5c1d114eac729358602bd621a5b5c74f3cd073ceeeb794407eb21b88dea08e28", + "source_path": "psych_design_companion/swl_s02_s05_v0_1/data/swl_s03_skills_workshop_pre_post.csv", + "source_sha256": "5c1d114eac729358602bd621a5b5c74f3cd073ceeeb794407eb21b88dea08e28", + "transformation": "byte-for-byte copy" + }, + { + "companion_path": "design/SWL-S03_DESIGN_CONTRACT.json", + "companion_sha256": "f69dba723c6a2147029c99810307a1953976e6b001b77ea5897217741dc55ded", + "source_path": "psych_design_companion/swl_s02_s05_v0_1/contracts/SWL-S03_DESIGN_CONTRACT.json", + "source_sha256": "f69dba723c6a2147029c99810307a1953976e6b001b77ea5897217741dc55ded", + "transformation": "byte-for-byte copy" + }, + { + "companion_path": "reference/results/SWL-S03_PYTHON_RESULT_RECEIPT.json", + "companion_sha256": "6a4b963dc3dbbf26460029cd364192e560c31135bf074aeb7f855f57dbe35b6e", + "source_path": "psych_design_companion/swl_s02_s05_v0_1/evidence/SWL-S03/PYTHON_RESULT_RECEIPT.json", + "source_sha256": "6a4b963dc3dbbf26460029cd364192e560c31135bf074aeb7f855f57dbe35b6e", + "transformation": "byte-for-byte copy" + } + ], + "source_batch": "swl_s02_s05_v0_1", + "study_id": "SWL-S03" + }, + { + "origins": [ + { + "companion_path": "data/swl_s04_three_condition_support.csv", + "companion_sha256": "1584c1f5ebcd4890a0ed5aa35bfbf7a2e685a0f74492a4b4557fd2d49d3a87c8", + "source_path": "psych_design_companion/swl_s02_s05_v0_1/data/swl_s04_three_condition_support.csv", + "source_sha256": "1584c1f5ebcd4890a0ed5aa35bfbf7a2e685a0f74492a4b4557fd2d49d3a87c8", + "transformation": "byte-for-byte copy" + }, + { + "companion_path": "design/SWL-S04_DESIGN_CONTRACT.json", + "companion_sha256": "03836ba796954dde6b1a37e5df60180d8e734938404f258f074b818373db9a27", + "source_path": "psych_design_companion/swl_s02_s05_v0_1/contracts/SWL-S04_DESIGN_CONTRACT.json", + "source_sha256": "03836ba796954dde6b1a37e5df60180d8e734938404f258f074b818373db9a27", + "transformation": "byte-for-byte copy" + }, + { + "companion_path": "reference/results/SWL-S04_PYTHON_RESULT_RECEIPT.json", + "companion_sha256": "4cad31f30359c41c6dc6431882b0bd0cec84100c3d86006a883fc071a6993666", + "source_path": "psych_design_companion/swl_s02_s05_v0_1/evidence/SWL-S04/PYTHON_RESULT_RECEIPT.json", + "source_sha256": "4cad31f30359c41c6dc6431882b0bd0cec84100c3d86006a883fc071a6993666", + "transformation": "byte-for-byte copy" + } + ], + "source_batch": "swl_s02_s05_v0_1", + "study_id": "SWL-S04" + }, + { + "origins": [ + { + "companion_path": "data/swl_s05_strategy_feedback.csv", + "companion_sha256": "9100b086a0b569bf2f9258c546e9b56b33e64c028b2e1e3b2888305b3c428445", + "source_path": "psych_design_companion/swl_s02_s05_v0_1/data/swl_s05_strategy_feedback.csv", + "source_sha256": "9100b086a0b569bf2f9258c546e9b56b33e64c028b2e1e3b2888305b3c428445", + "transformation": "byte-for-byte copy" + }, + { + "companion_path": "design/SWL-S05_DESIGN_CONTRACT.json", + "companion_sha256": "ad9f6347ac8167c7e9290eb7e806849009f0afaf8b982c2c5c655f206e4e40d8", + "source_path": "psych_design_companion/swl_s02_s05_v0_1/contracts/SWL-S05_DESIGN_CONTRACT.json", + "source_sha256": "ad9f6347ac8167c7e9290eb7e806849009f0afaf8b982c2c5c655f206e4e40d8", + "transformation": "byte-for-byte copy" + }, + { + "companion_path": "reference/results/SWL-S05_PYTHON_RESULT_RECEIPT.json", + "companion_sha256": "1451c6a584723efdfbc98d9d61120fdceb691dd161a3dd582e956d205ff48710", + "source_path": "psych_design_companion/swl_s02_s05_v0_1/evidence/SWL-S05/PYTHON_RESULT_RECEIPT.json", + "source_sha256": "1451c6a584723efdfbc98d9d61120fdceb691dd161a3dd582e956d205ff48710", + "transformation": "byte-for-byte copy" + } + ], + "source_batch": "swl_s02_s05_v0_1", + "study_id": "SWL-S05" + }, + { + "origins": [ + { + "companion_path": "data/swl_s06_confidence_trajectory.csv", + "companion_sha256": "a95ef37c89f3523e59822d6729746d2499ea95acc508ca25824d3e073ed258a2", + "source_path": "psych_design_companion/swl_s01_s06_s07_v0_1/data/swl_s06_confidence_trajectory.csv", + "source_sha256": "a95ef37c89f3523e59822d6729746d2499ea95acc508ca25824d3e073ed258a2", + "transformation": "byte-for-byte copy" + }, + { + "companion_path": "design/SWL-S06_DESIGN_CONTRACT.json", + "companion_sha256": "309143adb85b2c06517d38f031234c18370f796393983b5a89fd329b8da00061", + "source_path": "psych_design_companion/swl_s01_s06_s07_v0_1/contracts/SWL-S06_DESIGN_CONTRACT.json", + "source_sha256": "309143adb85b2c06517d38f031234c18370f796393983b5a89fd329b8da00061", + "transformation": "byte-for-byte copy" + }, + { + "companion_path": "reference/results/SWL-S06_PYTHON_RESULT_RECEIPT.json", + "companion_sha256": "cf271c251f23239159e087bdfed6cddeb4a442022e6f6fb9d21fd418fc6b039d", + "source_path": "psych_design_companion/swl_s01_s06_s07_v0_1/evidence/SWL-S06/PYTHON_RESULT_RECEIPT.json", + "source_sha256": "cf271c251f23239159e087bdfed6cddeb4a442022e6f6fb9d21fd418fc6b039d", + "transformation": "byte-for-byte copy" + } + ], + "source_batch": "swl_s01_s06_s07_v0_1", + "study_id": "SWL-S06" + }, + { + "origins": [ + { + "companion_path": "data/swl_s07_baseline_adjusted_intervention.csv", + "companion_sha256": "08925df34996fd9f75a4c5bd41f0de82c655b05212f2cd21435ba70349db52fc", + "source_path": "psych_design_companion/swl_s01_s06_s07_v0_1/data/swl_s07_baseline_adjusted_intervention.csv", + "source_sha256": "08925df34996fd9f75a4c5bd41f0de82c655b05212f2cd21435ba70349db52fc", + "transformation": "byte-for-byte copy" + }, + { + "companion_path": "design/SWL-S07_DESIGN_CONTRACT.json", + "companion_sha256": "b89086a679277b1652999fde987ccb89ce78ec6f44b3186a3f0c9cb694f39048", + "source_path": "psych_design_companion/swl_s01_s06_s07_v0_1/contracts/SWL-S07_DESIGN_CONTRACT.json", + "source_sha256": "b89086a679277b1652999fde987ccb89ce78ec6f44b3186a3f0c9cb694f39048", + "transformation": "byte-for-byte copy" + }, + { + "companion_path": "reference/results/SWL-S07_PYTHON_RESULT_RECEIPT.json", + "companion_sha256": "a32a1674f8673e6633768ff6fe7f82ea1e3bd5d14eb6bc8949ac7b5b3bd8e9c6", + "source_path": "psych_design_companion/swl_s01_s06_s07_v0_1/evidence/SWL-S07/PYTHON_RESULT_RECEIPT.json", + "source_sha256": "a32a1674f8673e6633768ff6fe7f82ea1e3bd5d14eb6bc8949ac7b5b3bd8e9c6", + "transformation": "byte-for-byte copy" + } + ], + "source_batch": "swl_s01_s06_s07_v0_1", + "study_id": "SWL-S07" + }, + { + "origins": [ + { + "companion_path": "data/swl_s08_longitudinal_treatment.csv", + "companion_sha256": "a37e797a5d67e38d91d7f88d06014bd351559efb22b21edb3cc75ff95db82556", + "source_path": "psych_design_companion/swl_s08_s10_v0_1/data/swl_s08_longitudinal_treatment.csv", + "source_sha256": "a37e797a5d67e38d91d7f88d06014bd351559efb22b21edb3cc75ff95db82556", + "transformation": "byte-for-byte copy" + }, + { + "companion_path": "design/SWL-S08_DESIGN_CONTRACT.json", + "companion_sha256": "f1d3faeb7c4e10e9836e9ac75f1bb18aa8adbe12f916ce70fe95ed73f9c38c42", + "source_path": "psych_design_companion/swl_s08_s10_v0_1/contracts/SWL-S08_DESIGN_CONTRACT.json", + "source_sha256": "f1d3faeb7c4e10e9836e9ac75f1bb18aa8adbe12f916ce70fe95ed73f9c38c42", + "transformation": "byte-for-byte copy" + }, + { + "companion_path": "reference/results/SWL-S08_PYTHON_RESULT_RECEIPT.json", + "companion_sha256": "22648086c0775cb852745e85e5b4c9569eb6f9c1011b1dbe2bbf0ca1e39385de", + "source_path": "psych_design_companion/swl_s08_s10_v0_1/evidence/SWL-S08/PYTHON_RESULT_RECEIPT.json", + "source_sha256": "22648086c0775cb852745e85e5b4c9569eb6f9c1011b1dbe2bbf0ca1e39385de", + "transformation": "byte-for-byte copy" + } + ], + "source_batch": "swl_s08_s10_v0_1", + "study_id": "SWL-S08" + }, + { + "origins": [ + { + "companion_path": "data/swl_s09_outcome_distribution_extension.csv", + "companion_sha256": "cacafff8486624e4362629a1b7cf026aa347c7f48620b80029b6a1cfd0d31e16", + "source_path": "psych_design_companion/swl_s08_s10_v0_1/data/swl_s09_outcome_distribution_extension.csv", + "source_sha256": "cacafff8486624e4362629a1b7cf026aa347c7f48620b80029b6a1cfd0d31e16", + "transformation": "byte-for-byte copy" + }, + { + "companion_path": "design/SWL-S09_DESIGN_CONTRACT.json", + "companion_sha256": "a3b01225008425235bea1a2f7632367a7a7746ce4f43594908a13fa59a2709de", + "source_path": "psych_design_companion/swl_s08_s10_v0_1/contracts/SWL-S09_DESIGN_CONTRACT.json", + "source_sha256": "a3b01225008425235bea1a2f7632367a7a7746ce4f43594908a13fa59a2709de", + "transformation": "byte-for-byte copy" + }, + { + "companion_path": "reference/results/SWL-S09_PYTHON_RESULT_RECEIPT.json", + "companion_sha256": "d972997d3d9b3c1de98d9e34046ece2c6ec84a575d5d186d0fa886a08cf79b0b", + "source_path": "psych_design_companion/swl_s08_s10_v0_1/evidence/SWL-S09/PYTHON_RESULT_RECEIPT.json", + "source_sha256": "d972997d3d9b3c1de98d9e34046ece2c6ec84a575d5d186d0fa886a08cf79b0b", + "transformation": "byte-for-byte copy" + } + ], + "source_batch": "swl_s08_s10_v0_1", + "study_id": "SWL-S09" + }, + { + "origins": [ + { + "companion_path": "data/swl_s10_replication_program.csv", + "companion_sha256": "4cb8239f978975c10191cbcdf47d17c62944e45dc2540cbc76ef5d4ccd366e36", + "source_path": "psych_design_companion/swl_s08_s10_v0_1/data/swl_s10_replication_program.csv", + "source_sha256": "4cb8239f978975c10191cbcdf47d17c62944e45dc2540cbc76ef5d4ccd366e36", + "transformation": "byte-for-byte copy" + }, + { + "companion_path": "design/SWL-S10_DESIGN_CONTRACT.json", + "companion_sha256": "cd690cb58bd797ddc82677894df95a014ec664eea2f28c4f748f51ffe6ff38c7", + "source_path": "psych_design_companion/swl_s08_s10_v0_1/contracts/SWL-S10_DESIGN_CONTRACT.json", + "source_sha256": "cd690cb58bd797ddc82677894df95a014ec664eea2f28c4f748f51ffe6ff38c7", + "transformation": "byte-for-byte copy" + }, + { + "companion_path": "reference/results/SWL-S10_PYTHON_RESULT_RECEIPT.json", + "companion_sha256": "177a8e980db081f716a623ba834011965972def4b5214da89f94ba24ae410af1", + "source_path": "psych_design_companion/swl_s08_s10_v0_1/evidence/SWL-S10/PYTHON_RESULT_RECEIPT.json", + "source_sha256": "177a8e980db081f716a623ba834011965972def4b5214da89f94ba24ae410af1", + "transformation": "byte-for-byte copy" + } + ], + "source_batch": "swl_s08_s10_v0_1", + "study_id": "SWL-S10" + } + ], + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/STUDY_INDEX.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/STUDY_INDEX.json new file mode 100644 index 0000000..7ed0059 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/STUDY_INDEX.json @@ -0,0 +1,695 @@ +{ + "companion_title": "Psychological Statistics by Design \u2014 Executable Companion", + "companion_version": "v0.1", + "first_result_study": "SWL-S02", + "package_release_candidate": "0.26.0", + "real_data_authorized": false, + "schema_version": "psych-design-study-index-v0.1", + "status": "release_candidate_implemented_not_public", + "studies": [ + { + "apa_source_maps": [ + "reference/apa_source_maps/SWL-S01/APA_RESULT_SOURCE_MAP_CORRELATION.json", + "reference/apa_source_maps/SWL-S01/APA_RESULT_SOURCE_MAP_REGRESSION.json" + ], + "book_chapters": [ + "11", + "12" + ], + "claim_type": "association and within-domain prediction", + "comparison_receipt": "outputs/swl_s01/verification_receipt.json", + "dataset_path": "data/swl_s01_student_well_being_survey.csv", + "design_contract_path": "design/SWL-S01_DESIGN_CONTRACT.json", + "estimands_and_direction": [ + "Pearson association between sleep quality and academic confidence", + "regression coefficients predicting assessment performance within the observed predictor domain" + ], + "expected_rows": 120, + "expected_unique_keys": 120, + "expected_unique_units": 120, + "factor_levels": {}, + "figure_specs": [ + "figures/specs/SWL-S01/FIGURE_SPEC_CORRELATION.json", + "figures/specs/SWL-S01/FIGURE_SPEC_REGRESSION.json" + ], + "first_fields_to_inspect": [ + "n", + "correlation_r_sleep_confidence", + "regression_r_squared", + "study_hours_b", + "confidence_b" + ], + "generated_figure_manifest": "outputs/swl_s01/figures/FIGURE_MANIFEST.json", + "main_prohibited_inference": "No intervention or causal claim is authorized from the cross-sectional survey.", + "matched_limitations": [ + "reference/matched_limitations/SWL-S01/MATCHED_LIMITATION_CORRELATION.json", + "reference/matched_limitations/SWL-S01/MATCHED_LIMITATION_REGRESSION.json" + ], + "measurement_map_path": "measurement/SWL-S01_MEASUREMENT_MAP.json", + "packet_command": "make packet STUDY=SWL-S01", + "python_result_receipt": "outputs/swl_s01/python_result.json", + "python_script": "scripts/python/swl_s01_survey_association_prediction.py", + "r_launcher": "scripts/r/run_study.R", + "r_result": "outputs/swl_s01/r_result.csv", + "r_script": "scripts/r/batches/swl_s01_s06_s07_v0_1/swl_s01.R", + "reader_command": "make study STUDY=SWL-S01", + "reference_python_result": "reference/results/SWL-S01_PYTHON_RESULT_RECEIPT.json", + "research_questions": [ + "How are sleep quality and academic confidence associated in the synthetic survey records?", + "How well do weekly study hours and academic confidence predict synthetic assessment performance within the registered predictor domain?" + ], + "reviewer_packet": "outputs/swl_s01/reviewer_packet/MANIFEST.json", + "runtime_apa_source_map": "outputs/swl_s01/apa_source_map.json", + "runtime_matched_limitation": "outputs/swl_s01/matched_limitation.md", + "status": "release_candidate_implemented_not_public", + "study_id": "SWL-S01", + "unique_key": [ + "participant_id" + ], + "unit_of_analysis": "student", + "verification_category": "V2", + "verification_command": "make verify-study STUDY=SWL-S01", + "verification_script": "scripts/verification/verify_swl_s01.py" + }, + { + "apa_source_maps": [ + "reference/apa_source_maps/SWL-S02/APA_RESULT_SOURCE_MAP.json" + ], + "book_chapters": [ + "6" + ], + "claim_type": "independent-group mean difference with conditional causal scope", + "comparison_receipt": "outputs/swl_s02/verification_receipt.json", + "dataset_path": "data/swl_s02_structured_study_routine.csv", + "design_contract_path": "design/SWL-S02_DESIGN_CONTRACT.json", + "estimands_and_direction": [ + "structured_routine minus standard_routine mean post-session performance" + ], + "expected_rows": 64, + "expected_unique_keys": 64, + "expected_unique_units": 64, + "factor_levels": { + "study_routine_group": [ + "standard_routine", + "structured_routine" + ] + }, + "figure_specs": [ + "figures/specs/SWL-S02/FIGURE_SPEC.json" + ], + "first_fields_to_inspect": [ + "n_standard", + "n_structured", + "mean_standard", + "mean_structured", + "mean_difference_structured_minus_standard", + "ci_95_low", + "ci_95_high", + "welch_t", + "p_value_two_sided", + "cohen_d_pooled" + ], + "generated_figure_manifest": "outputs/swl_s02/figures/FIGURE_MANIFEST.json", + "main_prohibited_inference": "Do not generalize beyond the synthetic pilot or ignore assignment and implementation conditions.", + "matched_limitations": [ + "reference/matched_limitations/SWL-S02/MATCHED_LIMITATION.json" + ], + "measurement_map_path": "measurement/SWL-S02_MEASUREMENT_MAP.json", + "packet_command": "make packet STUDY=SWL-S02", + "python_result_receipt": "outputs/swl_s02/python_result.json", + "python_script": "scripts/python/swl_s02_independent_groups.py", + "r_launcher": "scripts/r/run_study.R", + "r_result": "outputs/swl_s02/r_result.csv", + "r_script": "scripts/r/batches/swl_s02_s05_v0_1/swl_s02.R", + "reader_command": "make study STUDY=SWL-S02", + "reference_python_result": "reference/results/SWL-S02_PYTHON_RESULT_RECEIPT.json", + "research_questions": [ + "Do students assigned to a structured study routine differ in post-session performance from students using a standard routine?" + ], + "reviewer_packet": "outputs/swl_s02/reviewer_packet/MANIFEST.json", + "runtime_apa_source_map": "outputs/swl_s02/apa_source_map.json", + "runtime_matched_limitation": "outputs/swl_s02/matched_limitation.md", + "status": "release_candidate_implemented_not_public", + "study_id": "SWL-S02", + "unique_key": [ + "participant_id" + ], + "unit_of_analysis": "student", + "verification_category": "V2", + "verification_command": "make verify-study STUDY=SWL-S02", + "verification_script": "scripts/verification/verify_swl_s02.py" + }, + { + "apa_source_maps": [ + "reference/apa_source_maps/SWL-S03/APA_RESULT_SOURCE_MAP.json" + ], + "book_chapters": [ + "7" + ], + "claim_type": "within-person paired change without a unique causal attribution", + "comparison_receipt": "outputs/swl_s03/verification_receipt.json", + "dataset_path": "data/swl_s03_skills_workshop_pre_post.csv", + "design_contract_path": "design/SWL-S03_DESIGN_CONTRACT.json", + "estimands_and_direction": [ + "post minus pre mean paired change" + ], + "expected_rows": 96, + "expected_unique_keys": 96, + "expected_unique_units": 48, + "factor_levels": { + "occasion": [ + "pre", + "post" + ] + }, + "figure_specs": [ + "figures/specs/SWL-S03/FIGURE_SPEC.json" + ], + "first_fields_to_inspect": [ + "n_paired", + "mean_pre", + "mean_post", + "mean_change_post_minus_pre", + "sd_change", + "paired_t", + "p_value_two_sided", + "cohen_dz" + ], + "generated_figure_manifest": "outputs/swl_s03/figures/FIGURE_MANIFEST.json", + "main_prohibited_inference": "Do not identify the workshop as the unique cause without a control condition.", + "matched_limitations": [ + "reference/matched_limitations/SWL-S03/MATCHED_LIMITATION.json" + ], + "measurement_map_path": "measurement/SWL-S03_MEASUREMENT_MAP.json", + "packet_command": "make packet STUDY=SWL-S03", + "python_result_receipt": "outputs/swl_s03/python_result.json", + "python_script": "scripts/python/swl_s03_paired_change.py", + "r_launcher": "scripts/r/run_study.R", + "r_result": "outputs/swl_s03/r_result.csv", + "r_script": "scripts/r/batches/swl_s02_s05_v0_1/swl_s03.R", + "reader_command": "make study STUDY=SWL-S03", + "reference_python_result": "reference/results/SWL-S03_PYTHON_RESULT_RECEIPT.json", + "research_questions": [ + "How does academic confidence change from before to after a skills workshop for students with linked observations?" + ], + "reviewer_packet": "outputs/swl_s03/reviewer_packet/MANIFEST.json", + "runtime_apa_source_map": "outputs/swl_s03/apa_source_map.json", + "runtime_matched_limitation": "outputs/swl_s03/matched_limitation.md", + "status": "release_candidate_implemented_not_public", + "study_id": "SWL-S03", + "unique_key": [ + "participant_id", + "occasion" + ], + "unit_of_analysis": "student", + "verification_category": "V2", + "verification_command": "make verify-study STUDY=SWL-S03", + "verification_script": "scripts/verification/verify_swl_s03.py" + }, + { + "apa_source_maps": [ + "reference/apa_source_maps/SWL-S04/APA_RESULT_SOURCE_MAP.json" + ], + "book_chapters": [ + "8" + ], + "claim_type": "omnibus several-group comparison and two registered planned contrasts", + "comparison_receipt": "outputs/swl_s04/verification_receipt.json", + "dataset_path": "data/swl_s04_three_condition_support.csv", + "design_contract_path": "design/SWL-S04_DESIGN_CONTRACT.json", + "estimands_and_direction": [ + "guided-condition average minus standard support", + "guided practice plus feedback minus guided practice" + ], + "expected_rows": 90, + "expected_unique_keys": 90, + "expected_unique_units": 90, + "factor_levels": { + "support_condition": [ + "standard_support", + "guided_practice", + "guided_practice_plus_feedback" + ] + }, + "figure_specs": [ + "figures/specs/SWL-S04/FIGURE_SPEC.json" + ], + "first_fields_to_inspect": [ + "n_total", + "mean_standard_support", + "mean_guided_practice", + "mean_guided_practice_plus_feedback", + "anova_f", + "contrast_guided_average_minus_standard_estimate", + "contrast_feedback_increment_estimate" + ], + "generated_figure_manifest": "outputs/swl_s04/figures/FIGURE_MANIFEST.json", + "main_prohibited_inference": "Do not replace the registered comparison family with post-result pairwise fishing.", + "matched_limitations": [ + "reference/matched_limitations/SWL-S04/MATCHED_LIMITATION.json" + ], + "measurement_map_path": "measurement/SWL-S04_MEASUREMENT_MAP.json", + "packet_command": "make packet STUDY=SWL-S04", + "python_result_receipt": "outputs/swl_s04/python_result.json", + "python_script": "scripts/python/swl_s04_planned_contrasts.py", + "r_launcher": "scripts/r/run_study.R", + "r_result": "outputs/swl_s04/r_result.csv", + "r_script": "scripts/r/batches/swl_s02_s05_v0_1/swl_s04.R", + "reader_command": "make study STUDY=SWL-S04", + "reference_python_result": "reference/results/SWL-S04_PYTHON_RESULT_RECEIPT.json", + "research_questions": [ + "Do assessment outcomes differ across standard support, guided practice, and guided practice plus feedback, and which planned contrasts answer the substantive questions?" + ], + "reviewer_packet": "outputs/swl_s04/reviewer_packet/MANIFEST.json", + "runtime_apa_source_map": "outputs/swl_s04/apa_source_map.json", + "runtime_matched_limitation": "outputs/swl_s04/matched_limitation.md", + "status": "release_candidate_implemented_not_public", + "study_id": "SWL-S04", + "unique_key": [ + "participant_id" + ], + "unit_of_analysis": "student", + "verification_category": "V2", + "verification_command": "make verify-study STUDY=SWL-S04", + "verification_script": "scripts/verification/verify_swl_s04.py" + }, + { + "apa_source_maps": [ + "reference/apa_source_maps/SWL-S05/APA_RESULT_SOURCE_MAP.json" + ], + "book_chapters": [ + "9" + ], + "claim_type": "factorial interaction and registered simple strategy effects", + "comparison_receipt": "outputs/swl_s05/verification_receipt.json", + "dataset_path": "data/swl_s05_strategy_feedback.csv", + "design_contract_path": "design/SWL-S05_DESIGN_CONTRACT.json", + "estimands_and_direction": [ + "retrieval-practice effect with feedback minus retrieval-practice effect without feedback" + ], + "expected_rows": 96, + "expected_unique_keys": 96, + "expected_unique_units": 96, + "factor_levels": { + "feedback_condition": [ + "no_feedback", + "explanatory_feedback" + ], + "study_strategy": [ + "rereading", + "retrieval_practice" + ] + }, + "figure_specs": [ + "figures/specs/SWL-S05/FIGURE_SPEC.json" + ], + "first_fields_to_inspect": [ + "n_total", + "mean_rereading_no_feedback", + "mean_retrieval_no_feedback", + "mean_rereading_explanatory_feedback", + "mean_retrieval_explanatory_feedback", + "interaction_difference_in_differences", + "interaction_f" + ], + "generated_figure_manifest": "outputs/swl_s05/figures/FIGURE_MANIFEST.json", + "main_prohibited_inference": "Do not report only averaged main effects when the interaction is the registered target.", + "matched_limitations": [ + "reference/matched_limitations/SWL-S05/MATCHED_LIMITATION.json" + ], + "measurement_map_path": "measurement/SWL-S05_MEASUREMENT_MAP.json", + "packet_command": "make packet STUDY=SWL-S05", + "python_result_receipt": "outputs/swl_s05/python_result.json", + "python_script": "scripts/python/swl_s05_factorial_interaction.py", + "r_launcher": "scripts/r/run_study.R", + "r_result": "outputs/swl_s05/r_result.csv", + "r_script": "scripts/r/batches/swl_s02_s05_v0_1/swl_s05.R", + "reader_command": "make study STUDY=SWL-S05", + "reference_python_result": "reference/results/SWL-S05_PYTHON_RESULT_RECEIPT.json", + "research_questions": [ + "Does the effect of study strategy on assessment performance depend on the feedback condition?" + ], + "reviewer_packet": "outputs/swl_s05/reviewer_packet/MANIFEST.json", + "runtime_apa_source_map": "outputs/swl_s05/apa_source_map.json", + "runtime_matched_limitation": "outputs/swl_s05/matched_limitation.md", + "status": "release_candidate_implemented_not_public", + "study_id": "SWL-S05", + "unique_key": [ + "participant_id" + ], + "unit_of_analysis": "student", + "verification_category": "V2", + "verification_command": "make verify-study STUDY=SWL-S05", + "verification_script": "scripts/verification/verify_swl_s05.py" + }, + { + "apa_source_maps": [ + "reference/apa_source_maps/SWL-S06/APA_RESULT_SOURCE_MAP.json" + ], + "book_chapters": [ + "10" + ], + "claim_type": "complete-trajectory occasion comparison with missing-visit disclosure", + "comparison_receipt": "outputs/swl_s06/verification_receipt.json", + "dataset_path": "data/swl_s06_confidence_trajectory.csv", + "design_contract_path": "design/SWL-S06_DESIGN_CONTRACT.json", + "estimands_and_direction": [ + "occasion effect across baseline, post, and follow_up among complete trajectories" + ], + "expected_rows": 180, + "expected_unique_keys": 180, + "expected_unique_units": 60, + "factor_levels": { + "occasion": [ + "baseline", + "post", + "follow_up" + ] + }, + "figure_specs": [ + "figures/specs/SWL-S06/FIGURE_SPEC.json" + ], + "first_fields_to_inspect": [ + "n_scheduled_participants", + "observed_outcomes", + "missing_outcomes", + "n_complete_case", + "mean_baseline_complete_case", + "mean_post_complete_case", + "mean_follow_up_complete_case", + "rm_anova_f" + ], + "generated_figure_manifest": "outputs/swl_s06/figures/FIGURE_MANIFEST.json", + "main_prohibited_inference": "Do not treat complete-case occasion differences as a unique causal effect or hide missing visits.", + "matched_limitations": [ + "reference/matched_limitations/SWL-S06/MATCHED_LIMITATION.json" + ], + "measurement_map_path": "measurement/SWL-S06_MEASUREMENT_MAP.json", + "packet_command": "make packet STUDY=SWL-S06", + "python_result_receipt": "outputs/swl_s06/python_result.json", + "python_script": "scripts/python/swl_s06_repeated_measures.py", + "r_launcher": "scripts/r/run_study.R", + "r_result": "outputs/swl_s06/r_result.csv", + "r_script": "scripts/r/batches/swl_s01_s06_s07_v0_1/swl_s06.R", + "reader_command": "make study STUDY=SWL-S06", + "reference_python_result": "reference/results/SWL-S06_PYTHON_RESULT_RECEIPT.json", + "research_questions": [ + "Does mean academic confidence differ across baseline, post, and follow-up among complete synthetic trajectories?" + ], + "reviewer_packet": "outputs/swl_s06/reviewer_packet/MANIFEST.json", + "runtime_apa_source_map": "outputs/swl_s06/apa_source_map.json", + "runtime_matched_limitation": "outputs/swl_s06/matched_limitation.md", + "status": "release_candidate_implemented_not_public", + "study_id": "SWL-S06", + "unique_key": [ + "participant_id", + "occasion" + ], + "unit_of_analysis": "student", + "verification_category": "V2", + "verification_command": "make verify-study STUDY=SWL-S06", + "verification_script": "scripts/verification/verify_swl_s06.py" + }, + { + "apa_source_maps": [ + "reference/apa_source_maps/SWL-S07/APA_RESULT_SOURCE_MAP.json" + ], + "book_chapters": [ + "13" + ], + "claim_type": "baseline-adjusted group comparison under the registered ANCOVA model", + "comparison_receipt": "outputs/swl_s07/verification_receipt.json", + "dataset_path": "data/swl_s07_baseline_adjusted_intervention.csv", + "design_contract_path": "design/SWL-S07_DESIGN_CONTRACT.json", + "estimands_and_direction": [ + "intervention minus comparison adjusted post-performance difference at the overall baseline mean" + ], + "expected_rows": 90, + "expected_unique_keys": 90, + "expected_unique_units": 90, + "factor_levels": { + "intervention_group": [ + "comparison", + "intervention" + ] + }, + "figure_specs": [ + "figures/specs/SWL-S07/FIGURE_SPEC.json" + ], + "first_fields_to_inspect": [ + "n_total", + "raw_post_mean_comparison", + "raw_post_mean_intervention", + "adjusted_mean_comparison_at_overall_baseline", + "adjusted_mean_intervention_at_overall_baseline", + "adjusted_group_difference_intervention_minus_comparison", + "group_effect_f" + ], + "generated_figure_manifest": "outputs/swl_s07/figures/FIGURE_MANIFEST.json", + "main_prohibited_inference": "Do not claim that covariate adjustment creates randomization or removes unmeasured confounding.", + "matched_limitations": [ + "reference/matched_limitations/SWL-S07/MATCHED_LIMITATION.json" + ], + "measurement_map_path": "measurement/SWL-S07_MEASUREMENT_MAP.json", + "packet_command": "make packet STUDY=SWL-S07", + "python_result_receipt": "outputs/swl_s07/python_result.json", + "python_script": "scripts/python/swl_s07_ancova.py", + "r_launcher": "scripts/r/run_study.R", + "r_result": "outputs/swl_s07/r_result.csv", + "r_script": "scripts/r/batches/swl_s01_s06_s07_v0_1/swl_s07.R", + "reader_command": "make study STUDY=SWL-S07", + "reference_python_result": "reference/results/SWL-S07_PYTHON_RESULT_RECEIPT.json", + "research_questions": [ + "Do the intervention and comparison groups differ in post performance after adjustment to the overall baseline mean?" + ], + "reviewer_packet": "outputs/swl_s07/reviewer_packet/MANIFEST.json", + "runtime_apa_source_map": "outputs/swl_s07/apa_source_map.json", + "runtime_matched_limitation": "outputs/swl_s07/matched_limitation.md", + "status": "release_candidate_implemented_not_public", + "study_id": "SWL-S07", + "unique_key": [ + "participant_id" + ], + "unit_of_analysis": "student", + "verification_category": "V2", + "verification_command": "make verify-study STUDY=SWL-S07", + "verification_script": "scripts/verification/verify_swl_s07.py" + }, + { + "apa_source_maps": [ + "reference/apa_source_maps/SWL-S08/APA_RESULT_SOURCE_MAP.json" + ], + "book_chapters": [ + "14" + ], + "claim_type": "longitudinal treatment-versus-control differential change", + "comparison_receipt": "outputs/swl_s08/verification_receipt.json", + "dataset_path": "data/swl_s08_longitudinal_treatment.csv", + "design_contract_path": "design/SWL-S08_DESIGN_CONTRACT.json", + "estimands_and_direction": [ + "additional treatment-group change from baseline to post", + "additional treatment-group change from baseline to follow-up" + ], + "expected_rows": 168, + "expected_unique_keys": 168, + "expected_unique_units": 60, + "factor_levels": { + "occasion": [ + "baseline", + "post", + "followup" + ], + "treatment_group": [ + "control", + "treatment" + ] + }, + "figure_specs": [ + "figures/specs/SWL-S08/FIGURE_SPEC.json" + ], + "first_fields_to_inspect": [ + "n_participants", + "observed_rows", + "missing_visits", + "treatment_post_b", + "treatment_post_se", + "treatment_followup_b", + "treatment_followup_se" + ], + "generated_figure_manifest": "outputs/swl_s08/figures/FIGURE_MANIFEST.json", + "main_prohibited_inference": "Do not claim that a mixed model makes outcome-dependent missingness harmless or proves a real treatment effect.", + "matched_limitations": [ + "reference/matched_limitations/SWL-S08/MATCHED_LIMITATION.json" + ], + "measurement_map_path": "measurement/SWL-S08_MEASUREMENT_MAP.json", + "packet_command": "make packet STUDY=SWL-S08", + "python_result_receipt": "outputs/swl_s08/python_result.json", + "python_script": "scripts/python/swl_s08_mixed_model.py", + "r_launcher": "scripts/r/run_study.R", + "r_result": "outputs/swl_s08/r_result.csv", + "r_script": "scripts/r/batches/swl_s08_s10_v0_1/swl_s08.R", + "reader_command": "make study STUDY=SWL-S08", + "reference_python_result": "reference/results/SWL-S08_PYTHON_RESULT_RECEIPT.json", + "research_questions": [ + "Does change in synthetic well-being from baseline differ between treatment and control when participants have unequal numbers of observed visits?" + ], + "reviewer_packet": "outputs/swl_s08/reviewer_packet/MANIFEST.json", + "runtime_apa_source_map": "outputs/swl_s08/apa_source_map.json", + "runtime_matched_limitation": "outputs/swl_s08/matched_limitation.md", + "status": "release_candidate_implemented_not_public", + "study_id": "SWL-S08", + "unique_key": [ + "participant_id", + "occasion" + ], + "unit_of_analysis": "student", + "verification_category": "V3_V4", + "verification_command": "make verify-study STUDY=SWL-S08", + "verification_script": "scripts/verification/verify_swl_s08.py" + }, + { + "apa_source_maps": [ + "reference/apa_source_maps/SWL-S09/APA_RESULT_SOURCE_MAP_CATEGORICAL.json", + "reference/apa_source_maps/SWL-S09/APA_RESULT_SOURCE_MAP_RANK.json" + ], + "book_chapters": [ + "15" + ], + "claim_type": "categorical association and rank-based distributional shift", + "comparison_receipt": "outputs/swl_s09/verification_receipt.json", + "dataset_path": "data/swl_s09_outcome_distribution_extension.csv", + "design_contract_path": "design/SWL-S09_DESIGN_CONTRACT.json", + "estimands_and_direction": [ + "intervention minus comparison improvement-risk difference", + "distributional shift in recovery burden with intervention oriented as lower burden" + ], + "expected_rows": 160, + "expected_unique_keys": 160, + "expected_unique_units": 160, + "factor_levels": { + "clinically_improved": [ + "yes", + "no" + ], + "support_condition": [ + "comparison", + "intervention" + ] + }, + "figure_specs": [ + "figures/specs/SWL-S09/FIGURE_SPEC_CATEGORICAL.json", + "figures/specs/SWL-S09/FIGURE_SPEC_RANK.json" + ], + "first_fields_to_inspect": [ + "n_total", + "comparison_improved_proportion", + "intervention_improved_proportion", + "chi_square", + "cramers_v", + "comparison_burden_median", + "intervention_burden_median", + "mann_whitney_u_intervention" + ], + "generated_figure_manifest": "outputs/swl_s09/figures/FIGURE_MANIFEST.json", + "main_prohibited_inference": "Do not treat either registered outcome analysis as a generic repair for failed normality.", + "matched_limitations": [ + "reference/matched_limitations/SWL-S09/MATCHED_LIMITATION_CATEGORICAL.json", + "reference/matched_limitations/SWL-S09/MATCHED_LIMITATION_RANK.json" + ], + "measurement_map_path": "measurement/SWL-S09_MEASUREMENT_MAP.json", + "packet_command": "make packet STUDY=SWL-S09", + "python_result_receipt": "outputs/swl_s09/python_result.json", + "python_script": "scripts/python/swl_s09_distribution_extension.py", + "r_launcher": "scripts/r/run_study.R", + "r_result": "outputs/swl_s09/r_result.csv", + "r_script": "scripts/r/batches/swl_s08_s10_v0_1/swl_s09.R", + "reader_command": "make study STUDY=SWL-S09", + "reference_python_result": "reference/results/SWL-S09_PYTHON_RESULT_RECEIPT.json", + "research_questions": [ + "Does clinical-improvement status differ by support condition, and does positive-skew recovery burden shift between the two independent conditions?" + ], + "reviewer_packet": "outputs/swl_s09/reviewer_packet/MANIFEST.json", + "runtime_apa_source_map": "outputs/swl_s09/apa_source_map.json", + "runtime_matched_limitation": "outputs/swl_s09/matched_limitation.md", + "status": "release_candidate_implemented_not_public", + "study_id": "SWL-S09", + "unique_key": [ + "participant_id" + ], + "unit_of_analysis": "student", + "verification_category": "V2", + "verification_command": "make verify-study STUDY=SWL-S09", + "verification_script": "scripts/verification/verify_swl_s09.py" + }, + { + "apa_source_maps": [ + "reference/apa_source_maps/SWL-S10/APA_RESULT_SOURCE_MAP_META.json", + "reference/apa_source_maps/SWL-S10/APA_RESULT_SOURCE_MAP_POWER.json" + ], + "book_chapters": [ + "16" + ], + "claim_type": "prospective power planning and fixed-effect cumulative evidence", + "comparison_receipt": "outputs/swl_s10/verification_receipt.json", + "dataset_path": "data/swl_s10_replication_program.csv", + "design_contract_path": "design/SWL-S10_DESIGN_CONTRACT.json", + "estimands_and_direction": [ + "per-group sample size for d=0.40 at 80% power", + "fixed-effect pooled standardized mean difference across six replications" + ], + "expected_rows": 6, + "expected_unique_keys": 6, + "expected_unique_units": 6, + "factor_levels": { + "replication_type": [ + "conceptual", + "direct" + ] + }, + "figure_specs": [ + "figures/specs/SWL-S10/FIGURE_SPEC.json" + ], + "first_fields_to_inspect": [ + "planning_n_per_group_ceiling_80", + "study_count", + "fixed_effect_pooled_d", + "fixed_effect_se", + "fixed_effect_ci_95_low", + "fixed_effect_ci_95_high", + "cochran_q", + "i_squared_percent" + ], + "generated_figure_manifest": "outputs/swl_s10/figures/FIGURE_MANIFEST.json", + "main_prohibited_inference": "Do not interpret planned power or a fixed-effect synthesis as proof against bias, heterogeneity, or selective evidence.", + "matched_limitations": [ + "reference/matched_limitations/SWL-S10/MATCHED_LIMITATION_META.json", + "reference/matched_limitations/SWL-S10/MATCHED_LIMITATION_POWER.json" + ], + "measurement_map_path": "measurement/SWL-S10_MEASUREMENT_MAP.json", + "packet_command": "make packet STUDY=SWL-S10", + "python_result_receipt": "outputs/swl_s10/python_result.json", + "python_script": "scripts/python/swl_s10_power_replication.py", + "r_launcher": "scripts/r/run_study.R", + "r_result": "outputs/swl_s10/r_result.csv", + "r_script": "scripts/r/batches/swl_s08_s10_v0_1/swl_s10.R", + "reader_command": "make study STUDY=SWL-S10", + "reference_python_result": "reference/results/SWL-S10_PYTHON_RESULT_RECEIPT.json", + "research_questions": [ + "What per-group sample size follows from the registered d=0.40 power target, and what fixed-effect estimate summarizes the six completed synthetic replications?" + ], + "reviewer_packet": "outputs/swl_s10/reviewer_packet/MANIFEST.json", + "runtime_apa_source_map": "outputs/swl_s10/apa_source_map.json", + "runtime_matched_limitation": "outputs/swl_s10/matched_limitation.md", + "status": "release_candidate_implemented_not_public", + "study_id": "SWL-S10", + "unique_key": [ + "study_id" + ], + "unit_of_analysis": "completed synthetic replication", + "verification_category": "V2", + "verification_command": "make verify-study STUDY=SWL-S10", + "verification_script": "scripts/verification/verify_swl_s10.py" + } + ], + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/VERIFICATION_POLICY.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/VERIFICATION_POLICY.json new file mode 100644 index 0000000..253d7de --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/VERIFICATION_POLICY.json @@ -0,0 +1,346 @@ +{ + "independent_r_required_for_complete_verification": true, + "policy": "Compare only registered common estimands. Do not force V3/V4 software-specific inference into false equality.", + "schema_version": "psych-design-verification-policy-v0.1", + "studies": [ + { + "absolute_tolerance_by_field": {}, + "default_absolute_tolerance": 1e-07, + "independent_r_script": "scripts/r/batches/swl_s01_s06_s07_v0_1/swl_s01.R", + "parity_fields": [ + "n", + "correlation_r_sleep_confidence", + "correlation_t", + "correlation_df", + "correlation_p_two_sided", + "correlation_ci_95_low", + "correlation_ci_95_high", + "regression_df_model", + "regression_df_residual", + "regression_f", + "regression_p", + "regression_r_squared", + "regression_adjusted_r_squared", + "regression_rmse", + "intercept_b", + "intercept_se", + "intercept_t", + "intercept_p", + "intercept_ci_95_low", + "intercept_ci_95_high", + "study_hours_b", + "study_hours_se", + "study_hours_t", + "study_hours_p", + "study_hours_ci_95_low", + "study_hours_ci_95_high", + "confidence_b", + "confidence_se", + "confidence_t", + "confidence_p", + "confidence_ci_95_low", + "confidence_ci_95_high" + ], + "study_id": "SWL-S01", + "verification_category": "V2" + }, + { + "absolute_tolerance_by_field": {}, + "default_absolute_tolerance": 1e-07, + "independent_r_script": "scripts/r/batches/swl_s02_s05_v0_1/swl_s02.R", + "parity_fields": [ + "ci_95_high", + "ci_95_low", + "cohen_d_pooled", + "mean_difference_structured_minus_standard", + "mean_standard", + "mean_structured", + "n_standard", + "n_structured", + "p_value_two_sided", + "sd_standard", + "sd_structured", + "welch_df", + "welch_t" + ], + "study_id": "SWL-S02", + "verification_category": "V2" + }, + { + "absolute_tolerance_by_field": {}, + "default_absolute_tolerance": 1e-07, + "independent_r_script": "scripts/r/batches/swl_s02_s05_v0_1/swl_s03.R", + "parity_fields": [ + "ci_95_high", + "ci_95_low", + "cohen_dz", + "df", + "mean_change_post_minus_pre", + "mean_post", + "mean_pre", + "n_paired", + "p_value_two_sided", + "paired_t", + "sd_change" + ], + "study_id": "SWL-S03", + "verification_category": "V2" + }, + { + "absolute_tolerance_by_field": {}, + "default_absolute_tolerance": 1e-07, + "independent_r_script": "scripts/r/batches/swl_s02_s05_v0_1/swl_s04.R", + "parity_fields": [ + "anova_f", + "contrast_feedback_increment_estimate", + "contrast_feedback_increment_p", + "contrast_feedback_increment_p_holm", + "contrast_feedback_increment_t", + "contrast_guided_average_minus_standard_estimate", + "contrast_guided_average_minus_standard_p", + "contrast_guided_average_minus_standard_p_holm", + "contrast_guided_average_minus_standard_t", + "df_between", + "df_within", + "eta_squared", + "mean_guided_practice", + "mean_guided_practice_plus_feedback", + "mean_standard_support", + "n_guided_practice", + "n_guided_practice_plus_feedback", + "n_standard_support", + "n_total", + "p_value" + ], + "study_id": "SWL-S04", + "verification_category": "V2" + }, + { + "absolute_tolerance_by_field": {}, + "default_absolute_tolerance": 1e-07, + "independent_r_script": "scripts/r/batches/swl_s02_s05_v0_1/swl_s05.R", + "parity_fields": [ + "df_effect", + "df_within", + "feedback_f", + "feedback_p", + "feedback_partial_eta_squared", + "interaction_difference_in_differences", + "interaction_f", + "interaction_p", + "interaction_partial_eta_squared", + "mean_rereading_explanatory_feedback", + "mean_rereading_no_feedback", + "mean_retrieval_explanatory_feedback", + "mean_retrieval_no_feedback", + "n_per_cell", + "n_total", + "simple_strategy_explanatory_feedback_estimate", + "simple_strategy_explanatory_feedback_p", + "simple_strategy_explanatory_feedback_t", + "simple_strategy_no_feedback_estimate", + "simple_strategy_no_feedback_p", + "simple_strategy_no_feedback_t", + "strategy_f", + "strategy_p", + "strategy_partial_eta_squared" + ], + "study_id": "SWL-S05", + "verification_category": "V2" + }, + { + "absolute_tolerance_by_field": {}, + "default_absolute_tolerance": 1e-07, + "independent_r_script": "scripts/r/batches/swl_s01_s06_s07_v0_1/swl_s06.R", + "parity_fields": [ + "n_scheduled_participants", + "scheduled_rows", + "observed_outcomes", + "missing_outcomes", + "missing_baseline", + "missing_post", + "missing_follow_up", + "n_complete_case", + "n_incomplete_participants", + "mean_baseline_complete_case", + "mean_post_complete_case", + "mean_follow_up_complete_case", + "mean_change_post_minus_baseline", + "mean_change_follow_up_minus_baseline", + "rm_anova_f", + "rm_anova_df_occasion", + "rm_anova_df_error", + "rm_anova_p", + "greenhouse_geisser_epsilon", + "greenhouse_geisser_df_occasion", + "greenhouse_geisser_df_error", + "greenhouse_geisser_p", + "partial_eta_squared" + ], + "study_id": "SWL-S06", + "verification_category": "V2" + }, + { + "absolute_tolerance_by_field": {}, + "default_absolute_tolerance": 1e-07, + "independent_r_script": "scripts/r/batches/swl_s01_s06_s07_v0_1/swl_s07.R", + "parity_fields": [ + "n_total", + "n_comparison", + "n_intervention", + "baseline_overall_mean", + "raw_baseline_mean_comparison", + "raw_baseline_mean_intervention", + "raw_post_mean_comparison", + "raw_post_mean_intervention", + "adjusted_mean_comparison_at_overall_baseline", + "adjusted_mean_intervention_at_overall_baseline", + "adjusted_group_difference_intervention_minus_comparison", + "group_effect_se", + "group_effect_t", + "group_effect_p", + "group_effect_ci_95_low", + "group_effect_ci_95_high", + "group_effect_f", + "group_effect_df", + "model_df_error", + "group_partial_eta_squared", + "baseline_slope", + "baseline_slope_se", + "baseline_slope_t", + "baseline_slope_p", + "baseline_slope_ci_95_low", + "baseline_slope_ci_95_high", + "model_r_squared", + "model_adjusted_r_squared", + "homogeneity_interaction_b", + "homogeneity_interaction_se", + "homogeneity_interaction_t", + "homogeneity_interaction_p" + ], + "study_id": "SWL-S07", + "verification_category": "V2" + }, + { + "absolute_tolerance_by_field": { + "followup_b": 0.05, + "followup_se": 0.15, + "intercept_b": 0.05, + "intercept_se": 0.15, + "post_b": 0.05, + "post_se": 0.15, + "treatment_b": 0.05, + "treatment_followup_b": 0.05, + "treatment_followup_se": 0.15, + "treatment_post_b": 0.05, + "treatment_post_se": 0.15, + "treatment_se": 0.15 + }, + "default_absolute_tolerance": 1e-07, + "independent_r_script": "scripts/r/batches/swl_s08_s10_v0_1/swl_s08.R", + "parity_fields": [ + "n_participants", + "n_control", + "n_treatment", + "scheduled_rows", + "observed_rows", + "missing_visits", + "complete_participants", + "incomplete_participants", + "control_baseline_mean", + "control_post_mean", + "control_followup_mean", + "treatment_baseline_mean", + "treatment_post_mean", + "treatment_followup_mean", + "intercept_b", + "intercept_se", + "treatment_b", + "treatment_se", + "post_b", + "post_se", + "followup_b", + "followup_se", + "treatment_post_b", + "treatment_post_se", + "treatment_followup_b", + "treatment_followup_se" + ], + "study_id": "SWL-S08", + "v4_documented_not_equal_fields": [ + "test-statistic convention", + "denominator degrees of freedom", + "p values", + "confidence-interval method", + "optimizer details" + ], + "verification_category": "V3_estimand_aligned" + }, + { + "absolute_tolerance_by_field": {}, + "default_absolute_tolerance": 1e-07, + "independent_r_script": "scripts/r/batches/swl_s08_s10_v0_1/swl_s09.R", + "parity_fields": [ + "n_total", + "n_comparison", + "n_intervention", + "comparison_improved", + "comparison_not_improved", + "intervention_improved", + "intervention_not_improved", + "comparison_improved_proportion", + "intervention_improved_proportion", + "improved_risk_difference_intervention_minus_comparison", + "chi_square", + "chi_square_df", + "chi_square_p", + "cramers_v", + "minimum_expected_cell_count", + "comparison_burden_median", + "intervention_burden_median", + "burden_median_difference_intervention_minus_comparison", + "mann_whitney_u_intervention", + "mann_whitney_z_no_continuity", + "mann_whitney_p_two_sided", + "rank_biserial_intervention_lower", + "comparison_burden_min", + "comparison_burden_max", + "intervention_burden_min", + "intervention_burden_max" + ], + "study_id": "SWL-S09", + "verification_category": "V2_numeric_parity" + }, + { + "absolute_tolerance_by_field": {}, + "default_absolute_tolerance": 1e-07, + "independent_r_script": "scripts/r/batches/swl_s08_s10_v0_1/swl_s10.R", + "parity_fields": [ + "study_count", + "total_participants", + "target_effect_size_d", + "planning_alpha_two_sided", + "planning_power_primary", + "planning_n_per_group_unrounded_80", + "planning_n_per_group_ceiling_80", + "planning_power_sensitivity", + "planning_n_per_group_unrounded_90", + "planning_n_per_group_ceiling_90", + "minimum_observed_d", + "maximum_observed_d", + "fixed_effect_pooled_d", + "fixed_effect_se", + "fixed_effect_z", + "fixed_effect_p", + "fixed_effect_ci_95_low", + "fixed_effect_ci_95_high", + "cochran_q", + "cochran_q_df", + "cochran_q_p", + "i_squared_percent" + ], + "study_id": "SWL-S10", + "verification_category": "V2_numeric_parity" + } + ] +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s01_student_well_being_survey.csv b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s01_student_well_being_survey.csv new file mode 100644 index 0000000..01665cd --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s01_student_well_being_survey.csv @@ -0,0 +1,121 @@ +study_id,participant_id,sleep_quality,study_hours_per_week,anxiety_score,academic_confidence,assessment_performance +SWL-S01,S01-001,38.12,7.5,50.51,53.22,65.76 +SWL-S01,S01-002,41.3,6.54,50.6,55.79,66.21 +SWL-S01,S01-003,43.38,9.57,41.98,59.22,68.24 +SWL-S01,S01-004,45.3,4.09,46.38,60.49,58.51 +SWL-S01,S01-005,47.1,5.09,43.8,55.8,59.31 +SWL-S01,S01-006,48.87,6.06,39.23,60.4,65.85 +SWL-S01,S01-007,49.7,7.03,40.94,58.52,67.36 +SWL-S01,S01-008,51.67,7.99,41.36,56.38,68.99 +SWL-S01,S01-009,53.79,8.95,41.76,58.3,73.0 +SWL-S01,S01-010,55.97,9.92,42.18,60.23,77.08 +SWL-S01,S01-011,58.12,8.41,42.62,62.13,75.96 +SWL-S01,S01-012,60.16,5.42,43.12,63.97,71.71 +SWL-S01,S01-013,38.07,7.45,50.38,53.14,69.32 +SWL-S01,S01-014,40.91,6.48,50.65,55.4,64.64 +SWL-S01,S01-015,42.76,9.53,42.18,58.6,71.67 +SWL-S01,S01-016,44.71,4.06,46.66,59.79,64.06 +SWL-S01,S01-017,46.81,5.09,44.07,55.2,58.12 +SWL-S01,S01-018,49.02,6.09,39.41,60.01,71.96 +SWL-S01,S01-019,50.24,7.07,41.01,58.36,62.74 +SWL-S01,S01-020,52.38,8.04,41.34,56.37,65.5 +SWL-S01,S01-021,54.37,9.0,41.72,58.32,69.5 +SWL-S01,S01-022,56.22,9.96,42.13,60.2,73.43 +SWL-S01,S01-023,58.01,8.43,42.59,62.05,72.1 +SWL-S01,S01-024,59.83,5.41,43.07,63.91,67.67 +SWL-S01,S01-025,37.78,7.41,50.26,53.23,65.18 +SWL-S01,S01-026,40.85,6.43,50.44,55.72,65.5 +SWL-S01,S01-027,43.01,9.47,41.9,59.15,76.57 +SWL-S01,S01-028,45.15,4.01,46.37,60.47,66.01 +SWL-S01,S01-029,47.19,5.05,43.87,55.85,67.07 +SWL-S01,S01-030,49.1,6.08,39.39,60.45,68.88 +SWL-S01,S01-031,49.91,7.09,41.2,58.51,66.61 +SWL-S01,S01-032,51.72,8.08,41.7,56.24,71.37 +SWL-S01,S01-033,53.62,9.06,42.14,58.03,68.43 +SWL-S01,S01-034,55.67,10.02,42.51,59.89,79.5 +SWL-S01,S01-035,57.86,8.48,42.83,61.83,67.34 +SWL-S01,S01-036,60.1,5.44,43.14,63.8,64.03 +SWL-S01,S01-037,38.27,7.42,50.18,53.16,61.55 +SWL-S01,S01-038,41.32,6.41,50.28,55.58,61.75 +SWL-S01,S01-039,43.23,9.42,41.72,58.85,72.6 +SWL-S01,S01-040,45.05,3.96,46.21,60.02,61.8 +SWL-S01,S01-041,46.89,5.0,43.73,55.33,62.68 +SWL-S01,S01-042,48.84,6.04,39.24,60.03,69.43 +SWL-S01,S01-043,49.92,7.07,41.01,58.31,71.22 +SWL-S01,S01-044,52.08,8.09,41.48,56.34,73.12 +SWL-S01,S01-045,54.24,9.09,41.94,58.37,77.34 +SWL-S01,S01-046,56.3,10.07,42.4,60.34,76.51 +SWL-S01,S01-047,58.22,8.53,42.88,62.24,71.38 +SWL-S01,S01-048,60.02,5.49,43.36,64.06,70.04 +SWL-S01,S01-049,37.79,7.45,50.54,53.27,60.55 +SWL-S01,S01-050,40.63,6.42,50.68,55.61,67.8 +SWL-S01,S01-051,42.62,9.41,42.05,58.94,67.77 +SWL-S01,S01-052,44.75,3.92,46.38,60.24,58.14 +SWL-S01,S01-053,46.96,4.94,43.71,55.68,59.16 +SWL-S01,S01-054,49.14,5.98,39.07,60.41,65.96 +SWL-S01,S01-055,50.22,7.02,40.76,58.59,67.68 +SWL-S01,S01-056,52.15,8.06,41.25,56.38,69.39 +SWL-S01,S01-057,54.0,9.08,41.78,58.12,73.36 +SWL-S01,S01-058,55.85,10.09,42.33,59.87,77.3 +SWL-S01,S01-059,57.8,8.58,42.86,61.69,76.0 +SWL-S01,S01-060,59.88,5.55,43.34,63.59,71.62 +SWL-S01,S01-061,38.07,7.51,50.5,52.97,69.14 +SWL-S01,S01-062,41.27,6.47,50.62,55.51,64.44 +SWL-S01,S01-063,43.38,9.43,42.03,58.93,71.43 +SWL-S01,S01-064,45.35,3.91,46.44,60.22,63.79 +SWL-S01,S01-065,47.18,4.91,43.87,55.56,57.8 +SWL-S01,S01-066,48.94,5.93,39.31,60.2,71.63 +SWL-S01,S01-067,49.75,6.97,41.01,58.38,62.48 +SWL-S01,S01-068,51.69,8.01,41.4,56.31,65.43 +SWL-S01,S01-069,53.76,9.05,41.78,58.29,69.7 +SWL-S01,S01-070,55.93,10.08,42.16,60.3,73.93 +SWL-S01,S01-071,58.09,8.59,42.57,62.29,72.83 +SWL-S01,S01-072,60.16,5.58,43.03,64.21,68.46 +SWL-S01,S01-073,38.1,7.56,50.28,53.44,65.83 +SWL-S01,S01-074,40.94,6.52,50.55,55.72,65.83 +SWL-S01,S01-075,42.77,9.48,42.11,58.9,76.49 +SWL-S01,S01-076,44.69,3.94,46.63,60.04,65.56 +SWL-S01,S01-077,46.75,4.92,44.08,55.38,66.38 +SWL-S01,S01-078,48.94,5.91,39.47,60.1,68.13 +SWL-S01,S01-079,50.18,6.92,41.1,58.38,65.97 +SWL-S01,S01-080,52.35,7.95,41.44,56.32,70.93 +SWL-S01,S01-081,54.38,8.99,41.81,58.21,68.22 +SWL-S01,S01-082,56.27,10.03,42.2,60.04,79.48 +SWL-S01,S01-083,58.07,8.57,42.63,61.84,67.45 +SWL-S01,S01-084,59.88,5.59,43.08,63.66,64.23 +SWL-S01,S01-085,37.79,7.59,50.24,52.95,61.83 +SWL-S01,S01-086,40.84,6.57,50.39,55.44,62.12 +SWL-S01,S01-087,42.99,9.54,41.83,58.9,73.05 +SWL-S01,S01-088,45.14,4.0,46.28,60.29,62.28 +SWL-S01,S01-089,47.21,4.96,43.77,55.74,63.08 +SWL-S01,S01-090,49.14,5.92,39.3,60.44,69.65 +SWL-S01,S01-091,49.96,6.91,41.14,58.58,71.19 +SWL-S01,S01-092,51.76,7.92,41.67,56.38,72.83 +SWL-S01,S01-093,53.62,8.94,42.17,58.21,76.89 +SWL-S01,S01-094,55.63,9.98,42.59,60.1,76.04 +SWL-S01,S01-095,57.79,8.52,42.94,62.06,71.05 +SWL-S01,S01-096,60.02,5.56,43.25,64.03,69.95 +SWL-S01,S01-097,38.22,7.58,50.28,53.39,60.69 +SWL-S01,S01-098,41.3,6.59,50.35,55.8,68.08 +SWL-S01,S01-099,43.25,9.58,41.75,59.05,68.04 +SWL-S01,S01-100,45.09,4.05,46.2,60.16,58.28 +SWL-S01,S01-101,46.92,5.01,43.68,55.39,59.1 +SWL-S01,S01-102,48.84,5.97,39.17,59.99,65.73 +SWL-S01,S01-103,49.89,6.93,40.93,58.19,67.37 +SWL-S01,S01-104,52.05,7.91,41.39,56.14,69.13 +SWL-S01,S01-105,54.22,8.91,41.86,58.12,73.23 +SWL-S01,S01-106,56.32,9.93,42.34,60.08,77.33 +SWL-S01,S01-107,58.27,8.46,42.84,61.98,76.16 +SWL-S01,S01-108,60.09,5.5,43.35,63.83,71.82 +SWL-S01,S01-109,37.86,7.54,50.57,53.06,69.34 +SWL-S01,S01-110,40.67,6.57,50.75,55.45,64.6 +SWL-S01,S01-111,42.62,9.59,42.15,58.82,71.57 +SWL-S01,S01-112,44.71,4.09,46.49,60.18,63.94 +SWL-S01,S01-113,46.9,5.06,43.8,55.7,57.97 +SWL-S01,S01-114,49.1,6.03,39.13,60.52,71.79 +SWL-S01,S01-115,50.19,6.98,40.78,58.79,62.55 +SWL-S01,S01-116,52.16,7.95,41.22,56.65,65.33 +SWL-S01,S01-117,54.02,8.92,41.71,58.43,69.4 +SWL-S01,S01-118,55.86,9.91,42.24,60.18,73.46 +SWL-S01,S01-119,57.78,8.42,42.77,61.97,72.31 +SWL-S01,S01-120,59.83,5.45,43.26,63.81,68.05 diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s02_structured_study_routine.csv b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s02_structured_study_routine.csv new file mode 100644 index 0000000..6073513 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s02_structured_study_routine.csv @@ -0,0 +1,65 @@ +study_id,participant_id,study_routine_group,post_session_performance +SWL-S02,S02-001,standard_routine,62 +SWL-S02,S02-002,standard_routine,64 +SWL-S02,S02-003,standard_routine,65 +SWL-S02,S02-004,standard_routine,66 +SWL-S02,S02-005,standard_routine,67 +SWL-S02,S02-006,standard_routine,68 +SWL-S02,S02-007,standard_routine,69 +SWL-S02,S02-008,standard_routine,70 +SWL-S02,S02-009,standard_routine,71 +SWL-S02,S02-010,standard_routine,72 +SWL-S02,S02-011,standard_routine,73 +SWL-S02,S02-012,standard_routine,69 +SWL-S02,S02-013,standard_routine,66 +SWL-S02,S02-014,standard_routine,70 +SWL-S02,S02-015,standard_routine,64 +SWL-S02,S02-016,standard_routine,72 +SWL-S02,S02-017,standard_routine,62 +SWL-S02,S02-018,standard_routine,64 +SWL-S02,S02-019,standard_routine,65 +SWL-S02,S02-020,standard_routine,66 +SWL-S02,S02-021,standard_routine,67 +SWL-S02,S02-022,standard_routine,68 +SWL-S02,S02-023,standard_routine,69 +SWL-S02,S02-024,standard_routine,70 +SWL-S02,S02-025,standard_routine,71 +SWL-S02,S02-026,standard_routine,72 +SWL-S02,S02-027,standard_routine,73 +SWL-S02,S02-028,standard_routine,69 +SWL-S02,S02-029,standard_routine,66 +SWL-S02,S02-030,standard_routine,70 +SWL-S02,S02-031,standard_routine,64 +SWL-S02,S02-032,standard_routine,72 +SWL-S02,S02-033,structured_routine,75 +SWL-S02,S02-034,structured_routine,72 +SWL-S02,S02-035,structured_routine,76 +SWL-S02,S02-036,structured_routine,70 +SWL-S02,S02-037,structured_routine,78 +SWL-S02,S02-038,structured_routine,68 +SWL-S02,S02-039,structured_routine,70 +SWL-S02,S02-040,structured_routine,71 +SWL-S02,S02-041,structured_routine,72 +SWL-S02,S02-042,structured_routine,73 +SWL-S02,S02-043,structured_routine,74 +SWL-S02,S02-044,structured_routine,75 +SWL-S02,S02-045,structured_routine,76 +SWL-S02,S02-046,structured_routine,77 +SWL-S02,S02-047,structured_routine,78 +SWL-S02,S02-048,structured_routine,79 +SWL-S02,S02-049,structured_routine,75 +SWL-S02,S02-050,structured_routine,72 +SWL-S02,S02-051,structured_routine,76 +SWL-S02,S02-052,structured_routine,70 +SWL-S02,S02-053,structured_routine,78 +SWL-S02,S02-054,structured_routine,68 +SWL-S02,S02-055,structured_routine,70 +SWL-S02,S02-056,structured_routine,71 +SWL-S02,S02-057,structured_routine,72 +SWL-S02,S02-058,structured_routine,73 +SWL-S02,S02-059,structured_routine,74 +SWL-S02,S02-060,structured_routine,75 +SWL-S02,S02-061,structured_routine,76 +SWL-S02,S02-062,structured_routine,77 +SWL-S02,S02-063,structured_routine,78 +SWL-S02,S02-064,structured_routine,79 diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s03_skills_workshop_pre_post.csv b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s03_skills_workshop_pre_post.csv new file mode 100644 index 0000000..72df0cc --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s03_skills_workshop_pre_post.csv @@ -0,0 +1,97 @@ +study_id,participant_id,occasion,academic_confidence +SWL-S03,S03-001,pre,47 +SWL-S03,S03-001,post,46 +SWL-S03,S03-002,pre,49 +SWL-S03,S03-002,post,50 +SWL-S03,S03-003,pre,50 +SWL-S03,S03-003,post,52 +SWL-S03,S03-004,pre,52 +SWL-S03,S03-004,post,55 +SWL-S03,S03-005,pre,53 +SWL-S03,S03-005,post,57 +SWL-S03,S03-006,pre,54 +SWL-S03,S03-006,post,59 +SWL-S03,S03-007,pre,55 +SWL-S03,S03-007,post,61 +SWL-S03,S03-008,pre,56 +SWL-S03,S03-008,post,63 +SWL-S03,S03-009,pre,57 +SWL-S03,S03-009,post,65 +SWL-S03,S03-010,pre,58 +SWL-S03,S03-010,post,67 +SWL-S03,S03-011,pre,60 +SWL-S03,S03-011,post,60 +SWL-S03,S03-012,pre,62 +SWL-S03,S03-012,post,68 +SWL-S03,S03-013,pre,47 +SWL-S03,S03-013,post,46 +SWL-S03,S03-014,pre,49 +SWL-S03,S03-014,post,50 +SWL-S03,S03-015,pre,50 +SWL-S03,S03-015,post,52 +SWL-S03,S03-016,pre,52 +SWL-S03,S03-016,post,55 +SWL-S03,S03-017,pre,53 +SWL-S03,S03-017,post,57 +SWL-S03,S03-018,pre,54 +SWL-S03,S03-018,post,59 +SWL-S03,S03-019,pre,55 +SWL-S03,S03-019,post,61 +SWL-S03,S03-020,pre,56 +SWL-S03,S03-020,post,63 +SWL-S03,S03-021,pre,57 +SWL-S03,S03-021,post,65 +SWL-S03,S03-022,pre,58 +SWL-S03,S03-022,post,67 +SWL-S03,S03-023,pre,60 +SWL-S03,S03-023,post,60 +SWL-S03,S03-024,pre,62 +SWL-S03,S03-024,post,68 +SWL-S03,S03-025,pre,47 +SWL-S03,S03-025,post,46 +SWL-S03,S03-026,pre,49 +SWL-S03,S03-026,post,50 +SWL-S03,S03-027,pre,50 +SWL-S03,S03-027,post,52 +SWL-S03,S03-028,pre,52 +SWL-S03,S03-028,post,55 +SWL-S03,S03-029,pre,53 +SWL-S03,S03-029,post,57 +SWL-S03,S03-030,pre,54 +SWL-S03,S03-030,post,59 +SWL-S03,S03-031,pre,55 +SWL-S03,S03-031,post,61 +SWL-S03,S03-032,pre,56 +SWL-S03,S03-032,post,63 +SWL-S03,S03-033,pre,57 +SWL-S03,S03-033,post,65 +SWL-S03,S03-034,pre,58 +SWL-S03,S03-034,post,67 +SWL-S03,S03-035,pre,60 +SWL-S03,S03-035,post,60 +SWL-S03,S03-036,pre,62 +SWL-S03,S03-036,post,68 +SWL-S03,S03-037,pre,47 +SWL-S03,S03-037,post,46 +SWL-S03,S03-038,pre,49 +SWL-S03,S03-038,post,50 +SWL-S03,S03-039,pre,50 +SWL-S03,S03-039,post,52 +SWL-S03,S03-040,pre,52 +SWL-S03,S03-040,post,55 +SWL-S03,S03-041,pre,53 +SWL-S03,S03-041,post,57 +SWL-S03,S03-042,pre,54 +SWL-S03,S03-042,post,59 +SWL-S03,S03-043,pre,55 +SWL-S03,S03-043,post,61 +SWL-S03,S03-044,pre,56 +SWL-S03,S03-044,post,63 +SWL-S03,S03-045,pre,57 +SWL-S03,S03-045,post,65 +SWL-S03,S03-046,pre,58 +SWL-S03,S03-046,post,67 +SWL-S03,S03-047,pre,60 +SWL-S03,S03-047,post,60 +SWL-S03,S03-048,pre,62 +SWL-S03,S03-048,post,68 diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s04_three_condition_support.csv b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s04_three_condition_support.csv new file mode 100644 index 0000000..f10e5dd --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s04_three_condition_support.csv @@ -0,0 +1,91 @@ +study_id,participant_id,support_condition,assessment_performance +SWL-S04,S04-001,standard_support,60 +SWL-S04,S04-002,standard_support,62 +SWL-S04,S04-003,standard_support,63 +SWL-S04,S04-004,standard_support,64 +SWL-S04,S04-005,standard_support,65 +SWL-S04,S04-006,standard_support,66 +SWL-S04,S04-007,standard_support,67 +SWL-S04,S04-008,standard_support,68 +SWL-S04,S04-009,standard_support,69 +SWL-S04,S04-010,standard_support,70 +SWL-S04,S04-011,standard_support,71 +SWL-S04,S04-012,standard_support,72 +SWL-S04,S04-013,standard_support,73 +SWL-S04,S04-014,standard_support,65 +SWL-S04,S04-015,standard_support,69 +SWL-S04,S04-016,standard_support,60 +SWL-S04,S04-017,standard_support,62 +SWL-S04,S04-018,standard_support,63 +SWL-S04,S04-019,standard_support,64 +SWL-S04,S04-020,standard_support,65 +SWL-S04,S04-021,standard_support,66 +SWL-S04,S04-022,standard_support,67 +SWL-S04,S04-023,standard_support,68 +SWL-S04,S04-024,standard_support,69 +SWL-S04,S04-025,standard_support,70 +SWL-S04,S04-026,standard_support,71 +SWL-S04,S04-027,standard_support,72 +SWL-S04,S04-028,standard_support,73 +SWL-S04,S04-029,standard_support,65 +SWL-S04,S04-030,standard_support,69 +SWL-S04,S04-031,guided_practice,77 +SWL-S04,S04-032,guided_practice,78 +SWL-S04,S04-033,guided_practice,70 +SWL-S04,S04-034,guided_practice,74 +SWL-S04,S04-035,guided_practice,65 +SWL-S04,S04-036,guided_practice,67 +SWL-S04,S04-037,guided_practice,68 +SWL-S04,S04-038,guided_practice,69 +SWL-S04,S04-039,guided_practice,70 +SWL-S04,S04-040,guided_practice,71 +SWL-S04,S04-041,guided_practice,72 +SWL-S04,S04-042,guided_practice,73 +SWL-S04,S04-043,guided_practice,74 +SWL-S04,S04-044,guided_practice,75 +SWL-S04,S04-045,guided_practice,76 +SWL-S04,S04-046,guided_practice,77 +SWL-S04,S04-047,guided_practice,78 +SWL-S04,S04-048,guided_practice,70 +SWL-S04,S04-049,guided_practice,74 +SWL-S04,S04-050,guided_practice,65 +SWL-S04,S04-051,guided_practice,67 +SWL-S04,S04-052,guided_practice,68 +SWL-S04,S04-053,guided_practice,69 +SWL-S04,S04-054,guided_practice,70 +SWL-S04,S04-055,guided_practice,71 +SWL-S04,S04-056,guided_practice,72 +SWL-S04,S04-057,guided_practice,73 +SWL-S04,S04-058,guided_practice,74 +SWL-S04,S04-059,guided_practice,75 +SWL-S04,S04-060,guided_practice,76 +SWL-S04,S04-061,guided_practice_plus_feedback,79 +SWL-S04,S04-062,guided_practice_plus_feedback,80 +SWL-S04,S04-063,guided_practice_plus_feedback,81 +SWL-S04,S04-064,guided_practice_plus_feedback,82 +SWL-S04,S04-065,guided_practice_plus_feedback,83 +SWL-S04,S04-066,guided_practice_plus_feedback,84 +SWL-S04,S04-067,guided_practice_plus_feedback,76 +SWL-S04,S04-068,guided_practice_plus_feedback,80 +SWL-S04,S04-069,guided_practice_plus_feedback,71 +SWL-S04,S04-070,guided_practice_plus_feedback,73 +SWL-S04,S04-071,guided_practice_plus_feedback,74 +SWL-S04,S04-072,guided_practice_plus_feedback,75 +SWL-S04,S04-073,guided_practice_plus_feedback,76 +SWL-S04,S04-074,guided_practice_plus_feedback,77 +SWL-S04,S04-075,guided_practice_plus_feedback,78 +SWL-S04,S04-076,guided_practice_plus_feedback,79 +SWL-S04,S04-077,guided_practice_plus_feedback,80 +SWL-S04,S04-078,guided_practice_plus_feedback,81 +SWL-S04,S04-079,guided_practice_plus_feedback,82 +SWL-S04,S04-080,guided_practice_plus_feedback,83 +SWL-S04,S04-081,guided_practice_plus_feedback,84 +SWL-S04,S04-082,guided_practice_plus_feedback,76 +SWL-S04,S04-083,guided_practice_plus_feedback,80 +SWL-S04,S04-084,guided_practice_plus_feedback,71 +SWL-S04,S04-085,guided_practice_plus_feedback,73 +SWL-S04,S04-086,guided_practice_plus_feedback,74 +SWL-S04,S04-087,guided_practice_plus_feedback,75 +SWL-S04,S04-088,guided_practice_plus_feedback,76 +SWL-S04,S04-089,guided_practice_plus_feedback,77 +SWL-S04,S04-090,guided_practice_plus_feedback,78 diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s05_strategy_feedback.csv b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s05_strategy_feedback.csv new file mode 100644 index 0000000..48a50f1 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s05_strategy_feedback.csv @@ -0,0 +1,97 @@ +study_id,participant_id,study_strategy,feedback_condition,assessment_performance +SWL-S05,S05-001,rereading,no_feedback,62 +SWL-S05,S05-002,rereading,no_feedback,63 +SWL-S05,S05-003,rereading,no_feedback,65 +SWL-S05,S05-004,rereading,no_feedback,66 +SWL-S05,S05-005,rereading,no_feedback,67 +SWL-S05,S05-006,rereading,no_feedback,68 +SWL-S05,S05-007,rereading,no_feedback,69 +SWL-S05,S05-008,rereading,no_feedback,70 +SWL-S05,S05-009,rereading,no_feedback,71 +SWL-S05,S05-010,rereading,no_feedback,72 +SWL-S05,S05-011,rereading,no_feedback,73 +SWL-S05,S05-012,rereading,no_feedback,70 +SWL-S05,S05-013,rereading,no_feedback,66 +SWL-S05,S05-014,rereading,no_feedback,69 +SWL-S05,S05-015,rereading,no_feedback,64 +SWL-S05,S05-016,rereading,no_feedback,72 +SWL-S05,S05-017,rereading,no_feedback,62 +SWL-S05,S05-018,rereading,no_feedback,63 +SWL-S05,S05-019,rereading,no_feedback,65 +SWL-S05,S05-020,rereading,no_feedback,66 +SWL-S05,S05-021,rereading,no_feedback,67 +SWL-S05,S05-022,rereading,no_feedback,68 +SWL-S05,S05-023,rereading,no_feedback,69 +SWL-S05,S05-024,rereading,no_feedback,70 +SWL-S05,S05-025,retrieval_practice,no_feedback,72 +SWL-S05,S05-026,retrieval_practice,no_feedback,73 +SWL-S05,S05-027,retrieval_practice,no_feedback,74 +SWL-S05,S05-028,retrieval_practice,no_feedback,66 +SWL-S05,S05-029,retrieval_practice,no_feedback,67 +SWL-S05,S05-030,retrieval_practice,no_feedback,69 +SWL-S05,S05-031,retrieval_practice,no_feedback,70 +SWL-S05,S05-032,retrieval_practice,no_feedback,71 +SWL-S05,S05-033,retrieval_practice,no_feedback,72 +SWL-S05,S05-034,retrieval_practice,no_feedback,73 +SWL-S05,S05-035,retrieval_practice,no_feedback,74 +SWL-S05,S05-036,retrieval_practice,no_feedback,75 +SWL-S05,S05-037,retrieval_practice,no_feedback,76 +SWL-S05,S05-038,retrieval_practice,no_feedback,77 +SWL-S05,S05-039,retrieval_practice,no_feedback,74 +SWL-S05,S05-040,retrieval_practice,no_feedback,70 +SWL-S05,S05-041,retrieval_practice,no_feedback,73 +SWL-S05,S05-042,retrieval_practice,no_feedback,68 +SWL-S05,S05-043,retrieval_practice,no_feedback,76 +SWL-S05,S05-044,retrieval_practice,no_feedback,66 +SWL-S05,S05-045,retrieval_practice,no_feedback,67 +SWL-S05,S05-046,retrieval_practice,no_feedback,69 +SWL-S05,S05-047,retrieval_practice,no_feedback,70 +SWL-S05,S05-048,retrieval_practice,no_feedback,71 +SWL-S05,S05-049,rereading,explanatory_feedback,67 +SWL-S05,S05-050,rereading,explanatory_feedback,68 +SWL-S05,S05-051,rereading,explanatory_feedback,69 +SWL-S05,S05-052,rereading,explanatory_feedback,70 +SWL-S05,S05-053,rereading,explanatory_feedback,71 +SWL-S05,S05-054,rereading,explanatory_feedback,72 +SWL-S05,S05-055,rereading,explanatory_feedback,64 +SWL-S05,S05-056,rereading,explanatory_feedback,65 +SWL-S05,S05-057,rereading,explanatory_feedback,67 +SWL-S05,S05-058,rereading,explanatory_feedback,68 +SWL-S05,S05-059,rereading,explanatory_feedback,69 +SWL-S05,S05-060,rereading,explanatory_feedback,70 +SWL-S05,S05-061,rereading,explanatory_feedback,71 +SWL-S05,S05-062,rereading,explanatory_feedback,72 +SWL-S05,S05-063,rereading,explanatory_feedback,73 +SWL-S05,S05-064,rereading,explanatory_feedback,74 +SWL-S05,S05-065,rereading,explanatory_feedback,75 +SWL-S05,S05-066,rereading,explanatory_feedback,72 +SWL-S05,S05-067,rereading,explanatory_feedback,68 +SWL-S05,S05-068,rereading,explanatory_feedback,71 +SWL-S05,S05-069,rereading,explanatory_feedback,66 +SWL-S05,S05-070,rereading,explanatory_feedback,74 +SWL-S05,S05-071,rereading,explanatory_feedback,64 +SWL-S05,S05-072,rereading,explanatory_feedback,65 +SWL-S05,S05-073,retrieval_practice,explanatory_feedback,84 +SWL-S05,S05-074,retrieval_practice,explanatory_feedback,74 +SWL-S05,S05-075,retrieval_practice,explanatory_feedback,75 +SWL-S05,S05-076,retrieval_practice,explanatory_feedback,77 +SWL-S05,S05-077,retrieval_practice,explanatory_feedback,78 +SWL-S05,S05-078,retrieval_practice,explanatory_feedback,79 +SWL-S05,S05-079,retrieval_practice,explanatory_feedback,80 +SWL-S05,S05-080,retrieval_practice,explanatory_feedback,81 +SWL-S05,S05-081,retrieval_practice,explanatory_feedback,82 +SWL-S05,S05-082,retrieval_practice,explanatory_feedback,74 +SWL-S05,S05-083,retrieval_practice,explanatory_feedback,75 +SWL-S05,S05-084,retrieval_practice,explanatory_feedback,77 +SWL-S05,S05-085,retrieval_practice,explanatory_feedback,78 +SWL-S05,S05-086,retrieval_practice,explanatory_feedback,79 +SWL-S05,S05-087,retrieval_practice,explanatory_feedback,80 +SWL-S05,S05-088,retrieval_practice,explanatory_feedback,81 +SWL-S05,S05-089,retrieval_practice,explanatory_feedback,82 +SWL-S05,S05-090,retrieval_practice,explanatory_feedback,83 +SWL-S05,S05-091,retrieval_practice,explanatory_feedback,84 +SWL-S05,S05-092,retrieval_practice,explanatory_feedback,85 +SWL-S05,S05-093,retrieval_practice,explanatory_feedback,82 +SWL-S05,S05-094,retrieval_practice,explanatory_feedback,78 +SWL-S05,S05-095,retrieval_practice,explanatory_feedback,81 +SWL-S05,S05-096,retrieval_practice,explanatory_feedback,76 diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s06_confidence_trajectory.csv b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s06_confidence_trajectory.csv new file mode 100644 index 0000000..d43d3cf --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s06_confidence_trajectory.csv @@ -0,0 +1,181 @@ +study_id,participant_id,occasion,academic_confidence +SWL-S06,S06-001,baseline,43.0 +SWL-S06,S06-001,post,49.16 +SWL-S06,S06-001,follow_up,51.0 +SWL-S06,S06-002,baseline,45.08 +SWL-S06,S06-002,post,47.22 +SWL-S06,S06-002,follow_up,54.12 +SWL-S06,S06-003,baseline,46.14 +SWL-S06,S06-003,post,51.25 +SWL-S06,S06-003,follow_up,53.22 +SWL-S06,S06-004,baseline,47.17 +SWL-S06,S06-004,post,50.24 +SWL-S06,S06-004,follow_up,52.29 +SWL-S06,S06-005,baseline,48.18 +SWL-S06,S06-005,post, +SWL-S06,S06-005,follow_up,56.31 +SWL-S06,S06-006,baseline,49.15 +SWL-S06,S06-006,post,52.09 +SWL-S06,S06-006,follow_up,53.29 +SWL-S06,S06-007,baseline,50.1 +SWL-S06,S06-007,post,53.98 +SWL-S06,S06-007,follow_up,56.22 +SWL-S06,S06-008,baseline,51.02 +SWL-S06,S06-008,post,55.88 +SWL-S06,S06-008,follow_up,54.13 +SWL-S06,S06-009,baseline,51.95 +SWL-S06,S06-009,post,57.79 +SWL-S06,S06-009,follow_up, +SWL-S06,S06-010,baseline,52.88 +SWL-S06,S06-010,post,57.73 +SWL-S06,S06-010,follow_up,57.9 +SWL-S06,S06-011,baseline,53.84 +SWL-S06,S06-011,post,56.72 +SWL-S06,S06-011,follow_up,59.81 +SWL-S06,S06-012,baseline,54.82 +SWL-S06,S06-012,post,58.75 +SWL-S06,S06-012,follow_up,61.75 +SWL-S06,S06-013,baseline,42.84 +SWL-S06,S06-013,post,48.83 +SWL-S06,S06-013,follow_up,50.74 +SWL-S06,S06-014,baseline,44.88 +SWL-S06,S06-014,post,46.94 +SWL-S06,S06-014,follow_up,53.76 +SWL-S06,S06-015,baseline,45.95 +SWL-S06,S06-015,post,51.06 +SWL-S06,S06-015,follow_up,52.81 +SWL-S06,S06-016,baseline,47.03 +SWL-S06,S06-016,post,50.18 +SWL-S06,S06-016,follow_up,51.89 +SWL-S06,S06-017,baseline,48.1 +SWL-S06,S06-017,post, +SWL-S06,S06-017,follow_up,55.98 +SWL-S06,S06-018,baseline,49.15 +SWL-S06,S06-018,post,52.3 +SWL-S06,S06-018,follow_up,53.07 +SWL-S06,S06-019,baseline,50.18 +SWL-S06,S06-019,post,54.3 +SWL-S06,S06-019,follow_up,56.13 +SWL-S06,S06-020,baseline,51.17 +SWL-S06,S06-020,post,56.24 +SWL-S06,S06-020,follow_up,54.17 +SWL-S06,S06-021,baseline,52.13 +SWL-S06,S06-021,post,58.14 +SWL-S06,S06-021,follow_up,56.18 +SWL-S06,S06-022,baseline,53.07 +SWL-S06,S06-022,post,58.02 +SWL-S06,S06-022,follow_up,58.15 +SWL-S06,S06-023,baseline,53.99 +SWL-S06,S06-023,post,56.89 +SWL-S06,S06-023,follow_up,60.11 +SWL-S06,S06-024,baseline,54.92 +SWL-S06,S06-024,post,58.78 +SWL-S06,S06-024,follow_up,62.05 +SWL-S06,S06-025,baseline,42.86 +SWL-S06,S06-025,post,48.7 +SWL-S06,S06-025,follow_up,51.0 +SWL-S06,S06-026,baseline,44.83 +SWL-S06,S06-026,post,46.67 +SWL-S06,S06-026,follow_up,53.95 +SWL-S06,S06-027,baseline,45.82 +SWL-S06,S06-027,post,50.7 +SWL-S06,S06-027,follow_up,52.93 +SWL-S06,S06-028,baseline,46.85 +SWL-S06,S06-028,post,49.78 +SWL-S06,S06-028,follow_up, +SWL-S06,S06-029,baseline,47.91 +SWL-S06,S06-029,post,49.9 +SWL-S06,S06-029,follow_up,55.94 +SWL-S06,S06-030,baseline,48.98 +SWL-S06,S06-030,post,52.03 +SWL-S06,S06-030,follow_up,52.96 +SWL-S06,S06-031,baseline,50.06 +SWL-S06,S06-031,post,54.16 +SWL-S06,S06-031,follow_up,55.99 +SWL-S06,S06-032,baseline,51.12 +SWL-S06,S06-032,post,56.27 +SWL-S06,S06-032,follow_up,54.02 +SWL-S06,S06-033,baseline,52.17 +SWL-S06,S06-033,post,58.33 +SWL-S06,S06-033,follow_up,56.04 +SWL-S06,S06-034,baseline,53.18 +SWL-S06,S06-034,post,58.33 +SWL-S06,S06-034,follow_up,58.04 +SWL-S06,S06-035,baseline,54.16 +SWL-S06,S06-035,post,57.28 +SWL-S06,S06-035,follow_up,60.02 +SWL-S06,S06-036,baseline,55.11 +SWL-S06,S06-036,post,59.18 +SWL-S06,S06-036,follow_up,61.99 +SWL-S06,S06-037,baseline,43.04 +SWL-S06,S06-037,post,49.06 +SWL-S06,S06-037,follow_up,50.95 +SWL-S06,S06-038,baseline,44.96 +SWL-S06,S06-038,post,46.92 +SWL-S06,S06-038,follow_up,53.92 +SWL-S06,S06-039,baseline,45.89 +SWL-S06,S06-039,post,50.79 +SWL-S06,S06-039,follow_up,52.89 +SWL-S06,S06-040,baseline,46.84 +SWL-S06,S06-040,post,49.7 +SWL-S06,S06-040,follow_up,51.88 +SWL-S06,S06-041,baseline,47.82 +SWL-S06,S06-041,post, +SWL-S06,S06-041,follow_up,55.9 +SWL-S06,S06-042,baseline,48.83 +SWL-S06,S06-042,post,51.68 +SWL-S06,S06-042,follow_up,52.95 +SWL-S06,S06-043,baseline,49.87 +SWL-S06,S06-043,post,53.75 +SWL-S06,S06-043,follow_up,56.01 +SWL-S06,S06-044,baseline,50.94 +SWL-S06,S06-044,post,55.86 +SWL-S06,S06-044,follow_up,54.08 +SWL-S06,S06-045,baseline,52.01 +SWL-S06,S06-045,post,57.99 +SWL-S06,S06-045,follow_up,56.14 +SWL-S06,S06-046,baseline,53.09 +SWL-S06,S06-046,post,58.13 +SWL-S06,S06-046,follow_up,58.19 +SWL-S06,S06-047,baseline,54.14 +SWL-S06,S06-047,post,57.24 +SWL-S06,S06-047,follow_up,60.21 +SWL-S06,S06-048,baseline,55.18 +SWL-S06,S06-048,post,59.31 +SWL-S06,S06-048,follow_up,62.2 +SWL-S06,S06-049,baseline,43.18 +SWL-S06,S06-049,post,49.33 +SWL-S06,S06-049,follow_up,51.16 +SWL-S06,S06-050,baseline,45.14 +SWL-S06,S06-050,post,47.3 +SWL-S06,S06-050,follow_up,54.08 +SWL-S06,S06-051,baseline,46.08 +SWL-S06,S06-051,post,51.21 +SWL-S06,S06-051,follow_up,52.99 +SWL-S06,S06-052,baseline,47.01 +SWL-S06,S06-052,post,50.09 +SWL-S06,S06-052,follow_up,51.88 +SWL-S06,S06-053,baseline,47.94 +SWL-S06,S06-053,post,49.96 +SWL-S06,S06-053,follow_up,55.8 +SWL-S06,S06-054,baseline,48.87 +SWL-S06,S06-054,post,51.83 +SWL-S06,S06-054,follow_up, +SWL-S06,S06-055,baseline,49.83 +SWL-S06,S06-055,post,53.73 +SWL-S06,S06-055,follow_up,55.71 +SWL-S06,S06-056,baseline,50.82 +SWL-S06,S06-056,post,55.68 +SWL-S06,S06-056,follow_up,53.73 +SWL-S06,S06-057,baseline,51.84 +SWL-S06,S06-057,post,57.69 +SWL-S06,S06-057,follow_up,55.79 +SWL-S06,S06-058,baseline,52.9 +SWL-S06,S06-058,post,57.74 +SWL-S06,S06-058,follow_up,57.89 +SWL-S06,S06-059,baseline,53.97 +SWL-S06,S06-059,post,56.84 +SWL-S06,S06-059,follow_up,60.01 +SWL-S06,S06-060,baseline,55.04 +SWL-S06,S06-060,post,58.96 +SWL-S06,S06-060,follow_up,62.12 diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s07_baseline_adjusted_intervention.csv b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s07_baseline_adjusted_intervention.csv new file mode 100644 index 0000000..e9ea75d --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s07_baseline_adjusted_intervention.csv @@ -0,0 +1,91 @@ +study_id,participant_id,intervention_group,baseline_performance,post_performance +SWL-S07,S07-001,comparison,41.0,49.72 +SWL-S07,S07-002,comparison,43.11,49.22 +SWL-S07,S07-003,comparison,44.19,51.96 +SWL-S07,S07-004,comparison,45.22,53.65 +SWL-S07,S07-005,comparison,46.2,55.28 +SWL-S07,S07-006,comparison,47.12,56.87 +SWL-S07,S07-007,comparison,48.02,58.45 +SWL-S07,S07-008,comparison,48.91,60.04 +SWL-S07,S07-009,comparison,49.82,61.68 +SWL-S07,S07-010,comparison,50.78,63.37 +SWL-S07,S07-011,comparison,51.8,65.12 +SWL-S07,S07-012,comparison,52.86,66.94 +SWL-S07,S07-013,comparison,53.96,63.8 +SWL-S07,S07-014,comparison,55.07,61.67 +SWL-S07,S07-015,comparison,56.17,66.53 +SWL-S07,S07-016,comparison,41.22,49.82 +SWL-S07,S07-017,comparison,43.21,49.3 +SWL-S07,S07-018,comparison,44.15,51.99 +SWL-S07,S07-019,comparison,45.05,53.62 +SWL-S07,S07-020,comparison,45.94,55.23 +SWL-S07,S07-021,comparison,46.85,56.82 +SWL-S07,S07-022,comparison,47.79,58.42 +SWL-S07,S07-023,comparison,48.79,60.07 +SWL-S07,S07-024,comparison,49.84,61.76 +SWL-S07,S07-025,comparison,50.93,63.5 +SWL-S07,S07-026,comparison,52.04,65.27 +SWL-S07,S07-027,comparison,53.14,67.07 +SWL-S07,S07-028,comparison,54.21,63.86 +SWL-S07,S07-029,comparison,55.22,61.64 +SWL-S07,S07-030,comparison,56.17,66.39 +SWL-S07,S07-031,comparison,41.09,49.6 +SWL-S07,S07-032,comparison,42.98,49.04 +SWL-S07,S07-033,comparison,43.87,51.74 +SWL-S07,S07-034,comparison,44.8,53.44 +SWL-S07,S07-035,comparison,45.78,55.16 +SWL-S07,S07-036,comparison,46.81,56.89 +SWL-S07,S07-037,comparison,47.9,58.63 +SWL-S07,S07-038,comparison,49.0,60.37 +SWL-S07,S07-039,comparison,50.11,62.1 +SWL-S07,S07-040,comparison,51.19,63.8 +SWL-S07,S07-041,comparison,52.22,65.48 +SWL-S07,S07-042,comparison,53.19,67.13 +SWL-S07,S07-043,comparison,54.12,63.77 +SWL-S07,S07-044,comparison,55.01,61.41 +SWL-S07,S07-045,comparison,55.9,66.08 +SWL-S07,S07-046,intervention,56.19,73.96 +SWL-S07,S07-047,intervention,57.17,75.73 +SWL-S07,S07-048,intervention,58.11,72.47 +SWL-S07,S07-049,intervention,59.03,70.17 +SWL-S07,S07-050,intervention,43.94,63.32 +SWL-S07,S07-051,intervention,45.87,58.68 +SWL-S07,S07-052,intervention,46.82,57.32 +SWL-S07,S07-053,intervention,47.81,59.97 +SWL-S07,S07-054,intervention,48.85,61.64 +SWL-S07,S07-055,intervention,49.91,63.34 +SWL-S07,S07-056,intervention,51.0,65.07 +SWL-S07,S07-057,intervention,52.08,66.82 +SWL-S07,S07-058,intervention,53.15,68.59 +SWL-S07,S07-059,intervention,54.19,70.37 +SWL-S07,S07-060,intervention,55.18,72.14 +SWL-S07,S07-061,intervention,56.14,73.89 +SWL-S07,S07-062,intervention,57.06,75.63 +SWL-S07,S07-063,intervention,57.97,72.36 +SWL-S07,S07-064,intervention,58.89,70.06 +SWL-S07,S07-065,intervention,43.83,63.24 +SWL-S07,S07-066,intervention,45.81,58.65 +SWL-S07,S07-067,intervention,46.83,57.35 +SWL-S07,S07-068,intervention,47.88,60.05 +SWL-S07,S07-069,intervention,48.96,61.75 +SWL-S07,S07-070,intervention,50.05,63.47 +SWL-S07,S07-071,intervention,51.13,65.18 +SWL-S07,S07-072,intervention,52.18,66.9 +SWL-S07,S07-073,intervention,53.19,68.62 +SWL-S07,S07-074,intervention,54.16,70.33 +SWL-S07,S07-075,intervention,55.09,72.05 +SWL-S07,S07-076,intervention,56.01,73.78 +SWL-S07,S07-077,intervention,56.92,75.51 +SWL-S07,S07-078,intervention,57.85,72.25 +SWL-S07,S07-079,intervention,58.81,69.99 +SWL-S07,S07-080,intervention,43.82,63.22 +SWL-S07,S07-081,intervention,45.86,58.7 +SWL-S07,S07-082,intervention,46.93,57.44 +SWL-S07,S07-083,intervention,48.02,60.17 +SWL-S07,S07-084,intervention,49.1,61.88 +SWL-S07,S07-085,intervention,50.16,63.57 +SWL-S07,S07-086,intervention,51.19,65.24 +SWL-S07,S07-087,intervention,52.17,66.9 +SWL-S07,S07-088,intervention,53.12,68.57 +SWL-S07,S07-089,intervention,54.04,70.24 +SWL-S07,S07-090,intervention,54.95,71.93 diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s08_longitudinal_treatment.csv b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s08_longitudinal_treatment.csv new file mode 100644 index 0000000..4d853cd --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s08_longitudinal_treatment.csv @@ -0,0 +1,169 @@ +study_id,participant_id,treatment_group,occasion,occasion_order,treatment,post,followup,treatment_post,treatment_followup,well_being_score +SWL-S08,S08-C001,control,baseline,0,0,0,0,0,0,59.026271 +SWL-S08,S08-C001,control,post,1,0,1,0,0,0,55.597813 +SWL-S08,S08-C001,control,followup,2,0,0,1,0,0,54.953016 +SWL-S08,S08-C002,control,baseline,0,0,0,0,0,0,45.263279 +SWL-S08,S08-C002,control,post,1,0,1,0,0,0,46.112869 +SWL-S08,S08-C002,control,followup,2,0,0,1,0,0,51.310005 +SWL-S08,S08-C003,control,baseline,0,0,0,0,0,0,43.882732 +SWL-S08,S08-C003,control,post,1,0,1,0,0,0,48.150326 +SWL-S08,S08-C004,control,baseline,0,0,0,0,0,0,52.974872 +SWL-S08,S08-C004,control,followup,2,0,0,1,0,0,49.9739 +SWL-S08,S08-C005,control,baseline,0,0,0,0,0,0,49.753196 +SWL-S08,S08-C005,control,post,1,0,1,0,0,0,53.447063 +SWL-S08,S08-C005,control,followup,2,0,0,1,0,0,55.771125 +SWL-S08,S08-C006,control,baseline,0,0,0,0,0,0,47.708543 +SWL-S08,S08-C006,control,post,1,0,1,0,0,0,52.289792 +SWL-S08,S08-C006,control,followup,2,0,0,1,0,0,49.86561 +SWL-S08,S08-C007,control,baseline,0,0,0,0,0,0,51.057258 +SWL-S08,S08-C007,control,post,1,0,1,0,0,0,48.830591 +SWL-S08,S08-C007,control,followup,2,0,0,1,0,0,46.184688 +SWL-S08,S08-C008,control,baseline,0,0,0,0,0,0,52.637889 +SWL-S08,S08-C008,control,post,1,0,1,0,0,0,53.885874 +SWL-S08,S08-C009,control,baseline,0,0,0,0,0,0,52.657298 +SWL-S08,S08-C009,control,followup,2,0,0,1,0,0,54.218785 +SWL-S08,S08-C010,control,baseline,0,0,0,0,0,0,45.848457 +SWL-S08,S08-C010,control,post,1,0,1,0,0,0,42.360161 +SWL-S08,S08-C010,control,followup,2,0,0,1,0,0,42.798516 +SWL-S08,S08-C011,control,baseline,0,0,0,0,0,0,45.497753 +SWL-S08,S08-C011,control,post,1,0,1,0,0,0,48.078771 +SWL-S08,S08-C011,control,followup,2,0,0,1,0,0,53.188664 +SWL-S08,S08-C012,control,baseline,0,0,0,0,0,0,55.909019 +SWL-S08,S08-C012,control,post,1,0,1,0,0,0,59.661742 +SWL-S08,S08-C012,control,followup,2,0,0,1,0,0,58.370821 +SWL-S08,S08-C013,control,baseline,0,0,0,0,0,0,50.372635 +SWL-S08,S08-C013,control,post,1,0,1,0,0,0,49.81675 +SWL-S08,S08-C014,control,baseline,0,0,0,0,0,0,42.761702 +SWL-S08,S08-C014,control,post,1,0,1,0,0,0,47.460093 +SWL-S08,S08-C014,control,followup,2,0,0,1,0,0,49.595785 +SWL-S08,S08-C015,control,baseline,0,0,0,0,0,0,54.68489 +SWL-S08,S08-C015,control,post,1,0,1,0,0,0,57.717492 +SWL-S08,S08-C015,control,followup,2,0,0,1,0,0,54.363065 +SWL-S08,S08-C016,control,baseline,0,0,0,0,0,0,54.463514 +SWL-S08,S08-C016,control,post,1,0,1,0,0,0,51.72427 +SWL-S08,S08-C016,control,followup,2,0,0,1,0,0,50.700432 +SWL-S08,S08-C017,control,baseline,0,0,0,0,0,0,42.495182 +SWL-S08,S08-C017,control,post,1,0,1,0,0,0,44.525786 +SWL-S08,S08-C017,control,followup,2,0,0,1,0,0,48.817666 +SWL-S08,S08-C018,control,baseline,0,0,0,0,0,0,49.375649 +SWL-S08,S08-C018,control,post,1,0,1,0,0,0,49.775098 +SWL-S08,S08-C019,control,baseline,0,0,0,0,0,0,55.895405 +SWL-S08,S08-C019,control,post,1,0,1,0,0,0,52.966178 +SWL-S08,S08-C019,control,followup,2,0,0,1,0,0,54.566558 +SWL-S08,S08-C020,control,baseline,0,0,0,0,0,0,49.222153 +SWL-S08,S08-C020,control,post,1,0,1,0,0,0,53.321863 +SWL-S08,S08-C020,control,followup,2,0,0,1,0,0,57.72634 +SWL-S08,S08-C021,control,baseline,0,0,0,0,0,0,47.641432 +SWL-S08,S08-C021,control,post,1,0,1,0,0,0,50.552459 +SWL-S08,S08-C021,control,followup,2,0,0,1,0,0,47.842036 +SWL-S08,S08-C022,control,baseline,0,0,0,0,0,0,49.248054 +SWL-S08,S08-C022,control,post,1,0,1,0,0,0,49.073175 +SWL-S08,S08-C022,control,followup,2,0,0,1,0,0,48.161863 +SWL-S08,S08-C023,control,baseline,0,0,0,0,0,0,50.417981 +SWL-S08,S08-C023,control,post,1,0,1,0,0,0,55.683973 +SWL-S08,S08-C024,control,baseline,0,0,0,0,0,0,50.226058 +SWL-S08,S08-C024,control,post,1,0,1,0,0,0,51.472191 +SWL-S08,S08-C024,control,followup,2,0,0,1,0,0,47.666415 +SWL-S08,S08-C025,control,baseline,0,0,0,0,0,0,45.618223 +SWL-S08,S08-C025,control,post,1,0,1,0,0,0,42.909468 +SWL-S08,S08-C025,control,followup,2,0,0,1,0,0,43.691331 +SWL-S08,S08-C026,control,baseline,0,0,0,0,0,0,51.560847 +SWL-S08,S08-C026,control,post,1,0,1,0,0,0,54.181901 +SWL-S08,S08-C026,control,followup,2,0,0,1,0,0,58.309188 +SWL-S08,S08-C027,control,baseline,0,0,0,0,0,0,58.553394 +SWL-S08,S08-C027,control,post,1,0,1,0,0,0,57.878357 +SWL-S08,S08-C027,control,followup,2,0,0,1,0,0,56.751705 +SWL-S08,S08-C028,control,baseline,0,0,0,0,0,0,46.42106 +SWL-S08,S08-C028,control,post,1,0,1,0,0,0,44.607289 +SWL-S08,S08-C028,control,followup,2,0,0,1,0,0,47.276808 +SWL-S08,S08-C029,control,baseline,0,0,0,0,0,0,43.597409 +SWL-S08,S08-C029,control,post,1,0,1,0,0,0,48.807536 +SWL-S08,S08-C029,control,followup,2,0,0,1,0,0,51.972026 +SWL-S08,S08-C030,control,baseline,0,0,0,0,0,0,55.227845 +SWL-S08,S08-C030,control,post,1,0,1,0,0,0,57.11112 +SWL-S08,S08-C030,control,followup,2,0,0,1,0,0,53.423655 +SWL-S08,S08-T001,treatment,baseline,0,1,0,0,0,0,57.708914 +SWL-S08,S08-T001,treatment,post,1,1,1,0,1,0,59.708528 +SWL-S08,S08-T001,treatment,followup,2,1,0,1,0,1,62.648492 +SWL-S08,S08-T002,treatment,baseline,0,1,0,0,0,0,44.137754 +SWL-S08,S08-T002,treatment,post,1,1,1,0,1,0,52.178234 +SWL-S08,S08-T002,treatment,followup,2,1,0,1,0,1,59.836042 +SWL-S08,S08-T003,treatment,baseline,0,1,0,0,0,0,47.505657 +SWL-S08,S08-T003,treatment,post,1,1,1,0,1,0,55.502213 +SWL-S08,S08-T003,treatment,followup,2,1,0,1,0,1,56.900707 +SWL-S08,S08-T004,treatment,baseline,0,1,0,0,0,0,53.247496 +SWL-S08,S08-T004,treatment,post,1,1,1,0,1,0,58.083229 +SWL-S08,S08-T004,treatment,followup,2,1,0,1,0,1,60.167692 +SWL-S08,S08-T005,treatment,baseline,0,1,0,0,0,0,48.067374 +SWL-S08,S08-T005,treatment,post,1,1,1,0,1,0,59.266437 +SWL-S08,S08-T006,treatment,baseline,0,1,0,0,0,0,48.170446 +SWL-S08,S08-T006,treatment,post,1,1,1,0,1,0,57.222837 +SWL-S08,S08-T006,treatment,followup,2,1,0,1,0,1,55.508316 +SWL-S08,S08-T007,treatment,baseline,0,1,0,0,0,0,51.46552 +SWL-S08,S08-T007,treatment,followup,2,1,0,1,0,1,56.85284 +SWL-S08,S08-T008,treatment,baseline,0,1,0,0,0,0,53.779142 +SWL-S08,S08-T008,treatment,post,1,1,1,0,1,0,62.807827 +SWL-S08,S08-T008,treatment,followup,2,1,0,1,0,1,68.068692 +SWL-S08,S08-T009,treatment,baseline,0,1,0,0,0,0,52.751618 +SWL-S08,S08-T009,treatment,post,1,1,1,0,1,0,58.392688 +SWL-S08,S08-T009,treatment,followup,2,1,0,1,0,1,58.905982 +SWL-S08,S08-T010,treatment,baseline,0,1,0,0,0,0,44.898764 +SWL-S08,S08-T010,treatment,post,1,1,1,0,1,0,47.4223 +SWL-S08,S08-T010,treatment,followup,2,1,0,1,0,1,51.897769 +SWL-S08,S08-T011,treatment,baseline,0,1,0,0,0,0,48.365085 +SWL-S08,S08-T011,treatment,post,1,1,1,0,1,0,57.648778 +SWL-S08,S08-T012,treatment,baseline,0,1,0,0,0,0,56.759511 +SWL-S08,S08-T012,treatment,post,1,1,1,0,1,0,63.995064 +SWL-S08,S08-T012,treatment,followup,2,1,0,1,0,1,64.264884 +SWL-S08,S08-T013,treatment,baseline,0,1,0,0,0,0,47.006415 +SWL-S08,S08-T013,treatment,post,1,1,1,0,1,0,52.753412 +SWL-S08,S08-T013,treatment,followup,2,1,0,1,0,1,55.608171 +SWL-S08,S08-T014,treatment,baseline,0,1,0,0,0,0,44.901223 +SWL-S08,S08-T014,treatment,post,1,1,1,0,1,0,56.775668 +SWL-S08,S08-T014,treatment,followup,2,1,0,1,0,1,60.50382 +SWL-S08,S08-T015,treatment,baseline,0,1,0,0,0,0,57.635214 +SWL-S08,S08-T015,treatment,post,1,1,1,0,1,0,65.039601 +SWL-S08,S08-T015,treatment,followup,2,1,0,1,0,1,62.782749 +SWL-S08,S08-T016,treatment,baseline,0,1,0,0,0,0,51.425794 +SWL-S08,S08-T016,treatment,post,1,1,1,0,1,0,55.93076 +SWL-S08,S08-T016,treatment,followup,2,1,0,1,0,1,58.637836 +SWL-S08,S08-T017,treatment,baseline,0,1,0,0,0,0,42.638755 +SWL-S08,S08-T017,treatment,post,1,1,1,0,1,0,51.778966 +SWL-S08,S08-T018,treatment,baseline,0,1,0,0,0,0,52.416373 +SWL-S08,S08-T018,treatment,followup,2,1,0,1,0,1,57.206602 +SWL-S08,S08-T019,treatment,baseline,0,1,0,0,0,0,54.888425 +SWL-S08,S08-T019,treatment,post,1,1,1,0,1,0,58.517718 +SWL-S08,S08-T019,treatment,followup,2,1,0,1,0,1,64.344226 +SWL-S08,S08-T020,treatment,baseline,0,1,0,0,0,0,48.777844 +SWL-S08,S08-T020,treatment,post,1,1,1,0,1,0,58.94355 +SWL-S08,S08-T020,treatment,followup,2,1,0,1,0,1,64.721699 +SWL-S08,S08-T021,treatment,baseline,0,1,0,0,0,0,49.10017 +SWL-S08,S08-T021,treatment,post,1,1,1,0,1,0,55.56812 +SWL-S08,S08-T021,treatment,followup,2,1,0,1,0,1,55.095273 +SWL-S08,S08-T022,treatment,baseline,0,1,0,0,0,0,50.061392 +SWL-S08,S08-T022,treatment,post,1,1,1,0,1,0,56.975378 +SWL-S08,S08-T022,treatment,followup,2,1,0,1,0,1,60.553825 +SWL-S08,S08-T023,treatment,baseline,0,1,0,0,0,0,51.318899 +SWL-S08,S08-T023,treatment,post,1,1,1,0,1,0,63.245957 +SWL-S08,S08-T023,treatment,followup,2,1,0,1,0,1,65.720769 +SWL-S08,S08-T024,treatment,baseline,0,1,0,0,0,0,48.908545 +SWL-S08,S08-T024,treatment,post,1,1,1,0,1,0,54.662239 +SWL-S08,S08-T024,treatment,followup,2,1,0,1,0,1,52.502216 +SWL-S08,S08-T025,treatment,baseline,0,1,0,0,0,0,44.976533 +SWL-S08,S08-T025,treatment,post,1,1,1,0,1,0,50.027775 +SWL-S08,S08-T025,treatment,followup,2,1,0,1,0,1,54.324834 +SWL-S08,S08-T026,treatment,baseline,0,1,0,0,0,0,54.552688 +SWL-S08,S08-T026,treatment,post,1,1,1,0,1,0,63.44266 +SWL-S08,S08-T026,treatment,followup,2,1,0,1,0,1,67.78072 +SWL-S08,S08-T027,treatment,baseline,0,1,0,0,0,0,57.731167 +SWL-S08,S08-T027,treatment,post,1,1,1,0,1,0,60.927494 +SWL-S08,S08-T027,treatment,followup,2,1,0,1,0,1,61.852314 +SWL-S08,S08-T028,treatment,baseline,0,1,0,0,0,0,44.623195 +SWL-S08,S08-T028,treatment,post,1,1,1,0,1,0,49.792188 +SWL-S08,S08-T028,treatment,followup,2,1,0,1,0,1,56.593781 +SWL-S08,S08-T029,treatment,baseline,0,1,0,0,0,0,47.907972 +SWL-S08,S08-T029,treatment,post,1,1,1,0,1,0,58.49026 +SWL-S08,S08-T029,treatment,followup,2,1,0,1,0,1,62.665219 +SWL-S08,S08-T030,treatment,baseline,0,1,0,0,0,0,56.272115 +SWL-S08,S08-T030,treatment,post,1,1,1,0,1,0,62.100118 +SWL-S08,S08-T030,treatment,followup,2,1,0,1,0,1,61.354528 diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s09_outcome_distribution_extension.csv b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s09_outcome_distribution_extension.csv new file mode 100644 index 0000000..4f0eb1b --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s09_outcome_distribution_extension.csv @@ -0,0 +1,161 @@ +study_id,participant_id,support_condition,clinically_improved,recovery_burden_days +SWL-S09,S09-001,comparison,yes,5.147 +SWL-S09,S09-002,comparison,yes,9.845 +SWL-S09,S09-003,comparison,no,34.355 +SWL-S09,S09-004,comparison,yes,8.519 +SWL-S09,S09-005,comparison,no,27.155 +SWL-S09,S09-006,comparison,no,7.515 +SWL-S09,S09-007,comparison,yes,21.678 +SWL-S09,S09-008,comparison,no,6.754 +SWL-S09,S09-009,comparison,no,17.512 +SWL-S09,S09-010,comparison,yes,6.179 +SWL-S09,S09-011,comparison,no,14.345 +SWL-S09,S09-012,comparison,no,5.746 +SWL-S09,S09-013,comparison,yes,11.938 +SWL-S09,S09-014,comparison,no,5.199 +SWL-S09,S09-015,comparison,yes,10.11 +SWL-S09,S09-016,comparison,yes,35.765 +SWL-S09,S09-017,comparison,no,8.722 +SWL-S09,S09-018,comparison,yes,28.23 +SWL-S09,S09-019,comparison,no,7.67 +SWL-S09,S09-020,comparison,no,22.497 +SWL-S09,S09-021,comparison,yes,6.873 +SWL-S09,S09-022,comparison,no,18.137 +SWL-S09,S09-023,comparison,no,6.271 +SWL-S09,S09-024,comparison,yes,14.821 +SWL-S09,S09-025,comparison,no,5.817 +SWL-S09,S09-026,comparison,yes,12.302 +SWL-S09,S09-027,comparison,yes,5.254 +SWL-S09,S09-028,comparison,no,10.388 +SWL-S09,S09-029,comparison,yes,37.241 +SWL-S09,S09-030,comparison,no,8.935 +SWL-S09,S09-031,comparison,no,29.354 +SWL-S09,S09-032,comparison,yes,7.833 +SWL-S09,S09-033,comparison,no,23.354 +SWL-S09,S09-034,comparison,no,6.998 +SWL-S09,S09-035,comparison,yes,18.79 +SWL-S09,S09-036,comparison,no,6.367 +SWL-S09,S09-037,comparison,yes,15.32 +SWL-S09,S09-038,comparison,yes,5.891 +SWL-S09,S09-039,comparison,no,12.682 +SWL-S09,S09-040,comparison,yes,5.311 +SWL-S09,S09-041,comparison,no,10.678 +SWL-S09,S09-042,comparison,no,38.785 +SWL-S09,S09-043,comparison,yes,9.157 +SWL-S09,S09-044,comparison,no,30.53 +SWL-S09,S09-045,comparison,no,8.003 +SWL-S09,S09-046,comparison,yes,24.25 +SWL-S09,S09-047,comparison,no,7.128 +SWL-S09,S09-048,comparison,yes,19.473 +SWL-S09,S09-049,comparison,yes,6.467 +SWL-S09,S09-050,comparison,no,15.841 +SWL-S09,S09-051,comparison,yes,5.968 +SWL-S09,S09-052,comparison,no,13.08 +SWL-S09,S09-053,comparison,no,5.371 +SWL-S09,S09-054,comparison,yes,10.982 +SWL-S09,S09-055,comparison,no,40.4 +SWL-S09,S09-056,comparison,no,9.389 +SWL-S09,S09-057,comparison,yes,31.761 +SWL-S09,S09-058,comparison,no,8.18 +SWL-S09,S09-059,comparison,yes,25.188 +SWL-S09,S09-060,comparison,yes,7.265 +SWL-S09,S09-061,comparison,no,20.188 +SWL-S09,S09-062,comparison,yes,6.572 +SWL-S09,S09-063,comparison,no,16.386 +SWL-S09,S09-064,comparison,no,6.048 +SWL-S09,S09-065,comparison,yes,13.496 +SWL-S09,S09-066,comparison,no,5.433 +SWL-S09,S09-067,comparison,no,11.3 +SWL-S09,S09-068,comparison,yes,42.091 +SWL-S09,S09-069,comparison,no,9.632 +SWL-S09,S09-070,comparison,yes,33.049 +SWL-S09,S09-071,comparison,yes,8.366 +SWL-S09,S09-072,comparison,no,26.169 +SWL-S09,S09-073,comparison,yes,7.407 +SWL-S09,S09-074,comparison,no,20.936 +SWL-S09,S09-075,comparison,no,6.681 +SWL-S09,S09-076,comparison,yes,16.957 +SWL-S09,S09-077,comparison,no,6.132 +SWL-S09,S09-078,comparison,no,13.931 +SWL-S09,S09-079,comparison,yes,5.498 +SWL-S09,S09-080,comparison,no,11.632 +SWL-S09,S09-081,intervention,yes,3.473 +SWL-S09,S09-082,intervention,yes,8.726 +SWL-S09,S09-083,intervention,no,3.211 +SWL-S09,S09-084,intervention,yes,7.238 +SWL-S09,S09-085,intervention,yes,28.144 +SWL-S09,S09-086,intervention,no,6.11 +SWL-S09,S09-087,intervention,yes,22.001 +SWL-S09,S09-088,intervention,yes,5.255 +SWL-S09,S09-089,intervention,no,17.328 +SWL-S09,S09-090,intervention,yes,4.608 +SWL-S09,S09-091,intervention,yes,13.776 +SWL-S09,S09-092,intervention,no,4.12 +SWL-S09,S09-093,intervention,yes,11.075 +SWL-S09,S09-094,intervention,no,3.532 +SWL-S09,S09-095,intervention,yes,9.023 +SWL-S09,S09-096,intervention,yes,3.256 +SWL-S09,S09-097,intervention,no,7.465 +SWL-S09,S09-098,intervention,yes,3.051 +SWL-S09,S09-099,intervention,yes,6.284 +SWL-S09,S09-100,intervention,no,22.918 +SWL-S09,S09-101,intervention,yes,5.388 +SWL-S09,S09-102,intervention,yes,18.028 +SWL-S09,S09-103,intervention,no,4.711 +SWL-S09,S09-104,intervention,yes,14.309 +SWL-S09,S09-105,intervention,no,4.199 +SWL-S09,S09-106,intervention,yes,11.482 +SWL-S09,S09-107,intervention,yes,3.593 +SWL-S09,S09-108,intervention,no,9.334 +SWL-S09,S09-109,intervention,yes,3.304 +SWL-S09,S09-110,intervention,yes,7.703 +SWL-S09,S09-111,intervention,no,3.088 +SWL-S09,S09-112,intervention,yes,6.465 +SWL-S09,S09-113,intervention,yes,23.878 +SWL-S09,S09-114,intervention,no,5.527 +SWL-S09,S09-115,intervention,yes,18.76 +SWL-S09,S09-116,intervention,yes,4.818 +SWL-S09,S09-117,intervention,yes,14.867 +SWL-S09,S09-118,intervention,yes,4.281 +SWL-S09,S09-119,intervention,no,11.908 +SWL-S09,S09-120,intervention,yes,3.656 +SWL-S09,S09-121,intervention,yes,9.659 +SWL-S09,S09-122,intervention,no,3.353 +SWL-S09,S09-123,intervention,yes,7.951 +SWL-S09,S09-124,intervention,yes,3.126 +SWL-S09,S09-125,intervention,no,6.655 +SWL-S09,S09-126,intervention,yes,24.883 +SWL-S09,S09-127,intervention,yes,5.673 +SWL-S09,S09-128,intervention,yes,19.525 +SWL-S09,S09-129,intervention,yes,4.929 +SWL-S09,S09-130,intervention,no,15.451 +SWL-S09,S09-131,intervention,yes,4.367 +SWL-S09,S09-132,intervention,yes,12.353 +SWL-S09,S09-133,intervention,no,3.723 +SWL-S09,S09-134,intervention,yes,9.999 +SWL-S09,S09-135,intervention,yes,3.405 +SWL-S09,S09-136,intervention,no,8.211 +SWL-S09,S09-137,intervention,yes,3.166 +SWL-S09,S09-138,intervention,yes,6.854 +SWL-S09,S09-139,intervention,yes,25.934 +SWL-S09,S09-140,intervention,yes,5.825 +SWL-S09,S09-141,intervention,no,20.327 +SWL-S09,S09-142,intervention,yes,5.046 +SWL-S09,S09-143,intervention,yes,16.062 +SWL-S09,S09-144,intervention,no,4.457 +SWL-S09,S09-145,intervention,yes,12.819 +SWL-S09,S09-146,intervention,yes,3.792 +SWL-S09,S09-147,intervention,no,10.355 +SWL-S09,S09-148,intervention,yes,3.458 +SWL-S09,S09-149,intervention,yes,8.483 +SWL-S09,S09-150,intervention,yes,3.208 +SWL-S09,S09-151,intervention,yes,7.062 +SWL-S09,S09-152,intervention,no,27.034 +SWL-S09,S09-153,intervention,yes,5.984 +SWL-S09,S09-154,intervention,yes,21.165 +SWL-S09,S09-155,intervention,no,5.168 +SWL-S09,S09-156,intervention,yes,16.701 +SWL-S09,S09-157,intervention,yes,4.551 +SWL-S09,S09-158,intervention,no,13.307 +SWL-S09,S09-159,intervention,yes,3.865 +SWL-S09,S09-160,intervention,yes,10.727 diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s10_replication_program.csv b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s10_replication_program.csv new file mode 100644 index 0000000..d8afbeb --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/data/swl_s10_replication_program.csv @@ -0,0 +1,7 @@ +study_id,program_id,replication_type,registered_before_analysis,n_per_group,total_n,observed_cohens_d,sampling_variance_d +S10-R01,SWL-S10,direct,yes,70,140,0.28,0.0291478992 +S10-R02,SWL-S10,direct,yes,85,170,0.42,0.0245920624 +S10-R03,SWL-S10,direct,yes,60,120,0.35,0.0343893678 +S10-R04,SWL-S10,conceptual,yes,100,200,0.51,0.0213270408 +S10-R05,SWL-S10,direct,yes,75,150,0.31,0.0273248858 +S10-R06,SWL-S10,conceptual,yes,90,180,0.46,0.0234244949 diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S01_DESIGN_CONTRACT.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S01_DESIGN_CONTRACT.json new file mode 100644 index 0000000..8e4c351 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S01_DESIGN_CONTRACT.json @@ -0,0 +1,34 @@ +{ + "analysis_version": "0.1", + "batch_id": "psych-design-swl-s01-s06-s07-v0.1", + "causal_scope": "Association and within-domain prediction only; no intervention or causal claim is authorized.", + "chapter_ids": [ + "ch11", + "ch12" + ], + "contract_version": "0.1", + "design": "Cross-sectional synthetic survey with one row per participant.", + "identifier_fields": [ + "participant_id" + ], + "missingness_rule": "No missing values in the registered synthetic dataset.", + "outcomes": [ + "academic_confidence", + "assessment_performance" + ], + "predictors": [ + "sleep_quality", + "study_hours_per_week", + "academic_confidence" + ], + "real_data_authorized": false, + "research_questions": [ + "How are sleep quality and academic confidence associated in the synthetic survey records?", + "How well do weekly study hours and academic confidence predict synthetic assessment performance within the registered predictor domain?" + ], + "row_structure": "One row per synthetic participant.", + "short_name": "Student Well-Being Survey", + "study_id": "SWL-S01", + "synthetic_only": true, + "unit_of_analysis": "student" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S02_DESIGN_CONTRACT.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S02_DESIGN_CONTRACT.json new file mode 100644 index 0000000..21e3bea --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S02_DESIGN_CONTRACT.json @@ -0,0 +1,32 @@ +{ + "apa_source_map": "evidence/SWL-S02/APA_RESULT_SOURCE_MAP.json", + "assignment_procedure": "Synthetic students are assigned in equal numbers by a declared computer-generated blocked procedure.", + "batch_id": "psych-design-swl-s02-s05-v0.1", + "causal_scope": "Causal interpretation is limited to the declared assignment, implementation, and synthetic pilot conditions.", + "chapter_id": "ch06", + "contract_version": "0.1", + "data_file": "data/swl_s02_structured_study_routine.csv", + "design": "Two-group pilot with declared stratified random assignment.", + "factor_levels": { + "study_routine_group": [ + "standard_routine", + "structured_routine" + ] + }, + "figure_spec": "evidence/SWL-S02/FIGURE_SPEC.json", + "identifier_fields": [ + "participant_id" + ], + "matched_limitation": "evidence/SWL-S02/MATCHED_LIMITATION.json", + "missingness_rule": "No missing outcomes in the registered synthetic dataset.", + "outcome": "post_session_performance", + "python_analysis": "scripts/python/studies.py", + "r_verification": "scripts/r/swl_s02.R", + "research_question": "Do students assigned to a structured study routine differ in post-session performance from students using a standard routine?", + "result_receipt": "evidence/SWL-S02/PYTHON_RESULT_RECEIPT.json", + "row_structure": "One row per student.", + "short_name": "Structured Study-Routine Pilot", + "study_id": "SWL-S02", + "synthetic_only": true, + "unit_of_analysis": "student" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S03_DESIGN_CONTRACT.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S03_DESIGN_CONTRACT.json new file mode 100644 index 0000000..bbc6cd3 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S03_DESIGN_CONTRACT.json @@ -0,0 +1,33 @@ +{ + "apa_source_map": "evidence/SWL-S03/APA_RESULT_SOURCE_MAP.json", + "assignment_procedure": "No control condition is present.", + "batch_id": "psych-design-swl-s02-s05-v0.1", + "causal_scope": "The analysis estimates within-person change, not the workshop's causal effect in the absence of a control condition.", + "chapter_id": "ch07", + "contract_version": "0.1", + "data_file": "data/swl_s03_skills_workshop_pre_post.csv", + "design": "Within-person pre/post study.", + "factor_levels": { + "occasion": [ + "pre", + "post" + ] + }, + "figure_spec": "evidence/SWL-S03/FIGURE_SPEC.json", + "identifier_fields": [ + "participant_id", + "occasion" + ], + "matched_limitation": "evidence/SWL-S03/MATCHED_LIMITATION.json", + "missingness_rule": "The registered dataset contains exactly one pre and one post row per participant; incomplete pairs are not silently retained.", + "outcome": "academic_confidence", + "python_analysis": "scripts/python/studies.py", + "r_verification": "scripts/r/swl_s03.R", + "research_question": "How does academic confidence change from before to after a skills workshop for students with linked observations?", + "result_receipt": "evidence/SWL-S03/PYTHON_RESULT_RECEIPT.json", + "row_structure": "Two occasion rows per participant in long form.", + "short_name": "Skills Workshop Pre/Post Study", + "study_id": "SWL-S03", + "synthetic_only": true, + "unit_of_analysis": "student" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S04_DESIGN_CONTRACT.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S04_DESIGN_CONTRACT.json new file mode 100644 index 0000000..5184d0a --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S04_DESIGN_CONTRACT.json @@ -0,0 +1,33 @@ +{ + "apa_source_map": "evidence/SWL-S04/APA_RESULT_SOURCE_MAP.json", + "assignment_procedure": "Synthetic students are assigned in equal numbers to three declared conditions.", + "batch_id": "psych-design-swl-s02-s05-v0.1", + "causal_scope": "Causal interpretation is conditional on the declared random assignment and implementation fidelity.", + "chapter_id": "ch08", + "contract_version": "0.1", + "data_file": "data/swl_s04_three_condition_support.csv", + "design": "Three-condition between-student randomized study.", + "factor_levels": { + "support_condition": [ + "standard_support", + "guided_practice", + "guided_practice_plus_feedback" + ] + }, + "figure_spec": "evidence/SWL-S04/FIGURE_SPEC.json", + "identifier_fields": [ + "participant_id" + ], + "matched_limitation": "evidence/SWL-S04/MATCHED_LIMITATION.json", + "missingness_rule": "No missing outcomes in the registered synthetic dataset.", + "outcome": "assessment_performance", + "python_analysis": "scripts/python/studies.py", + "r_verification": "scripts/r/swl_s04.R", + "research_question": "Do assessment outcomes differ across standard support, guided practice, and guided practice plus feedback, and which planned contrasts answer the substantive questions?", + "result_receipt": "evidence/SWL-S04/PYTHON_RESULT_RECEIPT.json", + "row_structure": "One row per student.", + "short_name": "Three-Condition Learning-Support Study", + "study_id": "SWL-S04", + "synthetic_only": true, + "unit_of_analysis": "student" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S05_DESIGN_CONTRACT.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S05_DESIGN_CONTRACT.json new file mode 100644 index 0000000..a153351 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S05_DESIGN_CONTRACT.json @@ -0,0 +1,36 @@ +{ + "apa_source_map": "evidence/SWL-S05/APA_RESULT_SOURCE_MAP.json", + "assignment_procedure": "Synthetic students are assigned in equal numbers to all four cells.", + "batch_id": "psych-design-swl-s02-s05-v0.1", + "causal_scope": "Causal interpretation is conditional on random assignment, implementation fidelity, and the defined factor levels.", + "chapter_id": "ch09", + "contract_version": "0.1", + "data_file": "data/swl_s05_strategy_feedback.csv", + "design": "Balanced two-factor between-student factorial experiment.", + "factor_levels": { + "feedback_condition": [ + "no_feedback", + "explanatory_feedback" + ], + "study_strategy": [ + "rereading", + "retrieval_practice" + ] + }, + "figure_spec": "evidence/SWL-S05/FIGURE_SPEC.json", + "identifier_fields": [ + "participant_id" + ], + "matched_limitation": "evidence/SWL-S05/MATCHED_LIMITATION.json", + "missingness_rule": "The registered dataset contains all four cells with equal counts and no missing outcomes.", + "outcome": "assessment_performance", + "python_analysis": "scripts/python/studies.py", + "r_verification": "scripts/r/swl_s05.R", + "research_question": "Does the effect of study strategy on assessment performance depend on the feedback condition?", + "result_receipt": "evidence/SWL-S05/PYTHON_RESULT_RECEIPT.json", + "row_structure": "One row per student with exactly one valid strategy-by-feedback cell.", + "short_name": "Strategy-by-Feedback Experiment", + "study_id": "SWL-S05", + "synthetic_only": true, + "unit_of_analysis": "student" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S06_DESIGN_CONTRACT.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S06_DESIGN_CONTRACT.json new file mode 100644 index 0000000..c5afc27 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S06_DESIGN_CONTRACT.json @@ -0,0 +1,34 @@ +{ + "analysis_version": "0.1", + "batch_id": "psych-design-swl-s01-s06-s07-v0.1", + "causal_scope": "Occasion differences are descriptive/inferential within the registered synthetic schedule and do not identify a unique cause.", + "chapter_ids": [ + "ch10" + ], + "contract_version": "0.1", + "design": "Three-occasion repeated-measures study with six scheduled outcomes missing.", + "factor_levels": { + "occasion": [ + "baseline", + "post", + "follow_up" + ] + }, + "identifier_fields": [ + "participant_id", + "occasion" + ], + "missingness_rule": "The registered repeated-measures ANOVA uses complete trajectories and separately reports all missing visits.", + "outcomes": [ + "academic_confidence" + ], + "real_data_authorized": false, + "research_questions": [ + "Does mean academic confidence differ across baseline, post, and follow-up among complete synthetic trajectories?" + ], + "row_structure": "Three scheduled participant-occasion rows per student.", + "short_name": "Confidence Trajectory Study", + "study_id": "SWL-S06", + "synthetic_only": true, + "unit_of_analysis": "student" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S07_DESIGN_CONTRACT.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S07_DESIGN_CONTRACT.json new file mode 100644 index 0000000..c9f10d9 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S07_DESIGN_CONTRACT.json @@ -0,0 +1,36 @@ +{ + "analysis_version": "0.1", + "batch_id": "psych-design-swl-s01-s06-s07-v0.1", + "causal_scope": "Causal language remains conditional on the declared assignment and intervention implementation; adjustment alone does not create randomization.", + "chapter_ids": [ + "ch13" + ], + "contract_version": "0.1", + "design": "Two-group synthetic intervention study with one pre-treatment baseline covariate.", + "factor_levels": { + "intervention_group": [ + "comparison", + "intervention" + ] + }, + "identifier_fields": [ + "participant_id" + ], + "missingness_rule": "No missing values in the registered synthetic dataset.", + "outcomes": [ + "post_performance" + ], + "predictors": [ + "intervention_group", + "baseline_performance" + ], + "real_data_authorized": false, + "research_questions": [ + "Do the intervention and comparison groups differ in post performance after adjustment to the overall baseline mean?" + ], + "row_structure": "One row per synthetic participant.", + "short_name": "Baseline-Adjusted Intervention", + "study_id": "SWL-S07", + "synthetic_only": true, + "unit_of_analysis": "student" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S08_DESIGN_CONTRACT.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S08_DESIGN_CONTRACT.json new file mode 100644 index 0000000..017b6ff --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S08_DESIGN_CONTRACT.json @@ -0,0 +1,35 @@ +{ + "chapter_ids": [ + "ch14" + ], + "claim_boundary": "The synthetic model demonstrates differential change under the declared random-intercept specification; it is not evidence about a real treatment.", + "contract_version": "0.1", + "data_layout": { + "format": "long", + "observed_rows": 168, + "participants": 60, + "primary_key": [ + "participant_id", + "occasion" + ], + "scheduled_rows": 180, + "unequal_visit_counts_permitted": true + }, + "dataset": "data/swl_s08_longitudinal_treatment.csv", + "design": "two-group longitudinal study with three scheduled occasions", + "estimands": [ + "additional treatment-group change from baseline to post", + "additional treatment-group change from baseline to follow-up" + ], + "missingness_fixture": "Twelve visit omissions are fixed by participant identifier before outcome generation and do not depend on realized outcomes.", + "model": { + "engine": "statsmodels MixedLM", + "estimation": "maximum likelihood", + "fixed_effects": "well_being_score ~ treatment + post + followup + treatment_post + treatment_followup", + "random_effects": "random intercept for participant_id" + }, + "outcome": "well_being_score", + "study_id": "SWL-S08", + "synthetic_only": true, + "verification_category": "V3 fixed-effect estimand alignment plus V4 inference documentation" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S09_DESIGN_CONTRACT.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S09_DESIGN_CONTRACT.json new file mode 100644 index 0000000..18cad62 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S09_DESIGN_CONTRACT.json @@ -0,0 +1,28 @@ +{ + "chapter_ids": [ + "ch15" + ], + "claim_boundary": "The categorical and rank-based analyses answer different questions about two registered outcomes; neither is a generic repair for a failed normality test.", + "contract_version": "0.1", + "data_layout": { + "format": "one row per participant", + "participants": 160, + "participants_per_condition": 80, + "primary_key": [ + "participant_id" + ] + }, + "dataset": "data/swl_s09_outcome_distribution_extension.csv", + "design": "two independent support conditions with two registered outcomes", + "estimands": [ + "association between condition and binary clinical-improvement status", + "distributional shift in positive-skew recovery burden between conditions" + ], + "outcomes": { + "categorical": "clinically_improved", + "positive_skew_continuous": "recovery_burden_days" + }, + "study_id": "SWL-S09", + "synthetic_only": true, + "verification_category": "V2 numeric parity" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S10_DESIGN_CONTRACT.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S10_DESIGN_CONTRACT.json new file mode 100644 index 0000000..75e8091 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/design/SWL-S10_DESIGN_CONTRACT.json @@ -0,0 +1,23 @@ +{ + "chapter_ids": [ + "ch16" + ], + "claim_boundary": "Prospective power depends on a justified target effect and model; the fixed-effect synthesis assumes one common true effect and does not address publication bias or selective reporting.", + "contract_version": "0.1", + "data_layout": { + "format": "one row per completed synthetic replication", + "primary_key": [ + "study_id" + ], + "replication_count": 6 + }, + "dataset": "data/swl_s10_replication_program.csv", + "design": "registered replication program summarized at study level", + "estimands": [ + "prospective per-group sample size for d=0.40 at 80 percent power", + "fixed-effect pooled standardized mean difference across six replications" + ], + "study_id": "SWL-S10", + "synthetic_only": true, + "verification_category": "V2 numeric parity" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S01/FIGURE_SPEC_CORRELATION.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S01/FIGURE_SPEC_CORRELATION.json new file mode 100644 index 0000000..ca0dfb9 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S01/FIGURE_SPEC_CORRELATION.json @@ -0,0 +1,15 @@ +{ + "batch_id": "psych-design-swl-s01-s06-s07-v0.1", + "chapter_ids": [ + "ch11", + "ch12" + ], + "contract_version": "0.1", + "figure_role": "correlation", + "figure_type": "correlation_scatter", + "generator": "scripts/python/generate_figures.py", + "grayscale": true, + "result_source": "evidence/SWL-S01/PYTHON_RESULT_RECEIPT.json", + "study_id": "SWL-S01", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S01/FIGURE_SPEC_REGRESSION.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S01/FIGURE_SPEC_REGRESSION.json new file mode 100644 index 0000000..9c44a6e --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S01/FIGURE_SPEC_REGRESSION.json @@ -0,0 +1,15 @@ +{ + "batch_id": "psych-design-swl-s01-s06-s07-v0.1", + "chapter_ids": [ + "ch11", + "ch12" + ], + "contract_version": "0.1", + "figure_role": "regression", + "figure_type": "regression_residual_diagnostic", + "generator": "scripts/python/generate_figures.py", + "grayscale": true, + "result_source": "evidence/SWL-S01/PYTHON_RESULT_RECEIPT.json", + "study_id": "SWL-S01", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S02/FIGURE_SPEC.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S02/FIGURE_SPEC.json new file mode 100644 index 0000000..886a411 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S02/FIGURE_SPEC.json @@ -0,0 +1,13 @@ +{ + "batch_id": "psych-design-swl-s02-s05-v0.1", + "caption_scope": "Synthetic Structured Study-Routine Pilot display; interpretation remains bounded by the matched limitation.", + "contract_version": "0.1", + "data_file": "data/swl_s02_structured_study_routine.csv", + "figure_id": "SWL-S02-FIG-01", + "figure_type": "two_group_mean_ci", + "grayscale_required": true, + "minimum_dpi": 300, + "output_role": "generated_build_instance_not_tracked", + "source_script": "scripts/python/generate_figures.py", + "study_id": "SWL-S02" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S03/FIGURE_SPEC.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S03/FIGURE_SPEC.json new file mode 100644 index 0000000..dd0a544 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S03/FIGURE_SPEC.json @@ -0,0 +1,13 @@ +{ + "batch_id": "psych-design-swl-s02-s05-v0.1", + "caption_scope": "Synthetic Skills Workshop Pre/Post Study display; interpretation remains bounded by the matched limitation.", + "contract_version": "0.1", + "data_file": "data/swl_s03_skills_workshop_pre_post.csv", + "figure_id": "SWL-S03-FIG-01", + "figure_type": "paired_change", + "grayscale_required": true, + "minimum_dpi": 300, + "output_role": "generated_build_instance_not_tracked", + "source_script": "scripts/python/generate_figures.py", + "study_id": "SWL-S03" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S04/FIGURE_SPEC.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S04/FIGURE_SPEC.json new file mode 100644 index 0000000..c69d089 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S04/FIGURE_SPEC.json @@ -0,0 +1,13 @@ +{ + "batch_id": "psych-design-swl-s02-s05-v0.1", + "caption_scope": "Synthetic Three-Condition Learning-Support Study display; interpretation remains bounded by the matched limitation.", + "contract_version": "0.1", + "data_file": "data/swl_s04_three_condition_support.csv", + "figure_id": "SWL-S04-FIG-01", + "figure_type": "three_group_mean_ci", + "grayscale_required": true, + "minimum_dpi": 300, + "output_role": "generated_build_instance_not_tracked", + "source_script": "scripts/python/generate_figures.py", + "study_id": "SWL-S04" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S05/FIGURE_SPEC.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S05/FIGURE_SPEC.json new file mode 100644 index 0000000..ef137d3 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S05/FIGURE_SPEC.json @@ -0,0 +1,13 @@ +{ + "batch_id": "psych-design-swl-s02-s05-v0.1", + "caption_scope": "Synthetic Strategy-by-Feedback Experiment display; interpretation remains bounded by the matched limitation.", + "contract_version": "0.1", + "data_file": "data/swl_s05_strategy_feedback.csv", + "figure_id": "SWL-S05-FIG-01", + "figure_type": "factorial_interaction", + "grayscale_required": true, + "minimum_dpi": 300, + "output_role": "generated_build_instance_not_tracked", + "source_script": "scripts/python/generate_figures.py", + "study_id": "SWL-S05" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S06/FIGURE_SPEC.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S06/FIGURE_SPEC.json new file mode 100644 index 0000000..4c0c515 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S06/FIGURE_SPEC.json @@ -0,0 +1,14 @@ +{ + "batch_id": "psych-design-swl-s01-s06-s07-v0.1", + "chapter_ids": [ + "ch10" + ], + "contract_version": "0.1", + "figure_role": "primary", + "figure_type": "complete_case_trajectory", + "generator": "scripts/python/generate_figures.py", + "grayscale": true, + "result_source": "evidence/SWL-S06/PYTHON_RESULT_RECEIPT.json", + "study_id": "SWL-S06", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S07/FIGURE_SPEC.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S07/FIGURE_SPEC.json new file mode 100644 index 0000000..f0fa536 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S07/FIGURE_SPEC.json @@ -0,0 +1,14 @@ +{ + "batch_id": "psych-design-swl-s01-s06-s07-v0.1", + "chapter_ids": [ + "ch13" + ], + "contract_version": "0.1", + "figure_role": "primary", + "figure_type": "ancova_parallel_lines", + "generator": "scripts/python/generate_figures.py", + "grayscale": true, + "result_source": "evidence/SWL-S07/PYTHON_RESULT_RECEIPT.json", + "study_id": "SWL-S07", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S08/FIGURE_SPEC.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S08/FIGURE_SPEC.json new file mode 100644 index 0000000..2b32d2f --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S08/FIGURE_SPEC.json @@ -0,0 +1,14 @@ +{ + "caption": "Observed well-being means by group and occasion.", + "contract_version": "0.1", + "generator": "scripts/python/generate_figures.py", + "grayscale": true, + "output": "swl_s08_group_time_means.png", + "required_axis_labels": [ + "Occasion", + "Well-being score" + ], + "role": "descriptive_pattern_not_model_result", + "study_id": "SWL-S08", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S09/FIGURE_SPEC_CATEGORICAL.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S09/FIGURE_SPEC_CATEGORICAL.json new file mode 100644 index 0000000..77f264b --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S09/FIGURE_SPEC_CATEGORICAL.json @@ -0,0 +1,14 @@ +{ + "caption": "Improvement-status proportions by support condition.", + "contract_version": "0.1", + "generator": "scripts/python/generate_figures.py", + "grayscale": true, + "output": "swl_s09_improvement_proportions.png", + "required_axis_labels": [ + "Support condition", + "Proportion" + ], + "role": "categorical_outcome_descriptive", + "study_id": "SWL-S09", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S09/FIGURE_SPEC_RANK.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S09/FIGURE_SPEC_RANK.json new file mode 100644 index 0000000..af5efc3 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S09/FIGURE_SPEC_RANK.json @@ -0,0 +1,14 @@ +{ + "caption": "Positive-skew recovery burden by support condition.", + "contract_version": "0.1", + "generator": "scripts/python/generate_figures.py", + "grayscale": true, + "output": "swl_s09_recovery_burden.png", + "required_axis_labels": [ + "Support condition", + "Recovery burden (days)" + ], + "role": "rank_analysis_distribution_display", + "study_id": "SWL-S09", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S10/FIGURE_SPEC.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S10/FIGURE_SPEC.json new file mode 100644 index 0000000..5d1cbe1 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/figures/specs/SWL-S10/FIGURE_SPEC.json @@ -0,0 +1,14 @@ +{ + "caption": "Study-level standardized effects and fixed-effect pooled estimate.", + "contract_version": "0.1", + "generator": "scripts/python/generate_figures.py", + "grayscale": true, + "output": "swl_s10_fixed_effect_forest.png", + "required_axis_labels": [ + "Standardized mean difference", + "Replication" + ], + "role": "cumulative_evidence_forest_plot", + "study_id": "SWL-S10", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S01_MEASUREMENT_MAP.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S01_MEASUREMENT_MAP.json new file mode 100644 index 0000000..6ef749b --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S01_MEASUREMENT_MAP.json @@ -0,0 +1,35 @@ +{ + "factor_levels": {}, + "measurement_boundary": "Association and within-domain prediction only; no intervention or causal claim is authorized.", + "missingness_rule": "No missing values in the registered synthetic dataset.", + "outcomes": [ + "academic_confidence", + "assessment_performance" + ], + "predictors": [ + "sleep_quality", + "study_hours_per_week", + "academic_confidence" + ], + "real_data_authorized": false, + "schema_version": "psych-design-measurement-map-v0.1", + "score_direction_and_estimand": [ + "Pearson association between sleep quality and academic confidence", + "regression coefficients predicting assessment performance within the observed predictor domain" + ], + "source_columns": [ + "study_id", + "participant_id", + "sleep_quality", + "study_hours_per_week", + "anxiety_score", + "academic_confidence", + "assessment_performance" + ], + "study_id": "SWL-S01", + "synthetic_only": true, + "unique_key": [ + "participant_id" + ], + "unit_of_analysis": "student" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S02_MEASUREMENT_MAP.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S02_MEASUREMENT_MAP.json new file mode 100644 index 0000000..61d1691 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S02_MEASUREMENT_MAP.json @@ -0,0 +1,31 @@ +{ + "factor_levels": { + "study_routine_group": [ + "standard_routine", + "structured_routine" + ] + }, + "measurement_boundary": "Causal interpretation is limited to the declared assignment, implementation, and synthetic pilot conditions.", + "missingness_rule": "No missing outcomes in the registered synthetic dataset.", + "outcomes": [ + "post_session_performance" + ], + "predictors": [], + "real_data_authorized": false, + "schema_version": "psych-design-measurement-map-v0.1", + "score_direction_and_estimand": [ + "structured_routine minus standard_routine mean post-session performance" + ], + "source_columns": [ + "study_id", + "participant_id", + "study_routine_group", + "post_session_performance" + ], + "study_id": "SWL-S02", + "synthetic_only": true, + "unique_key": [ + "participant_id" + ], + "unit_of_analysis": "student" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S03_MEASUREMENT_MAP.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S03_MEASUREMENT_MAP.json new file mode 100644 index 0000000..71554d4 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S03_MEASUREMENT_MAP.json @@ -0,0 +1,32 @@ +{ + "factor_levels": { + "occasion": [ + "pre", + "post" + ] + }, + "measurement_boundary": "The analysis estimates within-person change, not the workshop's causal effect in the absence of a control condition.", + "missingness_rule": "The registered dataset contains exactly one pre and one post row per participant; incomplete pairs are not silently retained.", + "outcomes": [ + "academic_confidence" + ], + "predictors": [], + "real_data_authorized": false, + "schema_version": "psych-design-measurement-map-v0.1", + "score_direction_and_estimand": [ + "post minus pre mean paired change" + ], + "source_columns": [ + "study_id", + "participant_id", + "occasion", + "academic_confidence" + ], + "study_id": "SWL-S03", + "synthetic_only": true, + "unique_key": [ + "participant_id", + "occasion" + ], + "unit_of_analysis": "student" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S04_MEASUREMENT_MAP.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S04_MEASUREMENT_MAP.json new file mode 100644 index 0000000..a86974d --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S04_MEASUREMENT_MAP.json @@ -0,0 +1,33 @@ +{ + "factor_levels": { + "support_condition": [ + "standard_support", + "guided_practice", + "guided_practice_plus_feedback" + ] + }, + "measurement_boundary": "Causal interpretation is conditional on the declared random assignment and implementation fidelity.", + "missingness_rule": "No missing outcomes in the registered synthetic dataset.", + "outcomes": [ + "assessment_performance" + ], + "predictors": [], + "real_data_authorized": false, + "schema_version": "psych-design-measurement-map-v0.1", + "score_direction_and_estimand": [ + "guided-condition average minus standard support", + "guided practice plus feedback minus guided practice" + ], + "source_columns": [ + "study_id", + "participant_id", + "support_condition", + "assessment_performance" + ], + "study_id": "SWL-S04", + "synthetic_only": true, + "unique_key": [ + "participant_id" + ], + "unit_of_analysis": "student" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S05_MEASUREMENT_MAP.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S05_MEASUREMENT_MAP.json new file mode 100644 index 0000000..14fea77 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S05_MEASUREMENT_MAP.json @@ -0,0 +1,36 @@ +{ + "factor_levels": { + "feedback_condition": [ + "no_feedback", + "explanatory_feedback" + ], + "study_strategy": [ + "rereading", + "retrieval_practice" + ] + }, + "measurement_boundary": "Causal interpretation is conditional on random assignment, implementation fidelity, and the defined factor levels.", + "missingness_rule": "The registered dataset contains all four cells with equal counts and no missing outcomes.", + "outcomes": [ + "assessment_performance" + ], + "predictors": [], + "real_data_authorized": false, + "schema_version": "psych-design-measurement-map-v0.1", + "score_direction_and_estimand": [ + "retrieval-practice effect with feedback minus retrieval-practice effect without feedback" + ], + "source_columns": [ + "study_id", + "participant_id", + "study_strategy", + "feedback_condition", + "assessment_performance" + ], + "study_id": "SWL-S05", + "synthetic_only": true, + "unique_key": [ + "participant_id" + ], + "unit_of_analysis": "student" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S06_MEASUREMENT_MAP.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S06_MEASUREMENT_MAP.json new file mode 100644 index 0000000..45c3095 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S06_MEASUREMENT_MAP.json @@ -0,0 +1,33 @@ +{ + "factor_levels": { + "occasion": [ + "baseline", + "post", + "follow_up" + ] + }, + "measurement_boundary": "Occasion differences are descriptive/inferential within the registered synthetic schedule and do not identify a unique cause.", + "missingness_rule": "The registered repeated-measures ANOVA uses complete trajectories and separately reports all missing visits.", + "outcomes": [ + "academic_confidence" + ], + "predictors": [], + "real_data_authorized": false, + "schema_version": "psych-design-measurement-map-v0.1", + "score_direction_and_estimand": [ + "occasion effect across baseline, post, and follow_up among complete trajectories" + ], + "source_columns": [ + "study_id", + "participant_id", + "occasion", + "academic_confidence" + ], + "study_id": "SWL-S06", + "synthetic_only": true, + "unique_key": [ + "participant_id", + "occasion" + ], + "unit_of_analysis": "student" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S07_MEASUREMENT_MAP.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S07_MEASUREMENT_MAP.json new file mode 100644 index 0000000..caf10f1 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S07_MEASUREMENT_MAP.json @@ -0,0 +1,35 @@ +{ + "factor_levels": { + "intervention_group": [ + "comparison", + "intervention" + ] + }, + "measurement_boundary": "Causal language remains conditional on the declared assignment and intervention implementation; adjustment alone does not create randomization.", + "missingness_rule": "No missing values in the registered synthetic dataset.", + "outcomes": [ + "post_performance" + ], + "predictors": [ + "intervention_group", + "baseline_performance" + ], + "real_data_authorized": false, + "schema_version": "psych-design-measurement-map-v0.1", + "score_direction_and_estimand": [ + "intervention minus comparison adjusted post-performance difference at the overall baseline mean" + ], + "source_columns": [ + "study_id", + "participant_id", + "intervention_group", + "baseline_performance", + "post_performance" + ], + "study_id": "SWL-S07", + "synthetic_only": true, + "unique_key": [ + "participant_id" + ], + "unit_of_analysis": "student" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S08_MEASUREMENT_MAP.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S08_MEASUREMENT_MAP.json new file mode 100644 index 0000000..d70b0eb --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S08_MEASUREMENT_MAP.json @@ -0,0 +1,45 @@ +{ + "factor_levels": { + "occasion": [ + "baseline", + "post", + "followup" + ], + "treatment_group": [ + "control", + "treatment" + ] + }, + "measurement_boundary": "The synthetic model demonstrates differential change under the declared random-intercept specification; it is not evidence about a real treatment.", + "missingness_rule": "Twelve visit omissions are fixed by participant identifier before outcome generation and do not depend on realized outcomes.", + "outcomes": [ + "well_being_score" + ], + "predictors": [], + "real_data_authorized": false, + "schema_version": "psych-design-measurement-map-v0.1", + "score_direction_and_estimand": [ + "additional treatment-group change from baseline to post", + "additional treatment-group change from baseline to follow-up" + ], + "source_columns": [ + "study_id", + "participant_id", + "treatment_group", + "occasion", + "occasion_order", + "treatment", + "post", + "followup", + "treatment_post", + "treatment_followup", + "well_being_score" + ], + "study_id": "SWL-S08", + "synthetic_only": true, + "unique_key": [ + "participant_id", + "occasion" + ], + "unit_of_analysis": "student" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S09_MEASUREMENT_MAP.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S09_MEASUREMENT_MAP.json new file mode 100644 index 0000000..bb77935 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S09_MEASUREMENT_MAP.json @@ -0,0 +1,38 @@ +{ + "factor_levels": { + "clinically_improved": [ + "yes", + "no" + ], + "support_condition": [ + "comparison", + "intervention" + ] + }, + "measurement_boundary": "The categorical and rank-based analyses answer different questions about two registered outcomes; neither is a generic repair for a failed normality test.", + "missingness_rule": "No missingness rule beyond the governed design contract.", + "outcomes": { + "categorical": "clinically_improved", + "positive_skew_continuous": "recovery_burden_days" + }, + "predictors": [], + "real_data_authorized": false, + "schema_version": "psych-design-measurement-map-v0.1", + "score_direction_and_estimand": [ + "intervention minus comparison improvement-risk difference", + "distributional shift in recovery burden with intervention oriented as lower burden" + ], + "source_columns": [ + "study_id", + "participant_id", + "support_condition", + "clinically_improved", + "recovery_burden_days" + ], + "study_id": "SWL-S09", + "synthetic_only": true, + "unique_key": [ + "participant_id" + ], + "unit_of_analysis": "student" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S10_MEASUREMENT_MAP.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S10_MEASUREMENT_MAP.json new file mode 100644 index 0000000..55540e4 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/measurement/SWL-S10_MEASUREMENT_MAP.json @@ -0,0 +1,34 @@ +{ + "factor_levels": { + "replication_type": [ + "conceptual", + "direct" + ] + }, + "measurement_boundary": "Prospective power depends on a justified target effect and model; the fixed-effect synthesis assumes one common true effect and does not address publication bias or selective reporting.", + "missingness_rule": "No missingness rule beyond the governed design contract.", + "outcomes": [], + "predictors": [], + "real_data_authorized": false, + "schema_version": "psych-design-measurement-map-v0.1", + "score_direction_and_estimand": [ + "per-group sample size for d=0.40 at 80% power", + "fixed-effect pooled standardized mean difference across six replications" + ], + "source_columns": [ + "study_id", + "program_id", + "replication_type", + "registered_before_analysis", + "n_per_group", + "total_n", + "observed_cohens_d", + "sampling_variance_d" + ], + "study_id": "SWL-S10", + "synthetic_only": true, + "unique_key": [ + "study_id" + ], + "unit_of_analysis": "completed synthetic replication" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S01/APA_RESULT_SOURCE_MAP_CORRELATION.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S01/APA_RESULT_SOURCE_MAP_CORRELATION.json new file mode 100644 index 0000000..5199736 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S01/APA_RESULT_SOURCE_MAP_CORRELATION.json @@ -0,0 +1,38 @@ +{ + "batch_id": "psych-design-swl-s01-s06-s07-v0.1", + "chapter_ids": [ + "ch11", + "ch12" + ], + "contract_version": "0.1", + "reported_number_bindings": [ + { + "field": "n", + "json_pointer": "/reported_fields/n" + }, + { + "field": "correlation_r_sleep_confidence", + "json_pointer": "/reported_fields/correlation_r_sleep_confidence" + }, + { + "field": "correlation_df", + "json_pointer": "/reported_fields/correlation_df" + }, + { + "field": "correlation_p_two_sided", + "json_pointer": "/reported_fields/correlation_p_two_sided" + }, + { + "field": "correlation_ci_95_low", + "json_pointer": "/reported_fields/correlation_ci_95_low" + }, + { + "field": "correlation_ci_95_high", + "json_pointer": "/reported_fields/correlation_ci_95_high" + } + ], + "reporting_role": "correlation", + "result_receipt": "evidence/SWL-S01/PYTHON_RESULT_RECEIPT.json", + "study_id": "SWL-S01", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S01/APA_RESULT_SOURCE_MAP_REGRESSION.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S01/APA_RESULT_SOURCE_MAP_REGRESSION.json new file mode 100644 index 0000000..fece4e6 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S01/APA_RESULT_SOURCE_MAP_REGRESSION.json @@ -0,0 +1,70 @@ +{ + "batch_id": "psych-design-swl-s01-s06-s07-v0.1", + "chapter_ids": [ + "ch11", + "ch12" + ], + "contract_version": "0.1", + "reported_number_bindings": [ + { + "field": "n", + "json_pointer": "/reported_fields/n" + }, + { + "field": "regression_f", + "json_pointer": "/reported_fields/regression_f" + }, + { + "field": "regression_df_model", + "json_pointer": "/reported_fields/regression_df_model" + }, + { + "field": "regression_df_residual", + "json_pointer": "/reported_fields/regression_df_residual" + }, + { + "field": "regression_p", + "json_pointer": "/reported_fields/regression_p" + }, + { + "field": "regression_r_squared", + "json_pointer": "/reported_fields/regression_r_squared" + }, + { + "field": "study_hours_b", + "json_pointer": "/reported_fields/study_hours_b" + }, + { + "field": "study_hours_se", + "json_pointer": "/reported_fields/study_hours_se" + }, + { + "field": "study_hours_t", + "json_pointer": "/reported_fields/study_hours_t" + }, + { + "field": "study_hours_p", + "json_pointer": "/reported_fields/study_hours_p" + }, + { + "field": "confidence_b", + "json_pointer": "/reported_fields/confidence_b" + }, + { + "field": "confidence_se", + "json_pointer": "/reported_fields/confidence_se" + }, + { + "field": "confidence_t", + "json_pointer": "/reported_fields/confidence_t" + }, + { + "field": "confidence_p", + "json_pointer": "/reported_fields/confidence_p" + } + ], + "reporting_role": "regression", + "result_receipt": "evidence/SWL-S01/PYTHON_RESULT_RECEIPT.json", + "study_id": "SWL-S01", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S02/APA_RESULT_SOURCE_MAP.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S02/APA_RESULT_SOURCE_MAP.json new file mode 100644 index 0000000..5695b2e --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S02/APA_RESULT_SOURCE_MAP.json @@ -0,0 +1,53 @@ +{ + "batch_id": "psych-design-swl-s02-s05-v0.1", + "contract_version": "0.1", + "limitation_path": "evidence/SWL-S02/MATCHED_LIMITATION.json", + "policy": "Book prose must be written from these stable fields and rechecked after any dataset or analysis change.", + "prose_is_generated_from_stable_fields": false, + "receipt_path": "evidence/SWL-S02/PYTHON_RESULT_RECEIPT.json", + "receipt_sha256": "4dcd4f5889ab0c56574a34d0d5dc9e3e5bd7c03adb9bdbe7da21884b93fbf4ff", + "reported_number_bindings": [ + { + "json_pointer": "/reported_fields/mean_standard", + "reporting_role": "mean_standard" + }, + { + "json_pointer": "/reported_fields/sd_standard", + "reporting_role": "sd_standard" + }, + { + "json_pointer": "/reported_fields/mean_structured", + "reporting_role": "mean_structured" + }, + { + "json_pointer": "/reported_fields/sd_structured", + "reporting_role": "sd_structured" + }, + { + "json_pointer": "/reported_fields/welch_t", + "reporting_role": "welch_t" + }, + { + "json_pointer": "/reported_fields/welch_df", + "reporting_role": "welch_df" + }, + { + "json_pointer": "/reported_fields/p_value_two_sided", + "reporting_role": "p_value_two_sided" + }, + { + "json_pointer": "/reported_fields/ci_95_low", + "reporting_role": "ci_95_low" + }, + { + "json_pointer": "/reported_fields/ci_95_high", + "reporting_role": "ci_95_high" + }, + { + "json_pointer": "/reported_fields/cohen_d_pooled", + "reporting_role": "cohen_d_pooled" + } + ], + "source_mapping_complete": true, + "study_id": "SWL-S02" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S03/APA_RESULT_SOURCE_MAP.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S03/APA_RESULT_SOURCE_MAP.json new file mode 100644 index 0000000..d9095c5 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S03/APA_RESULT_SOURCE_MAP.json @@ -0,0 +1,57 @@ +{ + "batch_id": "psych-design-swl-s02-s05-v0.1", + "contract_version": "0.1", + "limitation_path": "evidence/SWL-S03/MATCHED_LIMITATION.json", + "policy": "Book prose must be written from these stable fields and rechecked after any dataset or analysis change.", + "prose_is_generated_from_stable_fields": false, + "receipt_path": "evidence/SWL-S03/PYTHON_RESULT_RECEIPT.json", + "receipt_sha256": "6a4b963dc3dbbf26460029cd364192e560c31135bf074aeb7f855f57dbe35b6e", + "reported_number_bindings": [ + { + "json_pointer": "/reported_fields/n_paired", + "reporting_role": "n_paired" + }, + { + "json_pointer": "/reported_fields/mean_pre", + "reporting_role": "mean_pre" + }, + { + "json_pointer": "/reported_fields/mean_post", + "reporting_role": "mean_post" + }, + { + "json_pointer": "/reported_fields/mean_change_post_minus_pre", + "reporting_role": "mean_change_post_minus_pre" + }, + { + "json_pointer": "/reported_fields/sd_change", + "reporting_role": "sd_change" + }, + { + "json_pointer": "/reported_fields/paired_t", + "reporting_role": "paired_t" + }, + { + "json_pointer": "/reported_fields/df", + "reporting_role": "df" + }, + { + "json_pointer": "/reported_fields/p_value_two_sided", + "reporting_role": "p_value_two_sided" + }, + { + "json_pointer": "/reported_fields/ci_95_low", + "reporting_role": "ci_95_low" + }, + { + "json_pointer": "/reported_fields/ci_95_high", + "reporting_role": "ci_95_high" + }, + { + "json_pointer": "/reported_fields/cohen_dz", + "reporting_role": "cohen_dz" + } + ], + "source_mapping_complete": true, + "study_id": "SWL-S03" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S04/APA_RESULT_SOURCE_MAP.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S04/APA_RESULT_SOURCE_MAP.json new file mode 100644 index 0000000..40b9a39 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S04/APA_RESULT_SOURCE_MAP.json @@ -0,0 +1,57 @@ +{ + "batch_id": "psych-design-swl-s02-s05-v0.1", + "contract_version": "0.1", + "limitation_path": "evidence/SWL-S04/MATCHED_LIMITATION.json", + "policy": "Book prose must be written from these stable fields and rechecked after any dataset or analysis change.", + "prose_is_generated_from_stable_fields": false, + "receipt_path": "evidence/SWL-S04/PYTHON_RESULT_RECEIPT.json", + "receipt_sha256": "4cad31f30359c41c6dc6431882b0bd0cec84100c3d86006a883fc071a6993666", + "reported_number_bindings": [ + { + "json_pointer": "/reported_fields/anova_f", + "reporting_role": "anova_f" + }, + { + "json_pointer": "/reported_fields/df_between", + "reporting_role": "df_between" + }, + { + "json_pointer": "/reported_fields/df_within", + "reporting_role": "df_within" + }, + { + "json_pointer": "/reported_fields/p_value", + "reporting_role": "p_value" + }, + { + "json_pointer": "/reported_fields/eta_squared", + "reporting_role": "eta_squared" + }, + { + "json_pointer": "/reported_fields/contrast_guided_average_minus_standard_estimate", + "reporting_role": "contrast_guided_average_minus_standard_estimate" + }, + { + "json_pointer": "/reported_fields/contrast_guided_average_minus_standard_t", + "reporting_role": "contrast_guided_average_minus_standard_t" + }, + { + "json_pointer": "/reported_fields/contrast_guided_average_minus_standard_p_holm", + "reporting_role": "contrast_guided_average_minus_standard_p_holm" + }, + { + "json_pointer": "/reported_fields/contrast_feedback_increment_estimate", + "reporting_role": "contrast_feedback_increment_estimate" + }, + { + "json_pointer": "/reported_fields/contrast_feedback_increment_t", + "reporting_role": "contrast_feedback_increment_t" + }, + { + "json_pointer": "/reported_fields/contrast_feedback_increment_p_holm", + "reporting_role": "contrast_feedback_increment_p_holm" + } + ], + "source_mapping_complete": true, + "study_id": "SWL-S04" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S05/APA_RESULT_SOURCE_MAP.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S05/APA_RESULT_SOURCE_MAP.json new file mode 100644 index 0000000..3a30809 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S05/APA_RESULT_SOURCE_MAP.json @@ -0,0 +1,61 @@ +{ + "batch_id": "psych-design-swl-s02-s05-v0.1", + "contract_version": "0.1", + "limitation_path": "evidence/SWL-S05/MATCHED_LIMITATION.json", + "policy": "Book prose must be written from these stable fields and rechecked after any dataset or analysis change.", + "prose_is_generated_from_stable_fields": false, + "receipt_path": "evidence/SWL-S05/PYTHON_RESULT_RECEIPT.json", + "receipt_sha256": "1451c6a584723efdfbc98d9d61120fdceb691dd161a3dd582e956d205ff48710", + "reported_number_bindings": [ + { + "json_pointer": "/reported_fields/interaction_f", + "reporting_role": "interaction_f" + }, + { + "json_pointer": "/reported_fields/df_effect", + "reporting_role": "df_effect" + }, + { + "json_pointer": "/reported_fields/df_within", + "reporting_role": "df_within" + }, + { + "json_pointer": "/reported_fields/interaction_p", + "reporting_role": "interaction_p" + }, + { + "json_pointer": "/reported_fields/interaction_partial_eta_squared", + "reporting_role": "interaction_partial_eta_squared" + }, + { + "json_pointer": "/reported_fields/interaction_difference_in_differences", + "reporting_role": "interaction_difference_in_differences" + }, + { + "json_pointer": "/reported_fields/simple_strategy_no_feedback_estimate", + "reporting_role": "simple_strategy_no_feedback_estimate" + }, + { + "json_pointer": "/reported_fields/simple_strategy_no_feedback_t", + "reporting_role": "simple_strategy_no_feedback_t" + }, + { + "json_pointer": "/reported_fields/simple_strategy_no_feedback_p", + "reporting_role": "simple_strategy_no_feedback_p" + }, + { + "json_pointer": "/reported_fields/simple_strategy_explanatory_feedback_estimate", + "reporting_role": "simple_strategy_explanatory_feedback_estimate" + }, + { + "json_pointer": "/reported_fields/simple_strategy_explanatory_feedback_t", + "reporting_role": "simple_strategy_explanatory_feedback_t" + }, + { + "json_pointer": "/reported_fields/simple_strategy_explanatory_feedback_p", + "reporting_role": "simple_strategy_explanatory_feedback_p" + } + ], + "source_mapping_complete": true, + "study_id": "SWL-S05" +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S06/APA_RESULT_SOURCE_MAP.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S06/APA_RESULT_SOURCE_MAP.json new file mode 100644 index 0000000..dc0f15f --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S06/APA_RESULT_SOURCE_MAP.json @@ -0,0 +1,61 @@ +{ + "batch_id": "psych-design-swl-s01-s06-s07-v0.1", + "chapter_ids": [ + "ch10" + ], + "contract_version": "0.1", + "reported_number_bindings": [ + { + "field": "n_complete_case", + "json_pointer": "/reported_fields/n_complete_case" + }, + { + "field": "mean_baseline_complete_case", + "json_pointer": "/reported_fields/mean_baseline_complete_case" + }, + { + "field": "mean_post_complete_case", + "json_pointer": "/reported_fields/mean_post_complete_case" + }, + { + "field": "mean_follow_up_complete_case", + "json_pointer": "/reported_fields/mean_follow_up_complete_case" + }, + { + "field": "rm_anova_f", + "json_pointer": "/reported_fields/rm_anova_f" + }, + { + "field": "rm_anova_df_occasion", + "json_pointer": "/reported_fields/rm_anova_df_occasion" + }, + { + "field": "rm_anova_df_error", + "json_pointer": "/reported_fields/rm_anova_df_error" + }, + { + "field": "greenhouse_geisser_epsilon", + "json_pointer": "/reported_fields/greenhouse_geisser_epsilon" + }, + { + "field": "greenhouse_geisser_df_occasion", + "json_pointer": "/reported_fields/greenhouse_geisser_df_occasion" + }, + { + "field": "greenhouse_geisser_df_error", + "json_pointer": "/reported_fields/greenhouse_geisser_df_error" + }, + { + "field": "greenhouse_geisser_p", + "json_pointer": "/reported_fields/greenhouse_geisser_p" + }, + { + "field": "partial_eta_squared", + "json_pointer": "/reported_fields/partial_eta_squared" + } + ], + "reporting_role": "primary", + "result_receipt": "evidence/SWL-S06/PYTHON_RESULT_RECEIPT.json", + "study_id": "SWL-S06", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S07/APA_RESULT_SOURCE_MAP.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S07/APA_RESULT_SOURCE_MAP.json new file mode 100644 index 0000000..e468d33 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S07/APA_RESULT_SOURCE_MAP.json @@ -0,0 +1,53 @@ +{ + "batch_id": "psych-design-swl-s01-s06-s07-v0.1", + "chapter_ids": [ + "ch13" + ], + "contract_version": "0.1", + "reported_number_bindings": [ + { + "field": "n_total", + "json_pointer": "/reported_fields/n_total" + }, + { + "field": "adjusted_mean_comparison_at_overall_baseline", + "json_pointer": "/reported_fields/adjusted_mean_comparison_at_overall_baseline" + }, + { + "field": "adjusted_mean_intervention_at_overall_baseline", + "json_pointer": "/reported_fields/adjusted_mean_intervention_at_overall_baseline" + }, + { + "field": "adjusted_group_difference_intervention_minus_comparison", + "json_pointer": "/reported_fields/adjusted_group_difference_intervention_minus_comparison" + }, + { + "field": "group_effect_f", + "json_pointer": "/reported_fields/group_effect_f" + }, + { + "field": "group_effect_df", + "json_pointer": "/reported_fields/group_effect_df" + }, + { + "field": "model_df_error", + "json_pointer": "/reported_fields/model_df_error" + }, + { + "field": "group_effect_p", + "json_pointer": "/reported_fields/group_effect_p" + }, + { + "field": "group_partial_eta_squared", + "json_pointer": "/reported_fields/group_partial_eta_squared" + }, + { + "field": "homogeneity_interaction_p", + "json_pointer": "/reported_fields/homogeneity_interaction_p" + } + ], + "reporting_role": "primary", + "result_receipt": "evidence/SWL-S07/PYTHON_RESULT_RECEIPT.json", + "study_id": "SWL-S07", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S08/APA_RESULT_SOURCE_MAP.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S08/APA_RESULT_SOURCE_MAP.json new file mode 100644 index 0000000..2570574 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S08/APA_RESULT_SOURCE_MAP.json @@ -0,0 +1,58 @@ +{ + "analysis_role": "longitudinal_random_intercept_model", + "contract_version": "0.1", + "rendered_text": "A maximum-likelihood random-intercept model indicated larger treatment-group change from baseline to post, b = 5.86, SE = 0.87, z = 6.74, p < .001, 95% CI [4.16, 7.57], and from baseline to follow-up, b = 7.78, SE = 0.89, z = 8.70, p < .001, 95% CI [6.03, 9.54].", + "reported_number_bindings": [ + { + "field": "treatment_post_b", + "json_pointer": "/reported_fields/treatment_post_b" + }, + { + "field": "treatment_post_se", + "json_pointer": "/reported_fields/treatment_post_se" + }, + { + "field": "treatment_post_z", + "json_pointer": "/reported_fields/treatment_post_z" + }, + { + "field": "treatment_post_p", + "json_pointer": "/reported_fields/treatment_post_p" + }, + { + "field": "treatment_post_ci_95_low", + "json_pointer": "/reported_fields/treatment_post_ci_95_low" + }, + { + "field": "treatment_post_ci_95_high", + "json_pointer": "/reported_fields/treatment_post_ci_95_high" + }, + { + "field": "treatment_followup_b", + "json_pointer": "/reported_fields/treatment_followup_b" + }, + { + "field": "treatment_followup_se", + "json_pointer": "/reported_fields/treatment_followup_se" + }, + { + "field": "treatment_followup_z", + "json_pointer": "/reported_fields/treatment_followup_z" + }, + { + "field": "treatment_followup_p", + "json_pointer": "/reported_fields/treatment_followup_p" + }, + { + "field": "treatment_followup_ci_95_low", + "json_pointer": "/reported_fields/treatment_followup_ci_95_low" + }, + { + "field": "treatment_followup_ci_95_high", + "json_pointer": "/reported_fields/treatment_followup_ci_95_high" + } + ], + "result_source": "evidence/SWL-S08/PYTHON_RESULT_RECEIPT.json", + "study_id": "SWL-S08", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S09/APA_RESULT_SOURCE_MAP_CATEGORICAL.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S09/APA_RESULT_SOURCE_MAP_CATEGORICAL.json new file mode 100644 index 0000000..bcae13d --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S09/APA_RESULT_SOURCE_MAP_CATEGORICAL.json @@ -0,0 +1,34 @@ +{ + "analysis_role": "categorical_improvement_status", + "contract_version": "0.1", + "rendered_text": "Clinical-improvement status differed by support condition, chi-square(1) = 10.23, p = .001, Cramer's V = .253; the improved proportions were .45 in comparison and .70 in intervention.", + "reported_number_bindings": [ + { + "field": "chi_square", + "json_pointer": "/reported_fields/chi_square" + }, + { + "field": "chi_square_df", + "json_pointer": "/reported_fields/chi_square_df" + }, + { + "field": "chi_square_p", + "json_pointer": "/reported_fields/chi_square_p" + }, + { + "field": "cramers_v", + "json_pointer": "/reported_fields/cramers_v" + }, + { + "field": "comparison_improved_proportion", + "json_pointer": "/reported_fields/comparison_improved_proportion" + }, + { + "field": "intervention_improved_proportion", + "json_pointer": "/reported_fields/intervention_improved_proportion" + } + ], + "result_source": "evidence/SWL-S09/PYTHON_RESULT_RECEIPT.json", + "study_id": "SWL-S09", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S09/APA_RESULT_SOURCE_MAP_RANK.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S09/APA_RESULT_SOURCE_MAP_RANK.json new file mode 100644 index 0000000..878a6bc --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S09/APA_RESULT_SOURCE_MAP_RANK.json @@ -0,0 +1,34 @@ +{ + "analysis_role": "positive_skew_recovery_burden", + "contract_version": "0.1", + "rendered_text": "Recovery burden was lower in intervention (Mdn = 6.75) than comparison (Mdn = 10.53), U = 1966.00, z = -4.21, p < .001, rank-biserial effect = .386.", + "reported_number_bindings": [ + { + "field": "comparison_burden_median", + "json_pointer": "/reported_fields/comparison_burden_median" + }, + { + "field": "intervention_burden_median", + "json_pointer": "/reported_fields/intervention_burden_median" + }, + { + "field": "mann_whitney_u_intervention", + "json_pointer": "/reported_fields/mann_whitney_u_intervention" + }, + { + "field": "mann_whitney_z_no_continuity", + "json_pointer": "/reported_fields/mann_whitney_z_no_continuity" + }, + { + "field": "mann_whitney_p_two_sided", + "json_pointer": "/reported_fields/mann_whitney_p_two_sided" + }, + { + "field": "rank_biserial_intervention_lower", + "json_pointer": "/reported_fields/rank_biserial_intervention_lower" + } + ], + "result_source": "evidence/SWL-S09/PYTHON_RESULT_RECEIPT.json", + "study_id": "SWL-S09", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S10/APA_RESULT_SOURCE_MAP_META.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S10/APA_RESULT_SOURCE_MAP_META.json new file mode 100644 index 0000000..42ddc38 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S10/APA_RESULT_SOURCE_MAP_META.json @@ -0,0 +1,54 @@ +{ + "analysis_role": "fixed_effect_cumulative_evidence", + "contract_version": "0.1", + "rendered_text": "Across six synthetic replications, the fixed-effect pooled estimate was d = 0.40, SE = 0.07, z = 6.05, p < .001, 95% CI [0.27, 0.53], with Q(5) = 1.60, p = .901, I-squared = 0%.", + "reported_number_bindings": [ + { + "field": "study_count", + "json_pointer": "/reported_fields/study_count" + }, + { + "field": "fixed_effect_pooled_d", + "json_pointer": "/reported_fields/fixed_effect_pooled_d" + }, + { + "field": "fixed_effect_se", + "json_pointer": "/reported_fields/fixed_effect_se" + }, + { + "field": "fixed_effect_z", + "json_pointer": "/reported_fields/fixed_effect_z" + }, + { + "field": "fixed_effect_p", + "json_pointer": "/reported_fields/fixed_effect_p" + }, + { + "field": "fixed_effect_ci_95_low", + "json_pointer": "/reported_fields/fixed_effect_ci_95_low" + }, + { + "field": "fixed_effect_ci_95_high", + "json_pointer": "/reported_fields/fixed_effect_ci_95_high" + }, + { + "field": "cochran_q", + "json_pointer": "/reported_fields/cochran_q" + }, + { + "field": "cochran_q_df", + "json_pointer": "/reported_fields/cochran_q_df" + }, + { + "field": "cochran_q_p", + "json_pointer": "/reported_fields/cochran_q_p" + }, + { + "field": "i_squared_percent", + "json_pointer": "/reported_fields/i_squared_percent" + } + ], + "result_source": "evidence/SWL-S10/PYTHON_RESULT_RECEIPT.json", + "study_id": "SWL-S10", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S10/APA_RESULT_SOURCE_MAP_POWER.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S10/APA_RESULT_SOURCE_MAP_POWER.json new file mode 100644 index 0000000..4e46d89 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/apa_source_maps/SWL-S10/APA_RESULT_SOURCE_MAP_POWER.json @@ -0,0 +1,26 @@ +{ + "analysis_role": "prospective_power_planning", + "contract_version": "0.1", + "rendered_text": "Under the declared normal-approximation assumptions, detecting d = 0.40 with 80% power and two-sided alpha = .05 required 99 participants per group after rounding up.", + "reported_number_bindings": [ + { + "field": "target_effect_size_d", + "json_pointer": "/reported_fields/target_effect_size_d" + }, + { + "field": "planning_power_primary", + "json_pointer": "/reported_fields/planning_power_primary" + }, + { + "field": "planning_alpha_two_sided", + "json_pointer": "/reported_fields/planning_alpha_two_sided" + }, + { + "field": "planning_n_per_group_ceiling_80", + "json_pointer": "/reported_fields/planning_n_per_group_ceiling_80" + } + ], + "result_source": "evidence/SWL-S10/PYTHON_RESULT_RECEIPT.json", + "study_id": "SWL-S10", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S01/MATCHED_LIMITATION_CORRELATION.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S01/MATCHED_LIMITATION_CORRELATION.json new file mode 100644 index 0000000..ce29c16 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S01/MATCHED_LIMITATION_CORRELATION.json @@ -0,0 +1,12 @@ +{ + "batch_id": "psych-design-swl-s01-s06-s07-v0.1", + "chapter_ids": [ + "ch11", + "ch12" + ], + "contract_version": "0.1", + "limitation": "The cross-sectional synthetic association does not establish temporal order, rule out common causes, or support the claim that changing sleep would change confidence.", + "limitation_role": "correlation", + "study_id": "SWL-S01", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S01/MATCHED_LIMITATION_REGRESSION.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S01/MATCHED_LIMITATION_REGRESSION.json new file mode 100644 index 0000000..ad3e21f --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S01/MATCHED_LIMITATION_REGRESSION.json @@ -0,0 +1,12 @@ +{ + "batch_id": "psych-design-swl-s01-s06-s07-v0.1", + "chapter_ids": [ + "ch11", + "ch12" + ], + "contract_version": "0.1", + "limitation": "Predictive performance is registered only for the generated records and observed predictor ranges; the model does not authorize extrapolation, causal interpretation, or performance claims for real students.", + "limitation_role": "regression", + "study_id": "SWL-S01", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S02/MATCHED_LIMITATION.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S02/MATCHED_LIMITATION.json new file mode 100644 index 0000000..96688e0 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S02/MATCHED_LIMITATION.json @@ -0,0 +1,10 @@ +{ + "batch_id": "psych-design-swl-s02-s05-v0.1", + "causal_scope": "Causal interpretation is limited to the declared assignment, implementation, and synthetic pilot conditions.", + "contract_version": "0.1", + "design": "Two-group pilot with declared stratified random assignment.", + "matched_limitation": "The small synthetic pilot represents one implementation of the routine; it does not establish transportability to other students, settings, or delivery conditions.", + "must_accompany_apa_result": true, + "study_id": "SWL-S02", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S03/MATCHED_LIMITATION.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S03/MATCHED_LIMITATION.json new file mode 100644 index 0000000..562d2cf --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S03/MATCHED_LIMITATION.json @@ -0,0 +1,10 @@ +{ + "batch_id": "psych-design-swl-s02-s05-v0.1", + "causal_scope": "The analysis estimates within-person change, not the workshop's causal effect in the absence of a control condition.", + "contract_version": "0.1", + "design": "Within-person pre/post study.", + "matched_limitation": "Pre/post change may reflect history, testing, maturation, or other time-varying influences because the design has no control condition.", + "must_accompany_apa_result": true, + "study_id": "SWL-S03", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S04/MATCHED_LIMITATION.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S04/MATCHED_LIMITATION.json new file mode 100644 index 0000000..52f9f9c --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S04/MATCHED_LIMITATION.json @@ -0,0 +1,10 @@ +{ + "batch_id": "psych-design-swl-s02-s05-v0.1", + "causal_scope": "Causal interpretation is conditional on the declared random assignment and implementation fidelity.", + "contract_version": "0.1", + "design": "Three-condition between-student randomized study.", + "matched_limitation": "The two planned contrasts answer only the registered support questions; they do not justify unregistered pairwise fishing or generalization beyond the synthetic implementation.", + "must_accompany_apa_result": true, + "study_id": "SWL-S04", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S05/MATCHED_LIMITATION.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S05/MATCHED_LIMITATION.json new file mode 100644 index 0000000..607085a --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S05/MATCHED_LIMITATION.json @@ -0,0 +1,10 @@ +{ + "batch_id": "psych-design-swl-s02-s05-v0.1", + "causal_scope": "Causal interpretation is conditional on random assignment, implementation fidelity, and the defined factor levels.", + "contract_version": "0.1", + "design": "Balanced two-factor between-student factorial experiment.", + "matched_limitation": "The interaction is specific to the registered strategy and feedback levels; main effects alone would obscure that conditional pattern.", + "must_accompany_apa_result": true, + "study_id": "SWL-S05", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S06/MATCHED_LIMITATION.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S06/MATCHED_LIMITATION.json new file mode 100644 index 0000000..b8b5efc --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S06/MATCHED_LIMITATION.json @@ -0,0 +1,11 @@ +{ + "batch_id": "psych-design-swl-s01-s06-s07-v0.1", + "chapter_ids": [ + "ch10" + ], + "contract_version": "0.1", + "limitation": "The complete-case repeated-measures analysis excludes six participants with a missing scheduled outcome and can be biased when missingness is informative; a mixed model is required for the later incomplete-longitudinal workflow.", + "limitation_role": "primary", + "study_id": "SWL-S06", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S07/MATCHED_LIMITATION.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S07/MATCHED_LIMITATION.json new file mode 100644 index 0000000..6f2409d --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S07/MATCHED_LIMITATION.json @@ -0,0 +1,11 @@ +{ + "batch_id": "psych-design-swl-s01-s06-s07-v0.1", + "chapter_ids": [ + "ch13" + ], + "contract_version": "0.1", + "limitation": "The adjusted comparison depends on linearity, reliable pre-treatment measurement, and sufficiently homogeneous covariate slopes; baseline adjustment does not repair unmeasured confounding or support generalization beyond the synthetic implementation.", + "limitation_role": "primary", + "study_id": "SWL-S07", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S08/MATCHED_LIMITATION.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S08/MATCHED_LIMITATION.json new file mode 100644 index 0000000..5aa0a61 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S08/MATCHED_LIMITATION.json @@ -0,0 +1,7 @@ +{ + "analysis_role": "longitudinal_random_intercept_model", + "contract_version": "0.1", + "limitation": "The synthetic result validates the workflow rather than a real-world treatment claim. Interpretation depends on the declared coding, random-intercept specification, assignment, measurement stability, and an ignorable-missingness assumption.", + "study_id": "SWL-S08", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S09/MATCHED_LIMITATION_CATEGORICAL.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S09/MATCHED_LIMITATION_CATEGORICAL.json new file mode 100644 index 0000000..6ba0752 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S09/MATCHED_LIMITATION_CATEGORICAL.json @@ -0,0 +1,7 @@ +{ + "analysis_role": "categorical_improvement_status", + "contract_version": "0.1", + "limitation": "The chi-square result concerns the registered binary outcome and does not recover information discarded when a continuous or ordinal construct is dichotomized.", + "study_id": "SWL-S09", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S09/MATCHED_LIMITATION_RANK.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S09/MATCHED_LIMITATION_RANK.json new file mode 100644 index 0000000..2137644 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S09/MATCHED_LIMITATION_RANK.json @@ -0,0 +1,7 @@ +{ + "analysis_role": "positive_skew_recovery_burden", + "contract_version": "0.1", + "limitation": "The rank-based test detects a distributional shift under the independent-groups design; it is not automatically a test of means or medians unless stronger shape assumptions are defended.", + "study_id": "SWL-S09", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S10/MATCHED_LIMITATION_META.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S10/MATCHED_LIMITATION_META.json new file mode 100644 index 0000000..712a2e3 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S10/MATCHED_LIMITATION_META.json @@ -0,0 +1,7 @@ +{ + "analysis_role": "fixed_effect_cumulative_evidence", + "contract_version": "0.1", + "limitation": "The fixed-effect synthesis assumes one common true effect and the six synthetic replications are complete by construction; publication bias, selective reporting, and meaningful heterogeneity are not ruled out.", + "study_id": "SWL-S10", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S10/MATCHED_LIMITATION_POWER.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S10/MATCHED_LIMITATION_POWER.json new file mode 100644 index 0000000..57b595a --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/matched_limitations/SWL-S10/MATCHED_LIMITATION_POWER.json @@ -0,0 +1,7 @@ +{ + "analysis_role": "prospective_power_planning", + "contract_version": "0.1", + "limitation": "The sample-size calculation is conditional on the chosen target effect, two-sided alpha, equal allocation, independent observations, and the normal approximation; power cannot repair invalid design or measurement.", + "study_id": "SWL-S10", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S01_PYTHON_RESULT_RECEIPT.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S01_PYTHON_RESULT_RECEIPT.json new file mode 100644 index 0000000..3cba187 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S01_PYTHON_RESULT_RECEIPT.json @@ -0,0 +1,53 @@ +{ + "analysis_version": "0.1", + "batch_id": "psych-design-swl-s01-s06-s07-v0.1", + "contract_version": "0.1", + "dataset_sha256": "46c4334a443924d27ddcdbf0c5c3f5cf06eeb27323e1aebb0ebc021e4fbbd44d", + "design_contract_sha256": "0a8888bf6693e3652bf5f076d22004bbfc4d3a3ec85b4c7bce73e3f645291955", + "python_analysis_sha256": "e93638002ac9c0d1fdb8b92a20c930be162e38c6fa6379431ca31470b3a549d2", + "reported_fields": { + "academic_confidence_max": 64.21, + "academic_confidence_min": 52.95, + "confidence_b": 0.7085751186, + "confidence_ci_95_high": 0.901166929, + "confidence_ci_95_low": 0.5159833082, + "confidence_p": 4.036651011e-11, + "confidence_se": 0.09724660754, + "confidence_t": 7.286373648, + "cooks_distance_above_4_over_n": 0, + "correlation_ci_95_high": 0.8196737896, + "correlation_ci_95_low": 0.6603449449, + "correlation_df": 118, + "correlation_p_two_sided": 5.50013758e-23, + "correlation_r_sleep_confidence": 0.7507291472, + "correlation_t": 12.34465766, + "intercept_b": 11.94837374, + "intercept_ci_95_high": 23.52600109, + "intercept_ci_95_low": 0.3707463941, + "intercept_p": 0.04321189514, + "intercept_se": 5.845965002, + "intercept_t": 2.043866794, + "max_abs_standardized_residual": 1.944940883, + "max_cooks_distance": 0.03179090813, + "n": 120, + "regression_adjusted_r_squared": 0.6465941067, + "regression_df_model": 2, + "regression_df_residual": 117, + "regression_f": 109.8616519, + "regression_p": 1.391208533e-27, + "regression_r_squared": 0.6525337016, + "regression_rmse": 3.079234956, + "sleep_quality_max": 60.16, + "sleep_quality_min": 37.78, + "study_hours_b": 2.053588004, + "study_hours_ci_95_high": 2.363537858, + "study_hours_ci_95_low": 1.74363815, + "study_hours_max": 10.09, + "study_hours_min": 3.91, + "study_hours_p": 9.728012917e-25, + "study_hours_se": 0.1565049507, + "study_hours_t": 13.12155299 + }, + "study_id": "SWL-S01", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S02_PYTHON_RESULT_RECEIPT.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S02_PYTHON_RESULT_RECEIPT.json new file mode 100644 index 0000000..ee5eac2 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S02_PYTHON_RESULT_RECEIPT.json @@ -0,0 +1,29 @@ +{ + "analysis_source": "scripts/python/studies.py", + "analysis_source_sha256": "3264f890504a2ae305fcb271f556332c7d60835778fcb27a3206a75bdeb4df0e", + "analysis_version": "0.1", + "batch_id": "psych-design-swl-s02-s05-v0.1", + "contract_version": "0.1", + "data_file": "data/swl_s02_structured_study_routine.csv", + "data_sha256": "42b977f231609300049524d19e64ed6223f50d0cdb1881b857f34b49884dd88d", + "design_contract": "contracts/SWL-S02_DESIGN_CONTRACT.json", + "design_contract_sha256": "4c90d374c7318bf0a45e0afa465f4d924362286fb40092bfb5af569541d46619", + "engine": "numpy/scipy transparent analysis", + "reported_fields": { + "ci_95_high": 7.615616474, + "ci_95_low": 4.384383526, + "cohen_d_pooled": 1.855921454, + "mean_difference_structured_minus_standard": 6.0, + "mean_standard": 68.0, + "mean_structured": 74.0, + "n_standard": 32, + "n_structured": 32, + "p_value_two_sided": 3.962227572e-10, + "sd_standard": 3.232895436, + "sd_structured": 3.232895436, + "welch_df": 62.0, + "welch_t": 7.423685817 + }, + "study_id": "SWL-S02", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S03_PYTHON_RESULT_RECEIPT.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S03_PYTHON_RESULT_RECEIPT.json new file mode 100644 index 0000000..d3194e6 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S03_PYTHON_RESULT_RECEIPT.json @@ -0,0 +1,27 @@ +{ + "analysis_source": "scripts/python/studies.py", + "analysis_source_sha256": "3264f890504a2ae305fcb271f556332c7d60835778fcb27a3206a75bdeb4df0e", + "analysis_version": "0.1", + "batch_id": "psych-design-swl-s02-s05-v0.1", + "contract_version": "0.1", + "data_file": "data/swl_s03_skills_workshop_pre_post.csv", + "data_sha256": "5c1d114eac729358602bd621a5b5c74f3cd073ceeeb794407eb21b88dea08e28", + "design_contract": "contracts/SWL-S03_DESIGN_CONTRACT.json", + "design_contract_sha256": "f69dba723c6a2147029c99810307a1953976e6b001b77ea5897217741dc55ded", + "engine": "numpy/scipy transparent analysis", + "reported_fields": { + "ci_95_high": 5.06979397, + "ci_95_low": 3.263539363, + "cohen_dz": 1.339649325, + "df": 47, + "mean_change_post_minus_pre": 4.166666667, + "mean_post": 58.58333333, + "mean_pre": 54.41666667, + "n_paired": 48, + "p_value_two_sided": 3.358781243e-12, + "paired_t": 9.281362778, + "sd_change": 3.110266687 + }, + "study_id": "SWL-S03", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S04_PYTHON_RESULT_RECEIPT.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S04_PYTHON_RESULT_RECEIPT.json new file mode 100644 index 0000000..504f5a2 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S04_PYTHON_RESULT_RECEIPT.json @@ -0,0 +1,36 @@ +{ + "analysis_source": "scripts/python/studies.py", + "analysis_source_sha256": "3264f890504a2ae305fcb271f556332c7d60835778fcb27a3206a75bdeb4df0e", + "analysis_version": "0.1", + "batch_id": "psych-design-swl-s02-s05-v0.1", + "contract_version": "0.1", + "data_file": "data/swl_s04_three_condition_support.csv", + "data_sha256": "1584c1f5ebcd4890a0ed5aa35bfbf7a2e685a0f74492a4b4557fd2d49d3a87c8", + "design_contract": "contracts/SWL-S04_DESIGN_CONTRACT.json", + "design_contract_sha256": "03836ba796954dde6b1a37e5df60180d8e734938404f258f074b818373db9a27", + "engine": "numpy/scipy transparent analysis", + "reported_fields": { + "anova_f": 65.02135348, + "contrast_feedback_increment_estimate": 6.0, + "contrast_feedback_increment_p": 1.746753975e-08, + "contrast_feedback_increment_p_holm": 1.746753975e-08, + "contrast_feedback_increment_t": 6.211610087, + "contrast_guided_average_minus_standard_estimate": 8.0, + "contrast_guided_average_minus_standard_p": 3.153323301e-15, + "contrast_guided_average_minus_standard_p_holm": 6.306646602e-15, + "contrast_guided_average_minus_standard_t": 9.563399348, + "df_between": 2, + "df_within": 87, + "eta_squared": 0.5991572294, + "mean_guided_practice": 71.93333333, + "mean_guided_practice_plus_feedback": 77.93333333, + "mean_standard_support": 66.93333333, + "n_guided_practice": 30, + "n_guided_practice_plus_feedback": 30, + "n_standard_support": 30, + "n_total": 90, + "p_value": 5.362550627e-18 + }, + "study_id": "SWL-S04", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S05_PYTHON_RESULT_RECEIPT.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S05_PYTHON_RESULT_RECEIPT.json new file mode 100644 index 0000000..f628f90 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S05_PYTHON_RESULT_RECEIPT.json @@ -0,0 +1,40 @@ +{ + "analysis_source": "scripts/python/studies.py", + "analysis_source_sha256": "3264f890504a2ae305fcb271f556332c7d60835778fcb27a3206a75bdeb4df0e", + "analysis_version": "0.1", + "batch_id": "psych-design-swl-s02-s05-v0.1", + "contract_version": "0.1", + "data_file": "data/swl_s05_strategy_feedback.csv", + "data_sha256": "9100b086a0b569bf2f9258c546e9b56b33e64c028b2e1e3b2888305b3c428445", + "design_contract": "contracts/SWL-S05_DESIGN_CONTRACT.json", + "design_contract_sha256": "ad9f6347ac8167c7e9290eb7e806849009f0afaf8b982c2c5c655f206e4e40d8", + "engine": "numpy/scipy transparent analysis", + "reported_fields": { + "df_effect": 1, + "df_within": 92, + "feedback_f": 57.1132954, + "feedback_p": 2.968853127e-11, + "feedback_partial_eta_squared": 0.3830194702, + "interaction_difference_in_differences": 6.0, + "interaction_f": 20.56078634, + "interaction_p": 1.736937155e-05, + "interaction_partial_eta_squared": 0.1826638478, + "mean_rereading_explanatory_feedback": 69.375, + "mean_rereading_no_feedback": 67.375, + "mean_retrieval_explanatory_feedback": 79.375, + "mean_retrieval_no_feedback": 71.375, + "n_per_cell": 24, + "n_total": 96, + "simple_strategy_explanatory_feedback_estimate": 10.0, + "simple_strategy_explanatory_feedback_p": 8.277399851e-18, + "simple_strategy_explanatory_feedback_t": 10.68768407, + "simple_strategy_no_feedback_estimate": 4.0, + "simple_strategy_no_feedback_p": 4.660540101e-05, + "simple_strategy_no_feedback_t": 4.275073628, + "strategy_f": 111.942059, + "strategy_p": 1.387812305e-17, + "strategy_partial_eta_squared": 0.5488914819 + }, + "study_id": "SWL-S05", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S06_PYTHON_RESULT_RECEIPT.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S06_PYTHON_RESULT_RECEIPT.json new file mode 100644 index 0000000..1c20dfc --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S06_PYTHON_RESULT_RECEIPT.json @@ -0,0 +1,35 @@ +{ + "analysis_version": "0.1", + "batch_id": "psych-design-swl-s01-s06-s07-v0.1", + "contract_version": "0.1", + "dataset_sha256": "a95ef37c89f3523e59822d6729746d2499ea95acc508ca25824d3e073ed258a2", + "design_contract_sha256": "309143adb85b2c06517d38f031234c18370f796393983b5a89fd329b8da00061", + "python_analysis_sha256": "e93638002ac9c0d1fdb8b92a20c930be162e38c6fa6379431ca31470b3a549d2", + "reported_fields": { + "greenhouse_geisser_df_error": 69.98984053, + "greenhouse_geisser_df_occasion": 1.320563029, + "greenhouse_geisser_epsilon": 0.6602815145, + "greenhouse_geisser_p": 5.918791808e-29, + "mean_baseline_complete_case": 49.50462963, + "mean_change_follow_up_minus_baseline": 5.981481481, + "mean_change_post_minus_baseline": 4.11037037, + "mean_follow_up_complete_case": 55.48611111, + "mean_post_complete_case": 53.615, + "missing_baseline": 0, + "missing_follow_up": 3, + "missing_outcomes": 6, + "missing_post": 3, + "n_complete_case": 54, + "n_incomplete_participants": 6, + "n_scheduled_participants": 60, + "observed_outcomes": 174, + "partial_eta_squared": 0.8372410924, + "rm_anova_df_error": 106, + "rm_anova_df_occasion": 2, + "rm_anova_f": 272.6350191, + "rm_anova_p": 1.628818229e-42, + "scheduled_rows": 180 + }, + "study_id": "SWL-S06", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S07_PYTHON_RESULT_RECEIPT.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S07_PYTHON_RESULT_RECEIPT.json new file mode 100644 index 0000000..36a5107 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S07_PYTHON_RESULT_RECEIPT.json @@ -0,0 +1,44 @@ +{ + "analysis_version": "0.1", + "batch_id": "psych-design-swl-s01-s06-s07-v0.1", + "contract_version": "0.1", + "dataset_sha256": "08925df34996fd9f75a4c5bd41f0de82c655b05212f2cd21435ba70349db52fc", + "design_contract_sha256": "b89086a679277b1652999fde987ccb89ce78ec6f44b3186a3f0c9cb694f39048", + "python_analysis_sha256": "e93638002ac9c0d1fdb8b92a20c930be162e38c6fa6379431ca31470b3a549d2", + "reported_fields": { + "adjusted_group_difference_intervention_minus_comparison": 4.195586604, + "adjusted_mean_comparison_at_overall_baseline": 60.70454003, + "adjusted_mean_intervention_at_overall_baseline": 64.90012664, + "baseline_overall_mean": 50.44633333, + "baseline_slope": 1.158451049, + "baseline_slope_ci_95_high": 1.269132905, + "baseline_slope_ci_95_low": 1.047769194, + "baseline_slope_p": 1.587746358e-35, + "baseline_slope_se": 0.05568595007, + "baseline_slope_t": 20.80329146, + "group_effect_ci_95_high": 5.23809565, + "group_effect_ci_95_low": 3.153077558, + "group_effect_df": 1, + "group_effect_f": 63.98634614, + "group_effect_p": 4.968806886e-12, + "group_effect_se": 0.5245042778, + "group_effect_t": 7.999146588, + "group_partial_eta_squared": 0.4237889569, + "homogeneity_interaction_b": -0.1136802344, + "homogeneity_interaction_p": 0.3101586282, + "homogeneity_interaction_se": 0.1113514523, + "homogeneity_interaction_t": -1.020913801, + "model_adjusted_r_squared": 0.8824557827, + "model_df_error": 87, + "model_r_squared": 0.8850972258, + "n_comparison": 45, + "n_intervention": 45, + "n_total": 90, + "raw_baseline_mean_comparison": 48.95, + "raw_baseline_mean_intervention": 51.94266667, + "raw_post_mean_comparison": 58.97111111, + "raw_post_mean_intervention": 66.63355556 + }, + "study_id": "SWL-S07", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S08_PYTHON_RESULT_RECEIPT.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S08_PYTHON_RESULT_RECEIPT.json new file mode 100644 index 0000000..d4a4a70 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S08_PYTHON_RESULT_RECEIPT.json @@ -0,0 +1,68 @@ +{ + "analysis_version": "0.1", + "batch_id": "psych-design-swl-s08-s10-v0.1", + "contract_version": "0.1", + "dataset_sha256": "a37e797a5d67e38d91d7f88d06014bd351559efb22b21edb3cc75ff95db82556", + "design_contract_sha256": "f1d3faeb7c4e10e9836e9ac75f1bb18aa8adbe12f916ce70fe95ed73f9c38c42", + "python_analysis_sha256": "f882af98887efec3a7756f41b1f2b54ca63df089dd449220cab68feedbd6b363", + "reported_fields": { + "aic": 901.3262459, + "bic": 926.3179577, + "complete_participants": 48, + "control_baseline_mean": 50, + "control_followup_mean": 51.50000012, + "control_post_mean": 51.00000004, + "converged": 1, + "followup_b": 1.474701992, + "followup_ci_95_high": 2.732255571, + "followup_ci_95_low": 0.2171484134, + "followup_p": 0.02153895904, + "followup_se": 0.6416207588, + "followup_z": 2.29840131, + "incomplete_participants": 12, + "intercept_b": 50, + "intercept_ci_95_high": 51.56533349, + "intercept_ci_95_low": 48.43466651, + "intercept_p": 0, + "intercept_se": 0.7986542115, + "intercept_z": 62.60531689, + "log_likelihood": -442.663123, + "missing_visits": 12, + "n_control": 30, + "n_participants": 60, + "n_treatment": 30, + "observed_rows": 168, + "post_b": 1.102577378, + "post_ci_95_high": 2.308809272, + "post_ci_95_low": -0.1036545168, + "post_p": 0.07320677973, + "post_se": 0.6154357448, + "post_z": 1.791539388, + "random_intercept_variance": 13.72877362, + "residual_variance": 5.40668287, + "scheduled_rows": 180, + "treatment_b": 0.4, + "treatment_baseline_mean": 50.4, + "treatment_ci_95_high": 2.613715852, + "treatment_ci_95_low": -1.813715852, + "treatment_followup_b": 7.78490858, + "treatment_followup_ci_95_high": 9.538639311, + "treatment_followup_ci_95_low": 6.031177849, + "treatment_followup_mean": 59.89999993, + "treatment_followup_p": 0, + "treatment_followup_se": 0.8947770187, + "treatment_followup_z": 8.700389503, + "treatment_p": 0.7232271073, + "treatment_post_b": 5.864929915, + "treatment_post_ci_95_high": 7.570766747, + "treatment_post_ci_95_low": 4.159093083, + "treatment_post_mean": 57.39999996, + "treatment_post_p": 1.598559151e-11, + "treatment_post_se": 0.870340907, + "treatment_post_z": 6.738658229, + "treatment_se": 1.129467618, + "treatment_z": 0.3541491529 + }, + "study_id": "SWL-S08", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S09_PYTHON_RESULT_RECEIPT.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S09_PYTHON_RESULT_RECEIPT.json new file mode 100644 index 0000000..a901db1 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S09_PYTHON_RESULT_RECEIPT.json @@ -0,0 +1,40 @@ +{ + "analysis_version": "0.1", + "batch_id": "psych-design-swl-s08-s10-v0.1", + "contract_version": "0.1", + "dataset_sha256": "cacafff8486624e4362629a1b7cf026aa347c7f48620b80029b6a1cfd0d31e16", + "design_contract_sha256": "a3b01225008425235bea1a2f7632367a7a7746ce4f43594908a13fa59a2709de", + "python_analysis_sha256": "f882af98887efec3a7756f41b1f2b54ca63df089dd449220cab68feedbd6b363", + "reported_fields": { + "burden_median_difference_intervention_minus_comparison": -3.7785, + "chi_square": 10.23017903, + "chi_square_df": 1, + "chi_square_p": 0.001381612526, + "comparison_burden_max": 42.091, + "comparison_burden_median": 10.533, + "comparison_burden_min": 5.147, + "comparison_burden_skewness": 1.191038677, + "comparison_improved": 36, + "comparison_improved_proportion": 0.45, + "comparison_not_improved": 44, + "cramers_v": 0.2528608687, + "improved_risk_difference_intervention_minus_comparison": 0.25, + "intervention_burden_max": 28.144, + "intervention_burden_median": 6.7545, + "intervention_burden_min": 3.051, + "intervention_burden_skewness": 1.180683232, + "intervention_improved": 56, + "intervention_improved_proportion": 0.7, + "intervention_not_improved": 24, + "mann_whitney_p_two_sided": 2.540505523e-05, + "mann_whitney_u_intervention": 1966, + "mann_whitney_z_no_continuity": -4.211170814, + "minimum_expected_cell_count": 34, + "n_comparison": 80, + "n_intervention": 80, + "n_total": 160, + "rank_biserial_intervention_lower": 0.385625 + }, + "study_id": "SWL-S09", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S10_PYTHON_RESULT_RECEIPT.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S10_PYTHON_RESULT_RECEIPT.json new file mode 100644 index 0000000..d8dc3da --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/results/SWL-S10_PYTHON_RESULT_RECEIPT.json @@ -0,0 +1,34 @@ +{ + "analysis_version": "0.1", + "batch_id": "psych-design-swl-s08-s10-v0.1", + "contract_version": "0.1", + "dataset_sha256": "4cb8239f978975c10191cbcdf47d17c62944e45dc2540cbc76ef5d4ccd366e36", + "design_contract_sha256": "cd690cb58bd797ddc82677894df95a014ec664eea2f28c4f748f51ffe6ff38c7", + "python_analysis_sha256": "f882af98887efec3a7756f41b1f2b54ca63df089dd449220cab68feedbd6b363", + "reported_fields": { + "cochran_q": 1.599988593, + "cochran_q_df": 5, + "cochran_q_p": 0.9012507239, + "fixed_effect_ci_95_high": 0.5277326023, + "fixed_effect_ci_95_low": 0.2693823169, + "fixed_effect_p": 1.472100141e-09, + "fixed_effect_pooled_d": 0.3985574596, + "fixed_effect_se": 0.06590568504, + "fixed_effect_z": 6.047391198, + "i_squared_percent": 0, + "maximum_observed_d": 0.51, + "minimum_observed_d": 0.28, + "planning_alpha_two_sided": 0.05, + "planning_n_per_group_ceiling_80": 99, + "planning_n_per_group_ceiling_90": 132, + "planning_n_per_group_unrounded_80": 98.11099668, + "planning_n_per_group_unrounded_90": 131.3427883, + "planning_power_primary": 0.8, + "planning_power_sensitivity": 0.9, + "study_count": 6, + "target_effect_size_d": 0.4, + "total_participants": 960 + }, + "study_id": "SWL-S10", + "synthetic_only": true +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/verification/SWL-S08/PYTHON_R_COMPARISON_POLICY.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/verification/SWL-S08/PYTHON_R_COMPARISON_POLICY.json new file mode 100644 index 0000000..79c6b79 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/verification/SWL-S08/PYTHON_R_COMPARISON_POLICY.json @@ -0,0 +1,25 @@ +{ + "contract_version": "0.1", + "python_engine": "statsmodels MixedLM", + "r_engine": "nlme::lme", + "shared_model": { + "estimation_method": "maximum likelihood", + "fixed_effects_formula": "well_being_score ~ treatment + post + followup + treatment_post + treatment_followup", + "group_reference": "control", + "occasion_reference": "baseline", + "random_effects_structure": "random intercept for participant_id" + }, + "study_id": "SWL-S08", + "v3_estimand_aligned_fields": { + "estimate_absolute_tolerance": 0.05, + "standard_error_absolute_tolerance": 0.15 + }, + "v4_documented_fields": [ + "test_statistic_type", + "denominator_degrees_of_freedom", + "p_value", + "confidence_interval_method", + "optimizer_details", + "log_likelihood_and_information_criteria" + ] +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/verification/swl_s01_s06_s07_v0_1_PYTHON_R_VERIFICATION_CONTRACT.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/verification/swl_s01_s06_s07_v0_1_PYTHON_R_VERIFICATION_CONTRACT.json new file mode 100644 index 0000000..64f6e93 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/verification/swl_s01_s06_s07_v0_1_PYTHON_R_VERIFICATION_CONTRACT.json @@ -0,0 +1,117 @@ +{ + "batch_id": "psych-design-swl-s01-s06-s07-v0.1", + "contract_version": "0.1", + "independent_implementation": "base R scripts do not call the Python implementation", + "runtime_outputs_tracked": false, + "studies": [ + { + "parity_fields": [ + "n", + "correlation_r_sleep_confidence", + "correlation_t", + "correlation_df", + "correlation_p_two_sided", + "correlation_ci_95_low", + "correlation_ci_95_high", + "regression_df_model", + "regression_df_residual", + "regression_f", + "regression_p", + "regression_r_squared", + "regression_adjusted_r_squared", + "regression_rmse", + "intercept_b", + "intercept_se", + "intercept_t", + "intercept_p", + "intercept_ci_95_low", + "intercept_ci_95_high", + "study_hours_b", + "study_hours_se", + "study_hours_t", + "study_hours_p", + "study_hours_ci_95_low", + "study_hours_ci_95_high", + "confidence_b", + "confidence_se", + "confidence_t", + "confidence_p", + "confidence_ci_95_low", + "confidence_ci_95_high" + ], + "r_script": "scripts/r/swl_s01.R", + "study_id": "SWL-S01", + "verification_category": "V2" + }, + { + "parity_fields": [ + "n_scheduled_participants", + "scheduled_rows", + "observed_outcomes", + "missing_outcomes", + "missing_baseline", + "missing_post", + "missing_follow_up", + "n_complete_case", + "n_incomplete_participants", + "mean_baseline_complete_case", + "mean_post_complete_case", + "mean_follow_up_complete_case", + "mean_change_post_minus_baseline", + "mean_change_follow_up_minus_baseline", + "rm_anova_f", + "rm_anova_df_occasion", + "rm_anova_df_error", + "rm_anova_p", + "greenhouse_geisser_epsilon", + "greenhouse_geisser_df_occasion", + "greenhouse_geisser_df_error", + "greenhouse_geisser_p", + "partial_eta_squared" + ], + "r_script": "scripts/r/swl_s06.R", + "study_id": "SWL-S06", + "verification_category": "V2" + }, + { + "parity_fields": [ + "n_total", + "n_comparison", + "n_intervention", + "baseline_overall_mean", + "raw_baseline_mean_comparison", + "raw_baseline_mean_intervention", + "raw_post_mean_comparison", + "raw_post_mean_intervention", + "adjusted_mean_comparison_at_overall_baseline", + "adjusted_mean_intervention_at_overall_baseline", + "adjusted_group_difference_intervention_minus_comparison", + "group_effect_se", + "group_effect_t", + "group_effect_p", + "group_effect_ci_95_low", + "group_effect_ci_95_high", + "group_effect_f", + "group_effect_df", + "model_df_error", + "group_partial_eta_squared", + "baseline_slope", + "baseline_slope_se", + "baseline_slope_t", + "baseline_slope_p", + "baseline_slope_ci_95_low", + "baseline_slope_ci_95_high", + "model_r_squared", + "model_adjusted_r_squared", + "homogeneity_interaction_b", + "homogeneity_interaction_se", + "homogeneity_interaction_t", + "homogeneity_interaction_p" + ], + "r_script": "scripts/r/swl_s07.R", + "study_id": "SWL-S07", + "verification_category": "V2" + } + ], + "tolerance": 1e-07 +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/verification/swl_s02_s05_v0_1_PYTHON_R_VERIFICATION_CONTRACT.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/verification/swl_s02_s05_v0_1_PYTHON_R_VERIFICATION_CONTRACT.json new file mode 100644 index 0000000..744be92 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/verification/swl_s02_s05_v0_1_PYTHON_R_VERIFICATION_CONTRACT.json @@ -0,0 +1,104 @@ +{ + "batch_id": "psych-design-swl-s02-s05-v0.1", + "contract_version": "0.1", + "independent_path_required": true, + "python_engine": "numpy/scipy transparent analysis", + "r_engine": "base R stats", + "runtime_output_policy": "R results and parity receipts are ignored build-instance evidence.", + "runtime_outputs_tracked": false, + "studies": [ + { + "parity_fields": [ + "ci_95_high", + "ci_95_low", + "cohen_d_pooled", + "mean_difference_structured_minus_standard", + "mean_standard", + "mean_structured", + "n_standard", + "n_structured", + "p_value_two_sided", + "sd_standard", + "sd_structured", + "welch_df", + "welch_t" + ], + "r_script": "scripts/r/swl_s02.R", + "study_id": "SWL-S02" + }, + { + "parity_fields": [ + "ci_95_high", + "ci_95_low", + "cohen_dz", + "df", + "mean_change_post_minus_pre", + "mean_post", + "mean_pre", + "n_paired", + "p_value_two_sided", + "paired_t", + "sd_change" + ], + "r_script": "scripts/r/swl_s03.R", + "study_id": "SWL-S03" + }, + { + "parity_fields": [ + "anova_f", + "contrast_feedback_increment_estimate", + "contrast_feedback_increment_p", + "contrast_feedback_increment_p_holm", + "contrast_feedback_increment_t", + "contrast_guided_average_minus_standard_estimate", + "contrast_guided_average_minus_standard_p", + "contrast_guided_average_minus_standard_p_holm", + "contrast_guided_average_minus_standard_t", + "df_between", + "df_within", + "eta_squared", + "mean_guided_practice", + "mean_guided_practice_plus_feedback", + "mean_standard_support", + "n_guided_practice", + "n_guided_practice_plus_feedback", + "n_standard_support", + "n_total", + "p_value" + ], + "r_script": "scripts/r/swl_s04.R", + "study_id": "SWL-S04" + }, + { + "parity_fields": [ + "df_effect", + "df_within", + "feedback_f", + "feedback_p", + "feedback_partial_eta_squared", + "interaction_difference_in_differences", + "interaction_f", + "interaction_p", + "interaction_partial_eta_squared", + "mean_rereading_explanatory_feedback", + "mean_rereading_no_feedback", + "mean_retrieval_explanatory_feedback", + "mean_retrieval_no_feedback", + "n_per_cell", + "n_total", + "simple_strategy_explanatory_feedback_estimate", + "simple_strategy_explanatory_feedback_p", + "simple_strategy_explanatory_feedback_t", + "simple_strategy_no_feedback_estimate", + "simple_strategy_no_feedback_p", + "simple_strategy_no_feedback_t", + "strategy_f", + "strategy_p", + "strategy_partial_eta_squared" + ], + "r_script": "scripts/r/swl_s05.R", + "study_id": "SWL-S05" + } + ], + "tolerance": 1e-07 +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/verification/swl_s08_s10_v0_1_PYTHON_R_VERIFICATION_CONTRACT.json b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/verification/swl_s08_s10_v0_1_PYTHON_R_VERIFICATION_CONTRACT.json new file mode 100644 index 0000000..a688b83 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/reference/verification/swl_s08_s10_v0_1_PYTHON_R_VERIFICATION_CONTRACT.json @@ -0,0 +1,120 @@ +{ + "batch_id": "psych-design-swl-s08-s10-v0.1", + "contract_version": "0.1", + "independent_implementation": "R scripts do not call the Python implementation; SWL-S08 uses nlme::lme and SWL-S09/S10 use explicit base-R calculations.", + "runtime_outputs_tracked": false, + "studies": [ + { + "absolute_tolerance_by_field": { + "followup_b": 0.05, + "followup_se": 0.15, + "intercept_b": 0.05, + "intercept_se": 0.15, + "post_b": 0.05, + "post_se": 0.15, + "treatment_b": 0.05, + "treatment_followup_b": 0.05, + "treatment_followup_se": 0.15, + "treatment_post_b": 0.05, + "treatment_post_se": 0.15, + "treatment_se": 0.15 + }, + "default_absolute_tolerance": 1e-07, + "parity_fields": [ + "n_participants", + "n_control", + "n_treatment", + "scheduled_rows", + "observed_rows", + "missing_visits", + "complete_participants", + "incomplete_participants", + "control_baseline_mean", + "control_post_mean", + "control_followup_mean", + "treatment_baseline_mean", + "treatment_post_mean", + "treatment_followup_mean", + "intercept_b", + "intercept_se", + "treatment_b", + "treatment_se", + "post_b", + "post_se", + "followup_b", + "followup_se", + "treatment_post_b", + "treatment_post_se", + "treatment_followup_b", + "treatment_followup_se" + ], + "r_script": "scripts/r/swl_s08.R", + "study_id": "SWL-S08", + "verification_category": "V3_estimand_aligned" + }, + { + "default_absolute_tolerance": 1e-07, + "parity_fields": [ + "n_total", + "n_comparison", + "n_intervention", + "comparison_improved", + "comparison_not_improved", + "intervention_improved", + "intervention_not_improved", + "comparison_improved_proportion", + "intervention_improved_proportion", + "improved_risk_difference_intervention_minus_comparison", + "chi_square", + "chi_square_df", + "chi_square_p", + "cramers_v", + "minimum_expected_cell_count", + "comparison_burden_median", + "intervention_burden_median", + "burden_median_difference_intervention_minus_comparison", + "mann_whitney_u_intervention", + "mann_whitney_z_no_continuity", + "mann_whitney_p_two_sided", + "rank_biserial_intervention_lower", + "comparison_burden_min", + "comparison_burden_max", + "intervention_burden_min", + "intervention_burden_max" + ], + "r_script": "scripts/r/swl_s09.R", + "study_id": "SWL-S09", + "verification_category": "V2_numeric_parity" + }, + { + "default_absolute_tolerance": 1e-07, + "parity_fields": [ + "study_count", + "total_participants", + "target_effect_size_d", + "planning_alpha_two_sided", + "planning_power_primary", + "planning_n_per_group_unrounded_80", + "planning_n_per_group_ceiling_80", + "planning_power_sensitivity", + "planning_n_per_group_unrounded_90", + "planning_n_per_group_ceiling_90", + "minimum_observed_d", + "maximum_observed_d", + "fixed_effect_pooled_d", + "fixed_effect_se", + "fixed_effect_z", + "fixed_effect_p", + "fixed_effect_ci_95_low", + "fixed_effect_ci_95_high", + "cochran_q", + "cochran_q_df", + "cochran_q_p", + "i_squared_percent" + ], + "r_script": "scripts/r/swl_s10.R", + "study_id": "SWL-S10", + "verification_category": "V2_numeric_parity" + } + ] +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/requirements-psych-design-companion.txt b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/requirements-psych-design-companion.txt new file mode 100644 index 0000000..f3b9ab8 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/requirements-psych-design-companion.txt @@ -0,0 +1,5 @@ +numpy==2.2.6 +pandas==2.2.3 +scipy==1.15.3 +statsmodels==0.14.6 +matplotlib==3.10.8 diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/__init__.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/__init__.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/__init__.py new file mode 100755 index 0000000..8b13789 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/__init__.py @@ -0,0 +1 @@ + diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/analysis_batches/__init__.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/analysis_batches/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/analysis_batches/batch_swl_s01_s06_s07.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/analysis_batches/batch_swl_s01_s06_s07.py new file mode 100755 index 0000000..cdd3e14 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/analysis_batches/batch_swl_s01_s06_s07.py @@ -0,0 +1,738 @@ +from __future__ import annotations + +import hashlib +import json +import math +from pathlib import Path +from typing import Any, Callable + +import numpy as np +import pandas as pd +from scipy import stats + +BATCH_ID = "psych-design-swl-s01-s06-s07-v0.1" +BASELINE_SOURCE_COMMIT = "da9abc9d5c71779cf794804cce6cfe245980b988" +BOOK_HANDOFF_COMMIT = "954835653734a33f234308fec78282b5dc8feb3d" +ANALYSIS_VERSION = "0.1" +STUDY_IDS = ("SWL-S01", "SWL-S06", "SWL-S07") + + +def stable_number(value: float | int) -> float | int: + if isinstance(value, (int, np.integer)): + return int(value) + value = float(value) + if not math.isfinite(value): + raise ValueError(f"non-finite result: {value}") + return float(f"{value:.10g}") + + +def write_json(path: Path, payload: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("w", encoding="utf-8", newline="\n") as handle: + handle.write(json.dumps(payload, indent=2, sort_keys=True) + "\n") + + +def sha256(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def _noise(values: list[float], n: int, shift: int = 0) -> np.ndarray: + base = np.asarray(values, dtype=float) + return np.roll(np.resize(base, n), shift) + + +def _ols(y: np.ndarray, x: np.ndarray) -> dict[str, Any]: + beta = np.linalg.solve(x.T @ x, x.T @ y) + fitted = x @ beta + residual = y - fitted + n, p = x.shape + df_residual = n - p + sse = float(residual @ residual) + sigma2 = sse / df_residual + xtx_inv = np.linalg.inv(x.T @ x) + covariance = sigma2 * xtx_inv + se = np.sqrt(np.diag(covariance)) + t_values = beta / se + p_values = 2.0 * stats.t.sf(np.abs(t_values), df_residual) + critical = float(stats.t.ppf(0.975, df_residual)) + ci_low = beta - critical * se + ci_high = beta + critical * se + y_centered = y - y.mean() + sst = float(y_centered @ y_centered) + r_squared = 1.0 - sse / sst + df_model = p - 1 + ss_model = sst - sse + f_value = (ss_model / df_model) / (sse / df_residual) + f_p = float(stats.f.sf(f_value, df_model, df_residual)) + leverage = np.einsum("ij,jk,ik->i", x, xtx_inv, x) + standardized = residual / np.sqrt(sigma2 * np.maximum(1e-12, 1.0 - leverage)) + cooks = (standardized**2 * leverage) / ( + p * np.maximum(1e-12, 1.0 - leverage) + ) + return { + "beta": beta, + "se": se, + "t": t_values, + "p": p_values, + "ci_low": ci_low, + "ci_high": ci_high, + "fitted": fitted, + "residual": residual, + "df_model": df_model, + "df_residual": df_residual, + "r_squared": r_squared, + "adjusted_r_squared": 1.0 - (1.0 - r_squared) * (n - 1) / df_residual, + "model_f": f_value, + "model_p": f_p, + "rmse": math.sqrt(sse / df_residual), + "leverage": leverage, + "standardized_residual": standardized, + "cooks_distance": cooks, + } + + +def generate_swl_s01() -> pd.DataFrame: + n = 120 + index = np.arange(n, dtype=float) + sleep = ( + 50 + + _noise([-12, -9, -7, -5, -3, -1, 0, 2, 4, 6, 8, 10], n) + + 0.28 * np.sin(index * 0.73) + + 0.12 * np.cos(index * 0.31) + ) + study_hours = ( + 7 + + 0.5 * _noise([-6, -4, -2, 0, 2, 4, 6, 3, -3, 1, -1, 5], n, shift=3) + + 0.09 * np.sin(index * 0.47) + ) + anxiety = ( + 58 + - 0.28 * sleep + + _noise([-5, -3, -2, -1, 0, 1, 2, 3, 4, -4, 1, -1], n, shift=5) + + 0.18 * np.cos(index * 0.41) + ) + confidence = ( + 42 + + 0.48 * sleep + - 0.16 * anxiety + + _noise([-4, -3, -2, -1, 0, 1, 2, 3, 4, -2, 1, -1], n, shift=7) + + 0.22 * np.sin(index * 0.29) + ) + performance = ( + 28 + + 2.1 * study_hours + + 0.58 * confidence + - 0.20 * anxiety + + _noise([-6, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 1, -2, 2, -4, 4], n, shift=2) + + 0.25 * np.cos(index * 0.37) + ) + return pd.DataFrame( + { + "study_id": "SWL-S01", + "participant_id": [f"S01-{index + 1:03d}" for index in range(n)], + "sleep_quality": np.round(sleep, 2), + "study_hours_per_week": np.round(study_hours, 2), + "anxiety_score": np.round(anxiety, 2), + "academic_confidence": np.round(confidence, 2), + "assessment_performance": np.round(performance, 2), + } + ) + + +def analyze_swl_s01(df: pd.DataFrame) -> dict[str, float | int]: + sleep = df["sleep_quality"].to_numpy(dtype=float) + confidence = df["academic_confidence"].to_numpy(dtype=float) + correlation = stats.pearsonr(sleep, confidence) + n = len(df) + r = float(correlation.statistic) + correlation_t = r * math.sqrt((n - 2) / (1.0 - r**2)) + fisher_se = 1.0 / math.sqrt(n - 3) + fisher_z = math.atanh(r) + fisher_critical = float(stats.norm.ppf(0.975)) + correlation_ci_low = math.tanh(fisher_z - fisher_critical * fisher_se) + correlation_ci_high = math.tanh(fisher_z + fisher_critical * fisher_se) + + y = df["assessment_performance"].to_numpy(dtype=float) + x = np.column_stack( + [ + np.ones(n), + df["study_hours_per_week"].to_numpy(dtype=float), + confidence, + ] + ) + model = _ols(y, x) + beta = model["beta"] + se = model["se"] + t_values = model["t"] + p_values = model["p"] + ci_low = model["ci_low"] + ci_high = model["ci_high"] + cooks = model["cooks_distance"] + standardized = model["standardized_residual"] + values: dict[str, float | int] = { + "n": n, + "correlation_r_sleep_confidence": r, + "correlation_t": correlation_t, + "correlation_df": n - 2, + "correlation_p_two_sided": float(correlation.pvalue), + "correlation_ci_95_low": correlation_ci_low, + "correlation_ci_95_high": correlation_ci_high, + "regression_df_model": model["df_model"], + "regression_df_residual": model["df_residual"], + "regression_f": model["model_f"], + "regression_p": model["model_p"], + "regression_r_squared": model["r_squared"], + "regression_adjusted_r_squared": model["adjusted_r_squared"], + "regression_rmse": model["rmse"], + "intercept_b": beta[0], + "intercept_se": se[0], + "intercept_t": t_values[0], + "intercept_p": p_values[0], + "intercept_ci_95_low": ci_low[0], + "intercept_ci_95_high": ci_high[0], + "study_hours_b": beta[1], + "study_hours_se": se[1], + "study_hours_t": t_values[1], + "study_hours_p": p_values[1], + "study_hours_ci_95_low": ci_low[1], + "study_hours_ci_95_high": ci_high[1], + "confidence_b": beta[2], + "confidence_se": se[2], + "confidence_t": t_values[2], + "confidence_p": p_values[2], + "confidence_ci_95_low": ci_low[2], + "confidence_ci_95_high": ci_high[2], + "sleep_quality_min": sleep.min(), + "sleep_quality_max": sleep.max(), + "study_hours_min": df["study_hours_per_week"].min(), + "study_hours_max": df["study_hours_per_week"].max(), + "academic_confidence_min": confidence.min(), + "academic_confidence_max": confidence.max(), + "max_abs_standardized_residual": np.max(np.abs(standardized)), + "max_cooks_distance": np.max(cooks), + "cooks_distance_above_4_over_n": int(np.sum(cooks > 4.0 / n)), + } + return {key: stable_number(value) for key, value in values.items()} + + +def generate_swl_s06() -> pd.DataFrame: + n = 60 + index = np.arange(n, dtype=float) + baseline = ( + 50 + + _noise([-7, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5], n) + + 0.18 * np.sin(index * 0.43) + ) + post = baseline + 4 + _noise( + [-2, -1, 0, 1, 2, 1, -1, 0, 2, -2, 1, -1], n, shift=4 + ) + 0.16 * np.cos(index * 0.39) + follow_up = baseline + 6 + _noise( + [-3, -2, -1, 0, 1, 2, 3, 1, -1, 2, -2, 0], n, shift=7 + ) + 0.14 * np.sin(index * 0.33) + post[[4, 16, 40]] = np.nan + follow_up[[8, 27, 53]] = np.nan + rows: list[dict[str, Any]] = [] + for index in range(n): + participant_id = f"S06-{index + 1:03d}" + for occasion, value in ( + ("baseline", baseline[index]), + ("post", post[index]), + ("follow_up", follow_up[index]), + ): + rows.append( + { + "study_id": "SWL-S06", + "participant_id": participant_id, + "occasion": occasion, + "academic_confidence": None + if np.isnan(value) + else round(float(value), 2), + } + ) + return pd.DataFrame(rows) + + +def analyze_swl_s06(df: pd.DataFrame) -> dict[str, float | int]: + wide = df.pivot( + index="participant_id", + columns="occasion", + values="academic_confidence", + ).sort_index() + wide = wide[["baseline", "post", "follow_up"]] + complete = wide.dropna() + y = complete.to_numpy(dtype=float) + n, k = y.shape + grand_mean = float(y.mean()) + subject_means = y.mean(axis=1) + occasion_means = y.mean(axis=0) + ss_total = float(np.square(y - grand_mean).sum()) + ss_subjects = float(k * np.square(subject_means - grand_mean).sum()) + ss_within = ss_total - ss_subjects + ss_occasion = float(n * np.square(occasion_means - grand_mean).sum()) + ss_error = ss_within - ss_occasion + df_occasion = k - 1 + df_error = (n - 1) * (k - 1) + f_value = (ss_occasion / df_occasion) / (ss_error / df_error) + p_value = float(stats.f.sf(f_value, df_occasion, df_error)) + covariance = np.cov(y, rowvar=False, ddof=1) + centering = np.eye(k) - np.ones((k, k)) / k + transformed = centering @ covariance @ centering + trace = float(np.trace(transformed)) + epsilon = trace**2 / ((k - 1) * float(np.trace(transformed @ transformed))) + epsilon = max(1.0 / (k - 1), min(1.0, epsilon)) + gg_df_occasion = epsilon * df_occasion + gg_df_error = epsilon * df_error + gg_p = float(stats.f.sf(f_value, gg_df_occasion, gg_df_error)) + missing_by_occasion = wide.isna().sum() + values: dict[str, float | int] = { + "n_scheduled_participants": len(wide), + "scheduled_rows": len(wide) * k, + "observed_outcomes": int(wide.notna().sum().sum()), + "missing_outcomes": int(wide.isna().sum().sum()), + "missing_baseline": int(missing_by_occasion["baseline"]), + "missing_post": int(missing_by_occasion["post"]), + "missing_follow_up": int(missing_by_occasion["follow_up"]), + "n_complete_case": n, + "n_incomplete_participants": len(wide) - n, + "mean_baseline_complete_case": occasion_means[0], + "mean_post_complete_case": occasion_means[1], + "mean_follow_up_complete_case": occasion_means[2], + "mean_change_post_minus_baseline": occasion_means[1] - occasion_means[0], + "mean_change_follow_up_minus_baseline": occasion_means[2] - occasion_means[0], + "rm_anova_f": f_value, + "rm_anova_df_occasion": df_occasion, + "rm_anova_df_error": df_error, + "rm_anova_p": p_value, + "greenhouse_geisser_epsilon": epsilon, + "greenhouse_geisser_df_occasion": gg_df_occasion, + "greenhouse_geisser_df_error": gg_df_error, + "greenhouse_geisser_p": gg_p, + "partial_eta_squared": ss_occasion / (ss_occasion + ss_error), + } + return {key: stable_number(value) for key, value in values.items()} + + +def generate_swl_s07() -> pd.DataFrame: + n_per_group = 45 + base_pattern = [-8, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7] + index = np.arange(n_per_group, dtype=float) + baseline_control = 49 + _noise(base_pattern, n_per_group) + 0.22 * np.sin(index * 0.51) + baseline_treatment = 52 + _noise(base_pattern, n_per_group, shift=4) + 0.19 * np.cos(index * 0.47) + error_pattern = [-6, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 1, -2, 2, -4] + post_control = 24 + 0.72 * baseline_control + _noise( + error_pattern, n_per_group, shift=1 + ) + 0.20 * np.cos(index * 0.37) + post_treatment = 24 + 0.72 * baseline_treatment + 5.5 + _noise( + error_pattern, n_per_group, shift=6 + ) + 0.18 * np.sin(index * 0.41) + rows: list[dict[str, Any]] = [] + for index in range(n_per_group): + rows.append( + { + "study_id": "SWL-S07", + "participant_id": f"S07-{index + 1:03d}", + "intervention_group": "comparison", + "baseline_performance": round(float(baseline_control[index]), 2), + "post_performance": round(float(post_control[index]), 2), + } + ) + for index in range(n_per_group): + rows.append( + { + "study_id": "SWL-S07", + "participant_id": f"S07-{n_per_group + index + 1:03d}", + "intervention_group": "intervention", + "baseline_performance": round(float(baseline_treatment[index]), 2), + "post_performance": round(float(post_treatment[index]), 2), + } + ) + return pd.DataFrame(rows) + + +def analyze_swl_s07(df: pd.DataFrame) -> dict[str, float | int]: + n = len(df) + treatment = (df["intervention_group"] == "intervention").astype(float).to_numpy() + baseline = df["baseline_performance"].to_numpy(dtype=float) + post = df["post_performance"].to_numpy(dtype=float) + baseline_mean = float(baseline.mean()) + centered = baseline - baseline_mean + x = np.column_stack([np.ones(n), treatment, centered]) + model = _ols(post, x) + beta = model["beta"] + se = model["se"] + t_values = model["t"] + p_values = model["p"] + ci_low = model["ci_low"] + ci_high = model["ci_high"] + interaction_x = np.column_stack( + [np.ones(n), treatment, centered, treatment * centered] + ) + interaction_model = _ols(post, interaction_x) + group_f = float(t_values[1] ** 2) + df_error = int(model["df_residual"]) + control_mask = treatment == 0 + intervention_mask = treatment == 1 + values: dict[str, float | int] = { + "n_total": n, + "n_comparison": int(control_mask.sum()), + "n_intervention": int(intervention_mask.sum()), + "baseline_overall_mean": baseline_mean, + "raw_baseline_mean_comparison": baseline[control_mask].mean(), + "raw_baseline_mean_intervention": baseline[intervention_mask].mean(), + "raw_post_mean_comparison": post[control_mask].mean(), + "raw_post_mean_intervention": post[intervention_mask].mean(), + "adjusted_mean_comparison_at_overall_baseline": beta[0], + "adjusted_mean_intervention_at_overall_baseline": beta[0] + beta[1], + "adjusted_group_difference_intervention_minus_comparison": beta[1], + "group_effect_se": se[1], + "group_effect_t": t_values[1], + "group_effect_p": p_values[1], + "group_effect_ci_95_low": ci_low[1], + "group_effect_ci_95_high": ci_high[1], + "group_effect_f": group_f, + "group_effect_df": 1, + "model_df_error": df_error, + "group_partial_eta_squared": group_f / (group_f + df_error), + "baseline_slope": beta[2], + "baseline_slope_se": se[2], + "baseline_slope_t": t_values[2], + "baseline_slope_p": p_values[2], + "baseline_slope_ci_95_low": ci_low[2], + "baseline_slope_ci_95_high": ci_high[2], + "model_r_squared": model["r_squared"], + "model_adjusted_r_squared": model["adjusted_r_squared"], + "homogeneity_interaction_b": interaction_model["beta"][3], + "homogeneity_interaction_se": interaction_model["se"][3], + "homogeneity_interaction_t": interaction_model["t"][3], + "homogeneity_interaction_p": interaction_model["p"][3], + } + return {key: stable_number(value) for key, value in values.items()} + + +GENERATORS: dict[str, Callable[[], pd.DataFrame]] = { + "SWL-S01": generate_swl_s01, + "SWL-S06": generate_swl_s06, + "SWL-S07": generate_swl_s07, +} +ANALYZERS: dict[str, Callable[[pd.DataFrame], dict[str, float | int]]] = { + "SWL-S01": analyze_swl_s01, + "SWL-S06": analyze_swl_s06, + "SWL-S07": analyze_swl_s07, +} + +STUDY_CONFIG: dict[str, dict[str, Any]] = { + "SWL-S01": { + "slug": "swl_s01_student_well_being_survey", + "chapter_ids": ["ch11", "ch12"], + "short_name": "Student Well-Being Survey", + "research_questions": [ + "How are sleep quality and academic confidence associated in the synthetic survey records?", + "How well do weekly study hours and academic confidence predict synthetic assessment performance within the registered predictor domain?", + ], + "design": "Cross-sectional synthetic survey with one row per participant.", + "unit_of_analysis": "student", + "row_structure": "One row per synthetic participant.", + "identifier_fields": ["participant_id"], + "outcomes": ["academic_confidence", "assessment_performance"], + "predictors": ["sleep_quality", "study_hours_per_week", "academic_confidence"], + "missingness_rule": "No missing values in the registered synthetic dataset.", + "causal_scope": "Association and within-domain prediction only; no intervention or causal claim is authorized.", + "limitations": { + "correlation": "The cross-sectional synthetic association does not establish temporal order, rule out common causes, or support the claim that changing sleep would change confidence.", + "regression": "Predictive performance is registered only for the generated records and observed predictor ranges; the model does not authorize extrapolation, causal interpretation, or performance claims for real students.", + }, + "figure_types": ["correlation_scatter", "regression_residual_diagnostic"], + "apa_field_sets": { + "correlation": [ + "n", + "correlation_r_sleep_confidence", + "correlation_df", + "correlation_p_two_sided", + "correlation_ci_95_low", + "correlation_ci_95_high", + ], + "regression": [ + "n", + "regression_f", + "regression_df_model", + "regression_df_residual", + "regression_p", + "regression_r_squared", + "study_hours_b", + "study_hours_se", + "study_hours_t", + "study_hours_p", + "confidence_b", + "confidence_se", + "confidence_t", + "confidence_p", + ], + }, + }, + "SWL-S06": { + "slug": "swl_s06_confidence_trajectory", + "chapter_ids": ["ch10"], + "short_name": "Confidence Trajectory Study", + "research_questions": [ + "Does mean academic confidence differ across baseline, post, and follow-up among complete synthetic trajectories?" + ], + "design": "Three-occasion repeated-measures study with six scheduled outcomes missing.", + "unit_of_analysis": "student", + "row_structure": "Three scheduled participant-occasion rows per student.", + "identifier_fields": ["participant_id", "occasion"], + "factor_levels": {"occasion": ["baseline", "post", "follow_up"]}, + "outcomes": ["academic_confidence"], + "missingness_rule": "The registered repeated-measures ANOVA uses complete trajectories and separately reports all missing visits.", + "causal_scope": "Occasion differences are descriptive/inferential within the registered synthetic schedule and do not identify a unique cause.", + "limitations": { + "primary": "The complete-case repeated-measures analysis excludes six participants with a missing scheduled outcome and can be biased when missingness is informative; a mixed model is required for the later incomplete-longitudinal workflow." + }, + "figure_types": ["complete_case_trajectory"], + "apa_field_sets": { + "primary": [ + "n_complete_case", + "mean_baseline_complete_case", + "mean_post_complete_case", + "mean_follow_up_complete_case", + "rm_anova_f", + "rm_anova_df_occasion", + "rm_anova_df_error", + "greenhouse_geisser_epsilon", + "greenhouse_geisser_df_occasion", + "greenhouse_geisser_df_error", + "greenhouse_geisser_p", + "partial_eta_squared", + ] + }, + }, + "SWL-S07": { + "slug": "swl_s07_baseline_adjusted_intervention", + "chapter_ids": ["ch13"], + "short_name": "Baseline-Adjusted Intervention", + "research_questions": [ + "Do the intervention and comparison groups differ in post performance after adjustment to the overall baseline mean?" + ], + "design": "Two-group synthetic intervention study with one pre-treatment baseline covariate.", + "unit_of_analysis": "student", + "row_structure": "One row per synthetic participant.", + "identifier_fields": ["participant_id"], + "factor_levels": {"intervention_group": ["comparison", "intervention"]}, + "outcomes": ["post_performance"], + "predictors": ["intervention_group", "baseline_performance"], + "missingness_rule": "No missing values in the registered synthetic dataset.", + "causal_scope": "Causal language remains conditional on the declared assignment and intervention implementation; adjustment alone does not create randomization.", + "limitations": { + "primary": "The adjusted comparison depends on linearity, reliable pre-treatment measurement, and sufficiently homogeneous covariate slopes; baseline adjustment does not repair unmeasured confounding or support generalization beyond the synthetic implementation." + }, + "figure_types": ["ancova_parallel_lines"], + "apa_field_sets": { + "primary": [ + "n_total", + "adjusted_mean_comparison_at_overall_baseline", + "adjusted_mean_intervention_at_overall_baseline", + "adjusted_group_difference_intervention_minus_comparison", + "group_effect_f", + "group_effect_df", + "model_df_error", + "group_effect_p", + "group_partial_eta_squared", + "homogeneity_interaction_p", + ] + }, + }, +} + + +def dataset_path(root: Path, study_id: str) -> Path: + return root / "data" / f"{STUDY_CONFIG[study_id]['slug']}.csv" + + +def validate_dataset(study_id: str, frame: pd.DataFrame) -> None: + if set(frame["study_id"].unique()) != {study_id}: + raise ValueError(f"study ID drift: {study_id}") + if study_id == "SWL-S01": + if len(frame) != 120 or frame["participant_id"].nunique() != 120: + raise ValueError("SWL-S01 row or participant count") + if frame.isna().any().any(): + raise ValueError("SWL-S01 missing values") + elif study_id == "SWL-S06": + if len(frame) != 180 or frame["participant_id"].nunique() != 60: + raise ValueError("SWL-S06 row or participant count") + if frame.duplicated(["participant_id", "occasion"]).any(): + raise ValueError("SWL-S06 duplicate participant-occasion key") + if set(frame["occasion"].unique()) != {"baseline", "post", "follow_up"}: + raise ValueError("SWL-S06 occasion levels") + if int(frame["academic_confidence"].isna().sum()) != 6: + raise ValueError("SWL-S06 missing-visit count") + elif study_id == "SWL-S07": + if len(frame) != 90 or frame["participant_id"].nunique() != 90: + raise ValueError("SWL-S07 row or participant count") + counts = frame["intervention_group"].value_counts().to_dict() + if counts != {"comparison": 45, "intervention": 45}: + raise ValueError("SWL-S07 group counts") + if frame.isna().any().any(): + raise ValueError("SWL-S07 missing values") + else: + raise ValueError(f"unknown study: {study_id}") + + +def result_receipt(root: Path, study_id: str, frame: pd.DataFrame) -> dict[str, Any]: + dataset = dataset_path(root, study_id) + contract = root / "contracts" / f"{study_id}_DESIGN_CONTRACT.json" + return { + "analysis_version": ANALYSIS_VERSION, + "batch_id": BATCH_ID, + "contract_version": "0.1", + "dataset_sha256": sha256(dataset), + "design_contract_sha256": sha256(contract), + "python_analysis_sha256": sha256(Path(__file__)), + "reported_fields": ANALYZERS[study_id](frame), + "study_id": study_id, + "synthetic_only": True, + } + + +def design_contract(study_id: str) -> dict[str, Any]: + config = STUDY_CONFIG[study_id] + return { + "analysis_version": ANALYSIS_VERSION, + "batch_id": BATCH_ID, + "causal_scope": config["causal_scope"], + "chapter_ids": config["chapter_ids"], + "contract_version": "0.1", + "design": config["design"], + "identifier_fields": config["identifier_fields"], + "missingness_rule": config["missingness_rule"], + "real_data_authorized": False, + "research_questions": config["research_questions"], + "row_structure": config["row_structure"], + "short_name": config["short_name"], + "study_id": study_id, + "synthetic_only": True, + "unit_of_analysis": config["unit_of_analysis"], + **({"factor_levels": config["factor_levels"]} if "factor_levels" in config else {}), + **({"outcomes": config["outcomes"]} if "outcomes" in config else {}), + **({"predictors": config["predictors"]} if "predictors" in config else {}), + } + + +def figure_specs(study_id: str) -> list[tuple[str, dict[str, Any]]]: + config = STUDY_CONFIG[study_id] + result: list[tuple[str, dict[str, Any]]] = [] + for figure_type in config["figure_types"]: + role = { + "correlation_scatter": "correlation", + "regression_residual_diagnostic": "regression", + "complete_case_trajectory": "primary", + "ancova_parallel_lines": "primary", + }[figure_type] + suffix = role.upper() + result.append( + ( + f"FIGURE_SPEC_{suffix}.json" if study_id == "SWL-S01" else "FIGURE_SPEC.json", + { + "batch_id": BATCH_ID, + "chapter_ids": config["chapter_ids"], + "contract_version": "0.1", + "figure_role": role, + "figure_type": figure_type, + "generator": "scripts/python/generate_figures.py", + "grayscale": True, + "result_source": f"evidence/{study_id}/PYTHON_RESULT_RECEIPT.json", + "study_id": study_id, + "synthetic_only": True, + }, + ) + ) + return result + + +def apa_maps(study_id: str) -> list[tuple[str, dict[str, Any]]]: + config = STUDY_CONFIG[study_id] + result: list[tuple[str, dict[str, Any]]] = [] + for role, fields in config["apa_field_sets"].items(): + filename = ( + f"APA_RESULT_SOURCE_MAP_{role.upper()}.json" + if study_id == "SWL-S01" + else "APA_RESULT_SOURCE_MAP.json" + ) + result.append( + ( + filename, + { + "batch_id": BATCH_ID, + "chapter_ids": config["chapter_ids"], + "contract_version": "0.1", + "reporting_role": role, + "reported_number_bindings": [ + { + "field": field, + "json_pointer": f"/reported_fields/{field}", + } + for field in fields + ], + "result_receipt": f"evidence/{study_id}/PYTHON_RESULT_RECEIPT.json", + "study_id": study_id, + "synthetic_only": True, + }, + ) + ) + return result + + +def limitation_records(study_id: str) -> list[tuple[str, dict[str, Any]]]: + config = STUDY_CONFIG[study_id] + result: list[tuple[str, dict[str, Any]]] = [] + for role, text in config["limitations"].items(): + filename = ( + f"MATCHED_LIMITATION_{role.upper()}.json" + if study_id == "SWL-S01" + else "MATCHED_LIMITATION.json" + ) + result.append( + ( + filename, + { + "batch_id": BATCH_ID, + "chapter_ids": config["chapter_ids"], + "contract_version": "0.1", + "limitation": text, + "limitation_role": role, + "study_id": study_id, + "synthetic_only": True, + }, + ) + ) + return result + + +def generate_all(output_root: Path) -> list[Path]: + generated: list[Path] = [] + output_root.mkdir(parents=True, exist_ok=True) + for study_id in STUDY_IDS: + frame = GENERATORS[study_id]() + validate_dataset(study_id, frame) + dataset = dataset_path(output_root, study_id) + dataset.parent.mkdir(parents=True, exist_ok=True) + csv_text = frame.to_csv(index=False, lineterminator="\n", na_rep="") + dataset.write_bytes(csv_text.encode("utf-8")) + generated.append(dataset) + contract = output_root / "contracts" / f"{study_id}_DESIGN_CONTRACT.json" + write_json(contract, design_contract(study_id)) + generated.append(contract) + receipt = output_root / "evidence" / study_id / "PYTHON_RESULT_RECEIPT.json" + write_json(receipt, result_receipt(output_root, study_id, frame)) + generated.append(receipt) + for filename, payload in figure_specs(study_id): + path = output_root / "evidence" / study_id / filename + write_json(path, payload) + generated.append(path) + for filename, payload in apa_maps(study_id): + path = output_root / "evidence" / study_id / filename + write_json(path, payload) + generated.append(path) + for filename, payload in limitation_records(study_id): + path = output_root / "evidence" / study_id / filename + write_json(path, payload) + generated.append(path) + return generated diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/analysis_batches/batch_swl_s02_s05.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/analysis_batches/batch_swl_s02_s05.py new file mode 100755 index 0000000..e4d26a0 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/analysis_batches/batch_swl_s02_s05.py @@ -0,0 +1,999 @@ +from __future__ import annotations + +import hashlib +import json +import math +from pathlib import Path +from typing import Any, Callable + +import numpy as np +import pandas as pd +from scipy import stats + +BATCH_ID = "psych-design-swl-s02-s05-v0.1" +BASELINE_SOURCE_COMMIT = "621b1eb4a534f4dfedb0647b7e0853a7fd9b8a90" +BOOK_HANDOFF_COMMIT = "0bb3db0c53dd3bb7ad18adc682a3fe1d39de5df0" +ANALYSIS_VERSION = "0.1" +STUDY_IDS = ("SWL-S02", "SWL-S03", "SWL-S04", "SWL-S05") + + +def stable_number(value: float | int) -> float | int: + if isinstance(value, int): + return value + value = float(value) + if not math.isfinite(value): + raise ValueError(f"non-finite result: {value}") + return float(f"{value:.10g}") + + +def write_json(path: Path, payload: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("w", encoding="utf-8", newline="\n") as handle: + handle.write(json.dumps(payload, indent=2, sort_keys=True) + "\n") + + +def sha256(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def _noise(values: list[int], n: int, shift: int = 0) -> np.ndarray: + base = np.asarray(values, dtype=float) + resized = np.resize(base, n) + return np.roll(resized, shift) + + +def generate_swl_s02() -> pd.DataFrame: + n_per_group = 32 + base_noise = [-6, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 1, -2, 2, -4, 4] + rows: list[dict[str, Any]] = [] + for index in range(n_per_group): + rows.append( + { + "study_id": "SWL-S02", + "participant_id": f"S02-{index + 1:03d}", + "study_routine_group": "standard_routine", + "post_session_performance": int(68 + _noise(base_noise, n_per_group)[index]), + } + ) + for index in range(n_per_group): + rows.append( + { + "study_id": "SWL-S02", + "participant_id": f"S02-{n_per_group + index + 1:03d}", + "study_routine_group": "structured_routine", + "post_session_performance": int( + 74 + _noise(base_noise, n_per_group, shift=5)[index] + ), + } + ) + return pd.DataFrame(rows) + + +def analyze_swl_s02(df: pd.DataFrame) -> dict[str, float | int]: + standard = df.loc[ + df["study_routine_group"] == "standard_routine", + "post_session_performance", + ].to_numpy(dtype=float) + structured = df.loc[ + df["study_routine_group"] == "structured_routine", + "post_session_performance", + ].to_numpy(dtype=float) + test = stats.ttest_ind(structured, standard, equal_var=False) + mean_difference = float(structured.mean() - standard.mean()) + variance_a = float(structured.var(ddof=1)) + variance_b = float(standard.var(ddof=1)) + se = math.sqrt(variance_a / len(structured) + variance_b / len(standard)) + df_welch = float(test.df) + critical = float(stats.t.ppf(0.975, df_welch)) + pooled_sd = math.sqrt( + ( + (len(structured) - 1) * variance_a + + (len(standard) - 1) * variance_b + ) + / (len(structured) + len(standard) - 2) + ) + values: dict[str, float | int] = { + "n_standard": len(standard), + "n_structured": len(structured), + "mean_standard": standard.mean(), + "mean_structured": structured.mean(), + "sd_standard": standard.std(ddof=1), + "sd_structured": structured.std(ddof=1), + "mean_difference_structured_minus_standard": mean_difference, + "welch_t": float(test.statistic), + "welch_df": df_welch, + "p_value_two_sided": float(test.pvalue), + "ci_95_low": mean_difference - critical * se, + "ci_95_high": mean_difference + critical * se, + "cohen_d_pooled": mean_difference / pooled_sd, + } + return {key: stable_number(value) for key, value in values.items()} + + +def generate_swl_s03() -> pd.DataFrame: + n = 48 + pre_noise = [-8, -6, -5, -3, -2, -1, 0, 1, 2, 3, 5, 7] + change_noise = [-5, -3, -2, -1, 0, 1, 2, 3, 4, 5, -4, 2] + pre = 55 + _noise(pre_noise, n) + change = 4 + _noise(change_noise, n) + rows: list[dict[str, Any]] = [] + for index in range(n): + participant_id = f"S03-{index + 1:03d}" + rows.append( + { + "study_id": "SWL-S03", + "participant_id": participant_id, + "occasion": "pre", + "academic_confidence": int(pre[index]), + } + ) + rows.append( + { + "study_id": "SWL-S03", + "participant_id": participant_id, + "occasion": "post", + "academic_confidence": int(pre[index] + change[index]), + } + ) + return pd.DataFrame(rows) + + +def analyze_swl_s03(df: pd.DataFrame) -> dict[str, float | int]: + wide = df.pivot( + index="participant_id", + columns="occasion", + values="academic_confidence", + ).sort_index() + pre = wide["pre"].to_numpy(dtype=float) + post = wide["post"].to_numpy(dtype=float) + differences = post - pre + test = stats.ttest_rel(post, pre) + n = len(differences) + mean_change = float(differences.mean()) + sd_change = float(differences.std(ddof=1)) + se = sd_change / math.sqrt(n) + critical = float(stats.t.ppf(0.975, n - 1)) + values: dict[str, float | int] = { + "n_paired": n, + "mean_pre": pre.mean(), + "mean_post": post.mean(), + "mean_change_post_minus_pre": mean_change, + "sd_change": sd_change, + "paired_t": float(test.statistic), + "df": n - 1, + "p_value_two_sided": float(test.pvalue), + "ci_95_low": mean_change - critical * se, + "ci_95_high": mean_change + critical * se, + "cohen_dz": mean_change / sd_change, + } + return {key: stable_number(value) for key, value in values.items()} + + +def generate_swl_s04() -> pd.DataFrame: + n_per_condition = 30 + base_noise = [-7, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, -2, 2] + conditions = ( + ("standard_support", 67, 0), + ("guided_practice", 72, 4), + ("guided_practice_plus_feedback", 78, 8), + ) + rows: list[dict[str, Any]] = [] + next_id = 1 + for condition, mean, shift in conditions: + condition_noise = _noise(base_noise, n_per_condition, shift=shift) + for index in range(n_per_condition): + rows.append( + { + "study_id": "SWL-S04", + "participant_id": f"S04-{next_id:03d}", + "support_condition": condition, + "assessment_performance": int(mean + condition_noise[index]), + } + ) + next_id += 1 + return pd.DataFrame(rows) + + +def _holm_adjust_two(p_first: float, p_second: float) -> tuple[float, float]: + indexed = sorted(enumerate((p_first, p_second)), key=lambda pair: pair[1]) + adjusted = [0.0, 0.0] + first_index, first_value = indexed[0] + second_index, second_value = indexed[1] + adjusted[first_index] = min(1.0, 2.0 * first_value) + adjusted[second_index] = max(adjusted[first_index], min(1.0, second_value)) + return adjusted[0], adjusted[1] + + +def analyze_swl_s04(df: pd.DataFrame) -> dict[str, float | int]: + levels = ( + "standard_support", + "guided_practice", + "guided_practice_plus_feedback", + ) + arrays = { + level: df.loc[ + df["support_condition"] == level, + "assessment_performance", + ].to_numpy(dtype=float) + for level in levels + } + all_values = np.concatenate([arrays[level] for level in levels]) + grand_mean = float(all_values.mean()) + group_means = {level: float(arrays[level].mean()) for level in levels} + ss_between = sum( + len(arrays[level]) * (group_means[level] - grand_mean) ** 2 + for level in levels + ) + ss_within = sum( + float(np.square(arrays[level] - group_means[level]).sum()) + for level in levels + ) + df_between = len(levels) - 1 + df_within = len(all_values) - len(levels) + ms_between = ss_between / df_between + ms_within = ss_within / df_within + f_value = ms_between / ms_within + p_value = float(stats.f.sf(f_value, df_between, df_within)) + + contrasts = ( + ( + "guided_average_minus_standard", + { + "standard_support": -1.0, + "guided_practice": 0.5, + "guided_practice_plus_feedback": 0.5, + }, + ), + ( + "feedback_increment_within_guided", + { + "standard_support": 0.0, + "guided_practice": -1.0, + "guided_practice_plus_feedback": 1.0, + }, + ), + ) + contrast_results: list[tuple[str, float, float, float]] = [] + for name, coefficients in contrasts: + estimate = sum( + coefficients[level] * group_means[level] for level in levels + ) + se = math.sqrt( + ms_within + * sum( + coefficients[level] ** 2 / len(arrays[level]) + for level in levels + ) + ) + t_value = estimate / se + p_contrast = float(2.0 * stats.t.sf(abs(t_value), df_within)) + contrast_results.append((name, estimate, t_value, p_contrast)) + adjusted = _holm_adjust_two( + contrast_results[0][3], + contrast_results[1][3], + ) + + values: dict[str, float | int] = { + "n_total": len(all_values), + "n_standard_support": len(arrays["standard_support"]), + "n_guided_practice": len(arrays["guided_practice"]), + "n_guided_practice_plus_feedback": len( + arrays["guided_practice_plus_feedback"] + ), + "mean_standard_support": group_means["standard_support"], + "mean_guided_practice": group_means["guided_practice"], + "mean_guided_practice_plus_feedback": group_means[ + "guided_practice_plus_feedback" + ], + "anova_f": f_value, + "df_between": df_between, + "df_within": df_within, + "p_value": p_value, + "eta_squared": ss_between / (ss_between + ss_within), + "contrast_guided_average_minus_standard_estimate": contrast_results[0][1], + "contrast_guided_average_minus_standard_t": contrast_results[0][2], + "contrast_guided_average_minus_standard_p": contrast_results[0][3], + "contrast_guided_average_minus_standard_p_holm": adjusted[0], + "contrast_feedback_increment_estimate": contrast_results[1][1], + "contrast_feedback_increment_t": contrast_results[1][2], + "contrast_feedback_increment_p": contrast_results[1][3], + "contrast_feedback_increment_p_holm": adjusted[1], + } + return {key: stable_number(value) for key, value in values.items()} + + +def generate_swl_s05() -> pd.DataFrame: + n_per_cell = 24 + base_noise = [-6, -5, -3, -2, -1, 0, 1, 2, 3, 4, 5, 2, -2, 1, -4, 4] + cells = ( + ("rereading", "no_feedback", 68, 0), + ("retrieval_practice", "no_feedback", 72, 3), + ("rereading", "explanatory_feedback", 70, 6), + ("retrieval_practice", "explanatory_feedback", 80, 9), + ) + rows: list[dict[str, Any]] = [] + next_id = 1 + for strategy, feedback, mean, shift in cells: + cell_noise = _noise(base_noise, n_per_cell, shift=shift) + for index in range(n_per_cell): + rows.append( + { + "study_id": "SWL-S05", + "participant_id": f"S05-{next_id:03d}", + "study_strategy": strategy, + "feedback_condition": feedback, + "assessment_performance": int(mean + cell_noise[index]), + } + ) + next_id += 1 + return pd.DataFrame(rows) + + +def analyze_swl_s05(df: pd.DataFrame) -> dict[str, float | int]: + strategies = ("rereading", "retrieval_practice") + feedback_levels = ("no_feedback", "explanatory_feedback") + cells = { + (strategy, feedback): df.loc[ + (df["study_strategy"] == strategy) + & (df["feedback_condition"] == feedback), + "assessment_performance", + ].to_numpy(dtype=float) + for strategy in strategies + for feedback in feedback_levels + } + cell_sizes = {len(values) for values in cells.values()} + if len(cell_sizes) != 1: + raise ValueError("SWL-S05 requires balanced cell sizes") + n_per_cell = cell_sizes.pop() + all_values = np.concatenate(list(cells.values())) + grand_mean = float(all_values.mean()) + cell_means = {key: float(values.mean()) for key, values in cells.items()} + strategy_means = { + strategy: np.concatenate( + [cells[(strategy, feedback)] for feedback in feedback_levels] + ).mean() + for strategy in strategies + } + feedback_means = { + feedback: np.concatenate( + [cells[(strategy, feedback)] for strategy in strategies] + ).mean() + for feedback in feedback_levels + } + ss_strategy = len(feedback_levels) * n_per_cell * sum( + (float(strategy_means[strategy]) - grand_mean) ** 2 + for strategy in strategies + ) + ss_feedback = len(strategies) * n_per_cell * sum( + (float(feedback_means[feedback]) - grand_mean) ** 2 + for feedback in feedback_levels + ) + ss_interaction = n_per_cell * sum( + ( + cell_means[(strategy, feedback)] + - float(strategy_means[strategy]) + - float(feedback_means[feedback]) + + grand_mean + ) + ** 2 + for strategy in strategies + for feedback in feedback_levels + ) + ss_within = sum( + float(np.square(values - cell_means[key]).sum()) + for key, values in cells.items() + ) + df_within = len(all_values) - len(cells) + ms_within = ss_within / df_within + f_strategy = ss_strategy / ms_within + f_feedback = ss_feedback / ms_within + f_interaction = ss_interaction / ms_within + + simple_results: dict[str, tuple[float, float, float]] = {} + for feedback in feedback_levels: + estimate = ( + cell_means[("retrieval_practice", feedback)] + - cell_means[("rereading", feedback)] + ) + se = math.sqrt(ms_within * (2.0 / n_per_cell)) + t_value = estimate / se + p_value = float(2.0 * stats.t.sf(abs(t_value), df_within)) + simple_results[feedback] = (estimate, t_value, p_value) + + interaction_estimate = ( + simple_results["explanatory_feedback"][0] + - simple_results["no_feedback"][0] + ) + values: dict[str, float | int] = { + "n_total": len(all_values), + "n_per_cell": n_per_cell, + "mean_rereading_no_feedback": cell_means[("rereading", "no_feedback")], + "mean_retrieval_no_feedback": cell_means[ + ("retrieval_practice", "no_feedback") + ], + "mean_rereading_explanatory_feedback": cell_means[ + ("rereading", "explanatory_feedback") + ], + "mean_retrieval_explanatory_feedback": cell_means[ + ("retrieval_practice", "explanatory_feedback") + ], + "strategy_f": f_strategy, + "strategy_p": float(stats.f.sf(f_strategy, 1, df_within)), + "strategy_partial_eta_squared": ss_strategy / (ss_strategy + ss_within), + "feedback_f": f_feedback, + "feedback_p": float(stats.f.sf(f_feedback, 1, df_within)), + "feedback_partial_eta_squared": ss_feedback / (ss_feedback + ss_within), + "interaction_f": f_interaction, + "interaction_p": float(stats.f.sf(f_interaction, 1, df_within)), + "interaction_partial_eta_squared": ss_interaction + / (ss_interaction + ss_within), + "df_effect": 1, + "df_within": df_within, + "interaction_difference_in_differences": interaction_estimate, + "simple_strategy_no_feedback_estimate": simple_results["no_feedback"][0], + "simple_strategy_no_feedback_t": simple_results["no_feedback"][1], + "simple_strategy_no_feedback_p": simple_results["no_feedback"][2], + "simple_strategy_explanatory_feedback_estimate": simple_results[ + "explanatory_feedback" + ][0], + "simple_strategy_explanatory_feedback_t": simple_results[ + "explanatory_feedback" + ][1], + "simple_strategy_explanatory_feedback_p": simple_results[ + "explanatory_feedback" + ][2], + } + return {key: stable_number(value) for key, value in values.items()} + + +GENERATORS: dict[str, Callable[[], pd.DataFrame]] = { + "SWL-S02": generate_swl_s02, + "SWL-S03": generate_swl_s03, + "SWL-S04": generate_swl_s04, + "SWL-S05": generate_swl_s05, +} +ANALYZERS: dict[str, Callable[[pd.DataFrame], dict[str, float | int]]] = { + "SWL-S02": analyze_swl_s02, + "SWL-S03": analyze_swl_s03, + "SWL-S04": analyze_swl_s04, + "SWL-S05": analyze_swl_s05, +} + +STUDY_CONFIG: dict[str, dict[str, Any]] = { + "SWL-S02": { + "slug": "swl_s02_structured_study_routine", + "chapter_id": "ch06", + "short_name": "Structured Study-Routine Pilot", + "research_question": ( + "Do students assigned to a structured study routine differ in " + "post-session performance from students using a standard routine?" + ), + "design": "Two-group pilot with declared stratified random assignment.", + "unit_of_analysis": "student", + "row_structure": "One row per student.", + "identifier_fields": ["participant_id"], + "factor_levels": { + "study_routine_group": ["standard_routine", "structured_routine"] + }, + "outcome": "post_session_performance", + "assignment_procedure": ( + "Synthetic students are assigned in equal numbers by a declared " + "computer-generated blocked procedure." + ), + "missingness_rule": "No missing outcomes in the registered synthetic dataset.", + "causal_scope": ( + "Causal interpretation is limited to the declared assignment, " + "implementation, and synthetic pilot conditions." + ), + "limitation": ( + "The small synthetic pilot represents one implementation of the " + "routine; it does not establish transportability to other students, " + "settings, or delivery conditions." + ), + "figure_type": "two_group_mean_ci", + "apa_fields": [ + "mean_standard", + "sd_standard", + "mean_structured", + "sd_structured", + "welch_t", + "welch_df", + "p_value_two_sided", + "ci_95_low", + "ci_95_high", + "cohen_d_pooled", + ], + }, + "SWL-S03": { + "slug": "swl_s03_skills_workshop_pre_post", + "chapter_id": "ch07", + "short_name": "Skills Workshop Pre/Post Study", + "research_question": ( + "How does academic confidence change from before to after a skills " + "workshop for students with linked observations?" + ), + "design": "Within-person pre/post study.", + "unit_of_analysis": "student", + "row_structure": "Two occasion rows per participant in long form.", + "identifier_fields": ["participant_id", "occasion"], + "factor_levels": {"occasion": ["pre", "post"]}, + "outcome": "academic_confidence", + "assignment_procedure": "No control condition is present.", + "missingness_rule": ( + "The registered dataset contains exactly one pre and one post row " + "per participant; incomplete pairs are not silently retained." + ), + "causal_scope": ( + "The analysis estimates within-person change, not the workshop's " + "causal effect in the absence of a control condition." + ), + "limitation": ( + "Pre/post change may reflect history, testing, maturation, or other " + "time-varying influences because the design has no control condition." + ), + "figure_type": "paired_change", + "apa_fields": [ + "n_paired", + "mean_pre", + "mean_post", + "mean_change_post_minus_pre", + "sd_change", + "paired_t", + "df", + "p_value_two_sided", + "ci_95_low", + "ci_95_high", + "cohen_dz", + ], + }, + "SWL-S04": { + "slug": "swl_s04_three_condition_support", + "chapter_id": "ch08", + "short_name": "Three-Condition Learning-Support Study", + "research_question": ( + "Do assessment outcomes differ across standard support, guided " + "practice, and guided practice plus feedback, and which planned " + "contrasts answer the substantive questions?" + ), + "design": "Three-condition between-student randomized study.", + "unit_of_analysis": "student", + "row_structure": "One row per student.", + "identifier_fields": ["participant_id"], + "factor_levels": { + "support_condition": [ + "standard_support", + "guided_practice", + "guided_practice_plus_feedback", + ] + }, + "outcome": "assessment_performance", + "assignment_procedure": ( + "Synthetic students are assigned in equal numbers to three declared " + "conditions." + ), + "missingness_rule": "No missing outcomes in the registered synthetic dataset.", + "causal_scope": ( + "Causal interpretation is conditional on the declared random " + "assignment and implementation fidelity." + ), + "limitation": ( + "The two planned contrasts answer only the registered support " + "questions; they do not justify unregistered pairwise fishing or " + "generalization beyond the synthetic implementation." + ), + "figure_type": "three_group_mean_ci", + "apa_fields": [ + "anova_f", + "df_between", + "df_within", + "p_value", + "eta_squared", + "contrast_guided_average_minus_standard_estimate", + "contrast_guided_average_minus_standard_t", + "contrast_guided_average_minus_standard_p_holm", + "contrast_feedback_increment_estimate", + "contrast_feedback_increment_t", + "contrast_feedback_increment_p_holm", + ], + }, + "SWL-S05": { + "slug": "swl_s05_strategy_feedback", + "chapter_id": "ch09", + "short_name": "Strategy-by-Feedback Experiment", + "research_question": ( + "Does the effect of study strategy on assessment performance depend " + "on the feedback condition?" + ), + "design": "Balanced two-factor between-student factorial experiment.", + "unit_of_analysis": "student", + "row_structure": ( + "One row per student with exactly one valid strategy-by-feedback cell." + ), + "identifier_fields": ["participant_id"], + "factor_levels": { + "study_strategy": ["rereading", "retrieval_practice"], + "feedback_condition": ["no_feedback", "explanatory_feedback"], + }, + "outcome": "assessment_performance", + "assignment_procedure": ( + "Synthetic students are assigned in equal numbers to all four cells." + ), + "missingness_rule": ( + "The registered dataset contains all four cells with equal counts and " + "no missing outcomes." + ), + "causal_scope": ( + "Causal interpretation is conditional on random assignment, " + "implementation fidelity, and the defined factor levels." + ), + "limitation": ( + "The interaction is specific to the registered strategy and feedback " + "levels; main effects alone would obscure that conditional pattern." + ), + "figure_type": "factorial_interaction", + "apa_fields": [ + "interaction_f", + "df_effect", + "df_within", + "interaction_p", + "interaction_partial_eta_squared", + "interaction_difference_in_differences", + "simple_strategy_no_feedback_estimate", + "simple_strategy_no_feedback_t", + "simple_strategy_no_feedback_p", + "simple_strategy_explanatory_feedback_estimate", + "simple_strategy_explanatory_feedback_t", + "simple_strategy_explanatory_feedback_p", + ], + }, +} + + +def data_filename(study_id: str) -> str: + return f"{STUDY_CONFIG[study_id]['slug']}.csv" + + +def build_design_contract(study_id: str) -> dict[str, Any]: + config = STUDY_CONFIG[study_id] + return { + "contract_version": "0.1", + "batch_id": BATCH_ID, + "study_id": study_id, + "short_name": config["short_name"], + "chapter_id": config["chapter_id"], + "synthetic_only": True, + "research_question": config["research_question"], + "design": config["design"], + "unit_of_analysis": config["unit_of_analysis"], + "row_structure": config["row_structure"], + "identifier_fields": config["identifier_fields"], + "factor_levels": config["factor_levels"], + "outcome": config["outcome"], + "assignment_procedure": config["assignment_procedure"], + "missingness_rule": config["missingness_rule"], + "causal_scope": config["causal_scope"], + "data_file": f"data/{data_filename(study_id)}", + "python_analysis": "scripts/python/studies.py", + "r_verification": f"scripts/r/{study_id.lower().replace('-', '_')}.R", + "result_receipt": f"evidence/{study_id}/PYTHON_RESULT_RECEIPT.json", + "figure_spec": f"evidence/{study_id}/FIGURE_SPEC.json", + "apa_source_map": f"evidence/{study_id}/APA_RESULT_SOURCE_MAP.json", + "matched_limitation": f"evidence/{study_id}/MATCHED_LIMITATION.json", + } + + +def build_figure_spec(study_id: str) -> dict[str, Any]: + config = STUDY_CONFIG[study_id] + return { + "contract_version": "0.1", + "batch_id": BATCH_ID, + "study_id": study_id, + "figure_id": f"{study_id}-FIG-01", + "figure_type": config["figure_type"], + "data_file": f"data/{data_filename(study_id)}", + "source_script": "scripts/python/generate_figures.py", + "grayscale_required": True, + "minimum_dpi": 300, + "output_role": "generated_build_instance_not_tracked", + "caption_scope": ( + f"Synthetic {config['short_name']} display; interpretation remains " + "bounded by the matched limitation." + ), + } + + +def build_limitation(study_id: str) -> dict[str, Any]: + config = STUDY_CONFIG[study_id] + return { + "contract_version": "0.1", + "batch_id": BATCH_ID, + "study_id": study_id, + "design": config["design"], + "causal_scope": config["causal_scope"], + "matched_limitation": config["limitation"], + "must_accompany_apa_result": True, + "synthetic_only": True, + } + + +def build_apa_map( + study_id: str, + receipt_sha256: str, +) -> dict[str, Any]: + config = STUDY_CONFIG[study_id] + return { + "contract_version": "0.1", + "batch_id": BATCH_ID, + "study_id": study_id, + "receipt_path": f"evidence/{study_id}/PYTHON_RESULT_RECEIPT.json", + "receipt_sha256": receipt_sha256, + "reported_number_bindings": [ + { + "reporting_role": field, + "json_pointer": f"/reported_fields/{field}", + } + for field in config["apa_fields"] + ], + "limitation_path": f"evidence/{study_id}/MATCHED_LIMITATION.json", + "source_mapping_complete": True, + "prose_is_generated_from_stable_fields": False, + "policy": ( + "Book prose must be written from these stable fields and rechecked " + "after any dataset or analysis change." + ), + } + + +def _validate_common(df: pd.DataFrame, study_id: str) -> None: + if df.empty: + raise ValueError(f"{study_id}: dataset is empty") + if set(df["study_id"]) != {study_id}: + raise ValueError(f"{study_id}: incorrect study_id binding") + if df["participant_id"].isna().any(): + raise ValueError(f"{study_id}: missing participant identifier") + + +def validate_dataset(study_id: str, df: pd.DataFrame) -> None: + _validate_common(df, study_id) + config = STUDY_CONFIG[study_id] + outcome = config["outcome"] + if outcome not in df: + raise ValueError(f"{study_id}: missing outcome {outcome}") + if df[outcome].isna().any(): + raise ValueError(f"{study_id}: missing outcomes are not allowed") + if study_id != "SWL-S03" and df["participant_id"].duplicated().any(): + raise ValueError(f"{study_id}: duplicate participant identifiers") + for factor, expected_levels in config["factor_levels"].items(): + actual_levels = sorted(df[factor].astype(str).unique().tolist()) + if actual_levels != sorted(expected_levels): + raise ValueError( + f"{study_id}: factor levels for {factor} are {actual_levels}, " + f"expected {sorted(expected_levels)}" + ) + if study_id == "SWL-S02": + counts = df.groupby("study_routine_group").size().to_dict() + if counts != {"standard_routine": 32, "structured_routine": 32}: + raise ValueError(f"SWL-S02: invalid group counts {counts}") + elif study_id == "SWL-S03": + if df.duplicated(["participant_id", "occasion"]).any(): + raise ValueError("SWL-S03: duplicate participant-occasion rows") + counts = df.groupby("participant_id")["occasion"].nunique() + if len(counts) != 48 or not (counts == 2).all(): + raise ValueError("SWL-S03: every participant must have pre and post") + occasions = df.groupby("participant_id")["occasion"].apply(set) + if not occasions.apply(lambda value: value == {"pre", "post"}).all(): + raise ValueError("SWL-S03: invalid occasion pairing") + elif study_id == "SWL-S04": + counts = df.groupby("support_condition").size().to_dict() + expected = { + "guided_practice": 30, + "guided_practice_plus_feedback": 30, + "standard_support": 30, + } + if counts != expected: + raise ValueError(f"SWL-S04: invalid condition counts {counts}") + elif study_id == "SWL-S05": + counts = ( + df.groupby(["study_strategy", "feedback_condition"]) + .size() + .to_dict() + ) + expected = { + ("rereading", "explanatory_feedback"): 24, + ("rereading", "no_feedback"): 24, + ("retrieval_practice", "explanatory_feedback"): 24, + ("retrieval_practice", "no_feedback"): 24, + } + if counts != expected: + raise ValueError(f"SWL-S05: invalid factorial cell counts {counts}") + + +def _write_dataset(path: Path, df: pd.DataFrame) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + csv_text = df.to_csv(index=False, lineterminator="\n") + path.write_bytes(csv_text.encode("utf-8")) + + +def generate_tracked_assets(output_root: Path, source_root: Path) -> list[Path]: + output_root = output_root.resolve() + source_root = source_root.resolve() + generated: list[Path] = [] + studies_source = source_root / "scripts" / "python" / "studies.py" + for study_id in STUDY_IDS: + contract_path = output_root / "contracts" / f"{study_id}_DESIGN_CONTRACT.json" + write_json(contract_path, build_design_contract(study_id)) + generated.append(contract_path) + + dataset_path = output_root / "data" / data_filename(study_id) + dataset = GENERATORS[study_id]() + validate_dataset(study_id, dataset) + _write_dataset(dataset_path, dataset) + generated.append(dataset_path) + + reported_fields = ANALYZERS[study_id](dataset) + receipt_path = output_root / "evidence" / study_id / "PYTHON_RESULT_RECEIPT.json" + receipt = { + "contract_version": "0.1", + "batch_id": BATCH_ID, + "analysis_version": ANALYSIS_VERSION, + "study_id": study_id, + "synthetic_only": True, + "data_file": f"data/{data_filename(study_id)}", + "data_sha256": sha256(dataset_path), + "design_contract": f"contracts/{study_id}_DESIGN_CONTRACT.json", + "design_contract_sha256": sha256(contract_path), + "analysis_source": "scripts/python/studies.py", + "analysis_source_sha256": sha256(studies_source), + "engine": "numpy/scipy transparent analysis", + "reported_fields": reported_fields, + } + write_json(receipt_path, receipt) + generated.append(receipt_path) + + limitation_path = output_root / "evidence" / study_id / "MATCHED_LIMITATION.json" + write_json(limitation_path, build_limitation(study_id)) + generated.append(limitation_path) + + figure_path = output_root / "evidence" / study_id / "FIGURE_SPEC.json" + write_json(figure_path, build_figure_spec(study_id)) + generated.append(figure_path) + + apa_path = output_root / "evidence" / study_id / "APA_RESULT_SOURCE_MAP.json" + write_json(apa_path, build_apa_map(study_id, sha256(receipt_path))) + generated.append(apa_path) + + r_contract_path = output_root / "evidence" / "PYTHON_R_VERIFICATION_CONTRACT.json" + r_contract = { + "contract_version": "0.1", + "batch_id": BATCH_ID, + "python_engine": "numpy/scipy transparent analysis", + "r_engine": "base R stats", + "tolerance": 1e-7, + "independent_path_required": True, + "runtime_outputs_tracked": False, + "runtime_output_policy": "R results and parity receipts are ignored build-instance evidence.", + "studies": [ + { + "study_id": study_id, + "r_script": f"scripts/r/{study_id.lower().replace('-', '_')}.R", + "parity_fields": sorted(ANALYZERS[study_id](GENERATORS[study_id]()).keys()), + } + for study_id in STUDY_IDS + ], + } + write_json(r_contract_path, r_contract) + generated.append(r_contract_path) + + implementation_path = output_root / "evidence" / "BATCH_IMPLEMENTATION_RECEIPT.json" + implementation = { + "contract_version": "0.1", + "batch_id": BATCH_ID, + "baseline_source_commit": BASELINE_SOURCE_COMMIT, + "book_handoff_commit": BOOK_HANDOFF_COMMIT, + "synthetic_only": True, + "public_release": False, + "study_ids": list(STUDY_IDS), + "all_study_specific_asset_sets_complete": True, + "dimensions": { + "dataset_present": True, + "design_contract_present": True, + "python_analysis_present": True, + "r_verification_path_present": True, + "result_receipt_present": True, + "figure_source_present": True, + "apa_source_map_present": True, + "limitations_present": True, + "exact_regeneration_status": "passed", + }, + "r_parity_status_role": "required_green_local_and_ci_gate_not_tracked_runtime_output", + "book_drafting_unblock_role": "eligible_after_green_merge_and_explicit_new_commit_anchor", + } + write_json(implementation_path, implementation) + generated.append(implementation_path) + + handoff_path = output_root / "evidence" / "BOOK_INTEGRATION_HANDOFF.json" + handoff = { + "contract_version": "0.1", + "batch_id": BATCH_ID, + "status": "ready_after_green_merge", + "candidate_tag": "psych-design-swl-s02-s05-v0-1", + "book_baseline_commit": BOOK_HANDOFF_COMMIT, + "current_book_source_anchor": BASELINE_SOURCE_COMMIT, + "new_source_commit": "resolve_from_the_merged_candidate_tag", + "unblocked_study_ids": list(STUDY_IDS), + "unblocked_chapter_ids": [ + STUDY_CONFIG[study_id]["chapter_id"] for study_id in STUDY_IDS + ], + "required_book_actions": [ + "record the exact merged PyStatsV1 commit", + "update the book source anchor explicitly", + "regenerate the exact source inventory", + "rerun the ten-study readiness matrix", + "authorize the Chapters 6-9 drafting batch only after those gates pass", + ], + "public_release_authorized": False, + "portal_change_authorized": False, + } + write_json(handoff_path, handoff) + generated.append(handoff_path) + + relative_hashes = { + path.relative_to(output_root).as_posix(): sha256(path) + for path in sorted(generated) + } + receipt_path = output_root / "evidence" / "BATCH_EXACT_REGENERATION_RECEIPT.json" + source_input_paths = ( + "PSYCH_DESIGN_SWL_S02_S05_CONTRACT.json", + "SOURCE_PROVENANCE.json", + "METHOD_SOURCE_BINDING.json", + "scripts/python/studies.py", + "scripts/python/run_all.py", + "scripts/python/compare_r_results.py", + "scripts/python/generate_figures.py", + "scripts/r/common.R", + "scripts/r/run_all.R", + "scripts/r/swl_s02.R", + "scripts/r/swl_s03.R", + "scripts/r/swl_s04.R", + "scripts/r/swl_s05.R", + ) + source_input_hashes = { + path: sha256(source_root / path) for path in source_input_paths + } + receipt = { + "contract_version": "0.1", + "batch_id": BATCH_ID, + "baseline_source_commit": BASELINE_SOURCE_COMMIT, + "book_handoff_commit": BOOK_HANDOFF_COMMIT, + "synthetic_only": True, + "generated_file_count": len(relative_hashes), + "generated_file_sha256": relative_hashes, + "source_input_sha256": source_input_hashes, + "exact_regeneration_status": "passed", + "public_release": False, + } + write_json(receipt_path, receipt) + generated.append(receipt_path) + return generated + + +def load_tracked_dataset(root: Path, study_id: str) -> pd.DataFrame: + return pd.read_csv(root / "data" / data_filename(study_id)) + + +def write_runtime_python_results(root: Path, output_root: Path) -> list[Path]: + written: list[Path] = [] + for study_id in STUDY_IDS: + dataset = load_tracked_dataset(root, study_id) + validate_dataset(study_id, dataset) + payload = { + "contract_version": "0.1", + "batch_id": BATCH_ID, + "study_id": study_id, + "synthetic_only": True, + "reported_fields": ANALYZERS[study_id](dataset), + } + path = output_root / study_id / "py_results.json" + write_json(path, payload) + written.append(path) + return written diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/analysis_batches/batch_swl_s08_s10.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/analysis_batches/batch_swl_s08_s10.py new file mode 100755 index 0000000..f3c7185 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/analysis_batches/batch_swl_s08_s10.py @@ -0,0 +1,967 @@ +from __future__ import annotations + +import hashlib +import json +import math +from pathlib import Path +from typing import Any, Callable + +import numpy as np +import pandas as pd +from scipy import stats +import statsmodels.api as sm +from statsmodels.regression.mixed_linear_model import MixedLM + +BATCH_ID = "psych-design-swl-s08-s10-v0.1" +BASELINE_SOURCE_COMMIT = "bb618a4c8c8186df43823b33bf2fab3e5d42330a" +BOOK_HANDOFF_COMMIT = "2d8532a62e7b4f1ded957325426ac6ed16a69e06" +STUDY_IDS = ("SWL-S08", "SWL-S09", "SWL-S10") + +DATA_FILES = { + "SWL-S08": "swl_s08_longitudinal_treatment.csv", + "SWL-S09": "swl_s09_outcome_distribution_extension.csv", + "SWL-S10": "swl_s10_replication_program.csv", +} + + +def stable_number(value: Any) -> float | int: + if isinstance(value, (int, np.integer)): + return int(value) + numeric = float(value) + if not math.isfinite(numeric): + raise ValueError(f"non-finite result: {value!r}") + if abs(numeric - round(numeric)) < 1e-12: + return int(round(numeric)) + return float(f"{numeric:.10g}") + + +def sha256(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def write_json(path: Path, payload: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text( + json.dumps(payload, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + newline="\n", + ) + + +def dataset_path(root: Path, study_id: str) -> Path: + return root / "data" / DATA_FILES[study_id] + + +# --------------------------------------------------------------------------- +# SWL-S08: longitudinal treatment study with unequal observed visits +# --------------------------------------------------------------------------- + +S08_OCCASIONS = ("baseline", "post", "followup") +S08_GROUPS = ("control", "treatment") +S08_TARGET_MEANS = { + ("control", "baseline"): 50.0, + ("control", "post"): 51.0, + ("control", "followup"): 51.5, + ("treatment", "baseline"): 50.4, + ("treatment", "post"): 57.4, + ("treatment", "followup"): 59.9, +} +S08_MISSING_VISITS = { + "control": {"post": {4, 9}, "followup": {3, 8, 13, 18, 23}}, + "treatment": {"post": {7, 18}, "followup": {5, 11, 17}}, +} + + +def _s08_participant_offsets(treatment: int) -> list[float]: + values = [ + 5.0 * math.sin(i * 1.713 + treatment * 0.41) + + 1.4 * math.cos(i * 0.931 - treatment * 0.22) + for i in range(1, 31) + ] + mean_value = sum(values) / len(values) + return [value - mean_value for value in values] + + +def generate_swl_s08() -> pd.DataFrame: + rows: list[dict[str, Any]] = [] + for treatment, group in ((0, "control"), (1, "treatment")): + offsets = _s08_participant_offsets(treatment) + for number in range(1, 31): + participant_id = f"S08-{group[0].upper()}{number:03d}" + for occasion_order, occasion in enumerate(S08_OCCASIONS): + if number in S08_MISSING_VISITS[group].get(occasion, set()): + continue + post = int(occasion == "post") + followup = int(occasion == "followup") + base = ( + 50.0 + + 0.4 * treatment + + 1.0 * post + + 1.5 * followup + + 6.0 * treatment * post + + 8.0 * treatment * followup + ) + residual = ( + 3.2 + * math.sin( + number * 2.137 + + occasion_order * 1.117 + + treatment * 0.577 + ) + + 0.9 + * math.cos( + number * 0.733 + + occasion_order * 2.419 + - treatment * 0.31 + ) + ) + rows.append( + { + "study_id": "SWL-S08", + "participant_id": participant_id, + "treatment_group": group, + "occasion": occasion, + "occasion_order": occasion_order, + "treatment": treatment, + "post": post, + "followup": followup, + "treatment_post": treatment * post, + "treatment_followup": treatment * followup, + "well_being_score": base + offsets[number - 1] + residual, + } + ) + frame = pd.DataFrame(rows) + for key, target in S08_TARGET_MEANS.items(): + mask = ( + (frame["treatment_group"] == key[0]) + & (frame["occasion"] == key[1]) + ) + frame.loc[mask, "well_being_score"] += ( + target - frame.loc[mask, "well_being_score"].mean() + ) + frame["well_being_score"] = frame["well_being_score"].round(6) + return frame.sort_values( + ["treatment_group", "participant_id", "occasion_order"], + kind="stable", + ).reset_index(drop=True) + + +def _fit_s08(frame: pd.DataFrame): + exog = sm.add_constant( + frame[ + [ + "treatment", + "post", + "followup", + "treatment_post", + "treatment_followup", + ] + ], + has_constant="add", + ) + exog_re = pd.DataFrame( + {"random_intercept": np.ones(len(frame), dtype=float)}, + index=frame.index, + ) + model = MixedLM( + endog=frame["well_being_score"], + exog=exog, + groups=frame["participant_id"], + exog_re=exog_re, + ) + return model.fit(reml=False, method="lbfgs", maxiter=500, disp=False) + + +def analyze_swl_s08(frame: pd.DataFrame) -> dict[str, float | int]: + result = _fit_s08(frame) + confidence = result.conf_int() + visits = frame.groupby("participant_id").size() + values: dict[str, float | int] = { + "n_participants": frame["participant_id"].nunique(), + "n_control": frame.loc[ + frame["treatment_group"] == "control", "participant_id" + ].nunique(), + "n_treatment": frame.loc[ + frame["treatment_group"] == "treatment", "participant_id" + ].nunique(), + "scheduled_rows": 180, + "observed_rows": len(frame), + "missing_visits": 180 - len(frame), + "complete_participants": int((visits == 3).sum()), + "incomplete_participants": int((visits == 2).sum()), + "control_baseline_mean": frame.loc[ + (frame["treatment_group"] == "control") + & (frame["occasion"] == "baseline"), + "well_being_score", + ].mean(), + "control_post_mean": frame.loc[ + (frame["treatment_group"] == "control") + & (frame["occasion"] == "post"), + "well_being_score", + ].mean(), + "control_followup_mean": frame.loc[ + (frame["treatment_group"] == "control") + & (frame["occasion"] == "followup"), + "well_being_score", + ].mean(), + "treatment_baseline_mean": frame.loc[ + (frame["treatment_group"] == "treatment") + & (frame["occasion"] == "baseline"), + "well_being_score", + ].mean(), + "treatment_post_mean": frame.loc[ + (frame["treatment_group"] == "treatment") + & (frame["occasion"] == "post"), + "well_being_score", + ].mean(), + "treatment_followup_mean": frame.loc[ + (frame["treatment_group"] == "treatment") + & (frame["occasion"] == "followup"), + "well_being_score", + ].mean(), + } + terms = { + "const": "intercept", + "treatment": "treatment", + "post": "post", + "followup": "followup", + "treatment_post": "treatment_post", + "treatment_followup": "treatment_followup", + } + for raw, name in terms.items(): + b = float(result.fe_params[raw]) + se = float(result.bse_fe[raw]) + values[f"{name}_b"] = b + values[f"{name}_se"] = se + values[f"{name}_z"] = b / se + values[f"{name}_p"] = float(result.pvalues[raw]) + values[f"{name}_ci_95_low"] = float(confidence.loc[raw, 0]) + values[f"{name}_ci_95_high"] = float(confidence.loc[raw, 1]) + values.update( + { + "random_intercept_variance": float(result.cov_re.iloc[0, 0]), + "residual_variance": float(result.scale), + "log_likelihood": float(result.llf), + "aic": float(result.aic), + "bic": float(result.bic), + "converged": int(bool(result.converged)), + } + ) + return {key: stable_number(value) for key, value in values.items()} + + +# --------------------------------------------------------------------------- +# SWL-S09: categorical and positive-skew outcome extension +# --------------------------------------------------------------------------- + + +def _s09_burden(index: int, intervention: bool) -> float: + # Modular ordering yields a reproducible, positive-skewed distribution + # without random-number-generator or library-version dependence. + order = ((index * 37 + (11 if intervention else 0)) % 80) + 1 + base = math.exp(order / 22.0) + ripple = ((index * 7) % 13) * 0.017 + index * 0.0003 + if intervention: + value = 2.3 + 0.68 * base + ripple + else: + value = 4.1 + base + ripple + return round(value, 3) + + +def generate_swl_s09() -> pd.DataFrame: + rows: list[dict[str, Any]] = [] + improved_counts = {"comparison": 36, "intervention": 56} + for group in ("comparison", "intervention"): + intervention = group == "intervention" + for index in range(80): + # A co-prime permutation keeps outcome labels distributed across + # the ordered participant identifiers rather than clustered. + improved = ((index * 29) % 80) < improved_counts[group] + rows.append( + { + "study_id": "SWL-S09", + "participant_id": f"S09-{len(rows) + 1:03d}", + "support_condition": group, + "clinically_improved": "yes" if improved else "no", + "recovery_burden_days": _s09_burden(index, intervention), + } + ) + return pd.DataFrame(rows) + + +def _mann_whitney_no_continuity( + intervention_values: np.ndarray, + comparison_values: np.ndarray, +) -> tuple[float, float, float, float]: + n1 = len(intervention_values) + n2 = len(comparison_values) + combined = np.concatenate([intervention_values, comparison_values]) + ranks = stats.rankdata(combined, method="average") + u1 = float(ranks[:n1].sum() - n1 * (n1 + 1) / 2) + _, tie_counts = np.unique(combined, return_counts=True) + tie_sum = float(np.sum(tie_counts**3 - tie_counts)) + n = n1 + n2 + variance = n1 * n2 / 12.0 * ( + (n + 1.0) - tie_sum / (n * (n - 1.0)) + ) + expected = n1 * n2 / 2.0 + z = (u1 - expected) / math.sqrt(variance) + p = float(2.0 * stats.norm.sf(abs(z))) + rank_biserial_lower = 1.0 - 2.0 * u1 / (n1 * n2) + return u1, z, p, rank_biserial_lower + + +def analyze_swl_s09(frame: pd.DataFrame) -> dict[str, float | int]: + table = pd.crosstab( + frame["support_condition"], frame["clinically_improved"] + ).reindex(index=["comparison", "intervention"], columns=["no", "yes"]) + observed = table.to_numpy(dtype=float) + chi_square, chi_p, chi_df, expected = stats.chi2_contingency( + observed, correction=False + ) + n = int(observed.sum()) + comparison = frame.loc[ + frame["support_condition"] == "comparison", "recovery_burden_days" + ].to_numpy(float) + intervention = frame.loc[ + frame["support_condition"] == "intervention", "recovery_burden_days" + ].to_numpy(float) + u, z, rank_p, rank_biserial = _mann_whitney_no_continuity( + intervention, comparison + ) + values: dict[str, float | int] = { + "n_total": n, + "n_comparison": len(comparison), + "n_intervention": len(intervention), + "comparison_improved": int(table.loc["comparison", "yes"]), + "comparison_not_improved": int(table.loc["comparison", "no"]), + "intervention_improved": int(table.loc["intervention", "yes"]), + "intervention_not_improved": int(table.loc["intervention", "no"]), + "comparison_improved_proportion": table.loc["comparison", "yes"] + / len(comparison), + "intervention_improved_proportion": table.loc[ + "intervention", "yes" + ] + / len(intervention), + "improved_risk_difference_intervention_minus_comparison": ( + table.loc["intervention", "yes"] / len(intervention) + - table.loc["comparison", "yes"] / len(comparison) + ), + "chi_square": chi_square, + "chi_square_df": chi_df, + "chi_square_p": chi_p, + "cramers_v": math.sqrt(chi_square / n), + "minimum_expected_cell_count": expected.min(), + "comparison_burden_median": np.median(comparison), + "intervention_burden_median": np.median(intervention), + "burden_median_difference_intervention_minus_comparison": ( + np.median(intervention) - np.median(comparison) + ), + "mann_whitney_u_intervention": u, + "mann_whitney_z_no_continuity": z, + "mann_whitney_p_two_sided": rank_p, + "rank_biserial_intervention_lower": rank_biserial, + "comparison_burden_skewness": stats.skew(comparison, bias=False), + "intervention_burden_skewness": stats.skew(intervention, bias=False), + "comparison_burden_min": comparison.min(), + "comparison_burden_max": comparison.max(), + "intervention_burden_min": intervention.min(), + "intervention_burden_max": intervention.max(), + } + return {key: stable_number(value) for key, value in values.items()} + + +# --------------------------------------------------------------------------- +# SWL-S10: prospective power planning and cumulative evidence +# --------------------------------------------------------------------------- + + +def generate_swl_s10() -> pd.DataFrame: + rows = [ + ("S10-R01", 70, 0.28, "direct"), + ("S10-R02", 85, 0.42, "direct"), + ("S10-R03", 60, 0.35, "direct"), + ("S10-R04", 100, 0.51, "conceptual"), + ("S10-R05", 75, 0.31, "direct"), + ("S10-R06", 90, 0.46, "conceptual"), + ] + payload: list[dict[str, Any]] = [] + for study_id, n_per_group, observed_d, replication_type in rows: + variance = (2.0 / n_per_group) + ( + observed_d**2 / (2.0 * (n_per_group - 2)) + ) + payload.append( + { + "study_id": study_id, + "program_id": "SWL-S10", + "replication_type": replication_type, + "registered_before_analysis": "yes", + "n_per_group": n_per_group, + "total_n": n_per_group * 2, + "observed_cohens_d": observed_d, + "sampling_variance_d": round(variance, 10), + } + ) + return pd.DataFrame(payload) + + +def _power_n_per_group( + effect_size: float, + power: float, + alpha: float = 0.05, +) -> float: + z_alpha = float(stats.norm.ppf(1.0 - alpha / 2.0)) + z_power = float(stats.norm.ppf(power)) + return 2.0 * ((z_alpha + z_power) / effect_size) ** 2 + + +def analyze_swl_s10(frame: pd.DataFrame) -> dict[str, float | int]: + d = frame["observed_cohens_d"].to_numpy(float) + var_d = frame["sampling_variance_d"].to_numpy(float) + weights = 1.0 / var_d + pooled = float(np.sum(weights * d) / np.sum(weights)) + se = float(np.sqrt(1.0 / np.sum(weights))) + z = pooled / se + p = float(2.0 * stats.norm.sf(abs(z))) + ci_low = pooled - 1.96 * se + ci_high = pooled + 1.96 * se + q = float(np.sum(weights * np.square(d - pooled))) + df_q = len(d) - 1 + p_q = float(stats.chi2.sf(q, df_q)) + i2 = max(0.0, (q - df_q) / q * 100.0) if q > 0 else 0.0 + n80 = _power_n_per_group(0.40, 0.80) + n90 = _power_n_per_group(0.40, 0.90) + values: dict[str, float | int] = { + "study_count": len(frame), + "total_participants": frame["total_n"].sum(), + "target_effect_size_d": 0.40, + "planning_alpha_two_sided": 0.05, + "planning_power_primary": 0.80, + "planning_n_per_group_unrounded_80": n80, + "planning_n_per_group_ceiling_80": math.ceil(n80), + "planning_power_sensitivity": 0.90, + "planning_n_per_group_unrounded_90": n90, + "planning_n_per_group_ceiling_90": math.ceil(n90), + "minimum_observed_d": d.min(), + "maximum_observed_d": d.max(), + "fixed_effect_pooled_d": pooled, + "fixed_effect_se": se, + "fixed_effect_z": z, + "fixed_effect_p": p, + "fixed_effect_ci_95_low": ci_low, + "fixed_effect_ci_95_high": ci_high, + "cochran_q": q, + "cochran_q_df": df_q, + "cochran_q_p": p_q, + "i_squared_percent": i2, + } + return {key: stable_number(value) for key, value in values.items()} + + +GENERATORS: dict[str, Callable[[], pd.DataFrame]] = { + "SWL-S08": generate_swl_s08, + "SWL-S09": generate_swl_s09, + "SWL-S10": generate_swl_s10, +} +ANALYZERS: dict[str, Callable[[pd.DataFrame], dict[str, float | int]]] = { + "SWL-S08": analyze_swl_s08, + "SWL-S09": analyze_swl_s09, + "SWL-S10": analyze_swl_s10, +} + + +def validate_dataset(study_id: str, frame: pd.DataFrame) -> None: + if study_id == "SWL-S08": + required = { + "study_id", + "participant_id", + "treatment_group", + "occasion", + "occasion_order", + "treatment", + "post", + "followup", + "treatment_post", + "treatment_followup", + "well_being_score", + } + if set(frame.columns) != required: + raise ValueError("SWL-S08 columns changed") + if len(frame) != 168 or frame["participant_id"].nunique() != 60: + raise ValueError("SWL-S08 dimensions changed") + if frame.duplicated(["participant_id", "occasion"]).any(): + raise ValueError("SWL-S08 duplicate participant/occasion key") + if (frame.groupby("participant_id")["treatment_group"].nunique() != 1).any(): + raise ValueError("SWL-S08 group drift") + if set(frame["occasion"]) != set(S08_OCCASIONS): + raise ValueError("SWL-S08 occasion levels changed") + visits = frame.groupby("participant_id").size() + if int((visits == 3).sum()) != 48 or int((visits == 2).sum()) != 12: + raise ValueError("SWL-S08 visit pattern changed") + elif study_id == "SWL-S09": + required = { + "study_id", + "participant_id", + "support_condition", + "clinically_improved", + "recovery_burden_days", + } + if set(frame.columns) != required: + raise ValueError("SWL-S09 columns changed") + if len(frame) != 160 or frame["participant_id"].nunique() != 160: + raise ValueError("SWL-S09 dimensions changed") + if set(frame["support_condition"]) != {"comparison", "intervention"}: + raise ValueError("SWL-S09 group levels changed") + if set(frame["clinically_improved"]) != {"yes", "no"}: + raise ValueError("SWL-S09 categorical outcome levels changed") + if frame.groupby("support_condition").size().to_dict() != { + "comparison": 80, + "intervention": 80, + }: + raise ValueError("SWL-S09 group balance changed") + if (frame["recovery_burden_days"] <= 0).any(): + raise ValueError("SWL-S09 burden outcome must be positive") + elif study_id == "SWL-S10": + required = { + "study_id", + "program_id", + "replication_type", + "registered_before_analysis", + "n_per_group", + "total_n", + "observed_cohens_d", + "sampling_variance_d", + } + if set(frame.columns) != required: + raise ValueError("SWL-S10 columns changed") + if len(frame) != 6 or frame["study_id"].nunique() != 6: + raise ValueError("SWL-S10 study count changed") + if set(frame["program_id"]) != {"SWL-S10"}: + raise ValueError("SWL-S10 program identity changed") + if set(frame["registered_before_analysis"]) != {"yes"}: + raise ValueError("SWL-S10 registration boundary changed") + if not np.all(frame["total_n"] == 2 * frame["n_per_group"]): + raise ValueError("SWL-S10 sample-size arithmetic changed") + if (frame["sampling_variance_d"] <= 0).any(): + raise ValueError("SWL-S10 variances must be positive") + else: + raise ValueError("unknown study: " + study_id) + + +def _study_contract(study_id: str) -> dict[str, Any]: + if study_id == "SWL-S08": + return { + "chapter_ids": ["ch14"], + "claim_boundary": ( + "The synthetic model demonstrates differential change under the " + "declared random-intercept specification; it is not evidence about " + "a real treatment." + ), + "contract_version": "0.1", + "data_layout": { + "format": "long", + "primary_key": ["participant_id", "occasion"], + "observed_rows": 168, + "participants": 60, + "scheduled_rows": 180, + "unequal_visit_counts_permitted": True, + }, + "dataset": f"data/{DATA_FILES[study_id]}", + "design": "two-group longitudinal study with three scheduled occasions", + "estimands": [ + "additional treatment-group change from baseline to post", + "additional treatment-group change from baseline to follow-up", + ], + "missingness_fixture": ( + "Twelve visit omissions are fixed by participant identifier before " + "outcome generation and do not depend on realized outcomes." + ), + "model": { + "engine": "statsmodels MixedLM", + "fixed_effects": ( + "well_being_score ~ treatment + post + followup + " + "treatment_post + treatment_followup" + ), + "random_effects": "random intercept for participant_id", + "estimation": "maximum likelihood", + }, + "outcome": "well_being_score", + "study_id": study_id, + "synthetic_only": True, + "verification_category": "V3 fixed-effect estimand alignment plus V4 inference documentation", + } + if study_id == "SWL-S09": + return { + "chapter_ids": ["ch15"], + "claim_boundary": ( + "The categorical and rank-based analyses answer different questions " + "about two registered outcomes; neither is a generic repair for a " + "failed normality test." + ), + "contract_version": "0.1", + "data_layout": { + "format": "one row per participant", + "primary_key": ["participant_id"], + "participants": 160, + "participants_per_condition": 80, + }, + "dataset": f"data/{DATA_FILES[study_id]}", + "design": "two independent support conditions with two registered outcomes", + "estimands": [ + "association between condition and binary clinical-improvement status", + "distributional shift in positive-skew recovery burden between conditions", + ], + "outcomes": { + "categorical": "clinically_improved", + "positive_skew_continuous": "recovery_burden_days", + }, + "study_id": study_id, + "synthetic_only": True, + "verification_category": "V2 numeric parity", + } + return { + "chapter_ids": ["ch16"], + "claim_boundary": ( + "Prospective power depends on a justified target effect and model; the " + "fixed-effect synthesis assumes one common true effect and does not " + "address publication bias or selective reporting." + ), + "contract_version": "0.1", + "data_layout": { + "format": "one row per completed synthetic replication", + "primary_key": ["study_id"], + "replication_count": 6, + }, + "dataset": f"data/{DATA_FILES[study_id]}", + "design": "registered replication program summarized at study level", + "estimands": [ + "prospective per-group sample size for d=0.40 at 80 percent power", + "fixed-effect pooled standardized mean difference across six replications", + ], + "study_id": study_id, + "synthetic_only": True, + "verification_category": "V2 numeric parity", + } + + +def _apa_maps(study_id: str, fields: dict[str, float | int]) -> list[tuple[str, dict[str, Any]]]: + common = { + "contract_version": "0.1", + "result_source": f"evidence/{study_id}/PYTHON_RESULT_RECEIPT.json", + "study_id": study_id, + "synthetic_only": True, + } + if study_id == "SWL-S08": + sentence = ( + "A maximum-likelihood random-intercept model indicated larger " + "treatment-group change from baseline to post, b = 5.86, SE = 0.87, " + "z = 6.74, p < .001, 95% CI [4.16, 7.57], and from baseline to " + "follow-up, b = 7.78, SE = 0.89, z = 8.70, p < .001, 95% CI " + "[6.03, 9.54]." + ) + names = [ + "treatment_post_b", + "treatment_post_se", + "treatment_post_z", + "treatment_post_p", + "treatment_post_ci_95_low", + "treatment_post_ci_95_high", + "treatment_followup_b", + "treatment_followup_se", + "treatment_followup_z", + "treatment_followup_p", + "treatment_followup_ci_95_low", + "treatment_followup_ci_95_high", + ] + payload = dict(common) + payload.update( + { + "analysis_role": "longitudinal_random_intercept_model", + "rendered_text": sentence, + "reported_number_bindings": [ + { + "field": name, + "json_pointer": f"/reported_fields/{name}", + } + for name in names + ], + } + ) + return [("APA_RESULT_SOURCE_MAP.json", payload)] + if study_id == "SWL-S09": + categorical_names = [ + "chi_square", + "chi_square_df", + "chi_square_p", + "cramers_v", + "comparison_improved_proportion", + "intervention_improved_proportion", + ] + rank_names = [ + "comparison_burden_median", + "intervention_burden_median", + "mann_whitney_u_intervention", + "mann_whitney_z_no_continuity", + "mann_whitney_p_two_sided", + "rank_biserial_intervention_lower", + ] + categorical = dict(common) + categorical.update( + { + "analysis_role": "categorical_improvement_status", + "rendered_text": ( + "Clinical-improvement status differed by support condition, " + "chi-square(1) = 10.23, p = .001, Cramer's V = .253; the " + "improved proportions were .45 in comparison and .70 in intervention." + ), + "reported_number_bindings": [ + {"field": name, "json_pointer": f"/reported_fields/{name}"} + for name in categorical_names + ], + } + ) + rank_map = dict(common) + rank_map.update( + { + "analysis_role": "positive_skew_recovery_burden", + "rendered_text": ( + "Recovery burden was lower in intervention (Mdn = 6.75) than " + "comparison (Mdn = 10.53), U = 1966.00, z = -4.21, p < .001, " + "rank-biserial effect = .386." + ), + "reported_number_bindings": [ + {"field": name, "json_pointer": f"/reported_fields/{name}"} + for name in rank_names + ], + } + ) + return [ + ("APA_RESULT_SOURCE_MAP_CATEGORICAL.json", categorical), + ("APA_RESULT_SOURCE_MAP_RANK.json", rank_map), + ] + power_names = [ + "target_effect_size_d", + "planning_power_primary", + "planning_alpha_two_sided", + "planning_n_per_group_ceiling_80", + ] + meta_names = [ + "study_count", + "fixed_effect_pooled_d", + "fixed_effect_se", + "fixed_effect_z", + "fixed_effect_p", + "fixed_effect_ci_95_low", + "fixed_effect_ci_95_high", + "cochran_q", + "cochran_q_df", + "cochran_q_p", + "i_squared_percent", + ] + power = dict(common) + power.update( + { + "analysis_role": "prospective_power_planning", + "rendered_text": ( + "Under the declared normal-approximation assumptions, detecting " + "d = 0.40 with 80% power and two-sided alpha = .05 required 99 " + "participants per group after rounding up." + ), + "reported_number_bindings": [ + {"field": name, "json_pointer": f"/reported_fields/{name}"} + for name in power_names + ], + } + ) + meta = dict(common) + meta.update( + { + "analysis_role": "fixed_effect_cumulative_evidence", + "rendered_text": ( + "Across six synthetic replications, the fixed-effect pooled estimate " + "was d = 0.40, SE = 0.07, z = 6.05, p < .001, 95% CI [0.27, 0.53], " + "with Q(5) = 1.60, p = .901, I-squared = 0%." + ), + "reported_number_bindings": [ + {"field": name, "json_pointer": f"/reported_fields/{name}"} + for name in meta_names + ], + } + ) + return [ + ("APA_RESULT_SOURCE_MAP_POWER.json", power), + ("APA_RESULT_SOURCE_MAP_META.json", meta), + ] + + +def _figure_specs(study_id: str) -> list[tuple[str, dict[str, Any]]]: + common = { + "contract_version": "0.1", + "grayscale": True, + "study_id": study_id, + "synthetic_only": True, + } + if study_id == "SWL-S08": + payload = dict(common) + payload.update( + { + "caption": "Observed well-being means by group and occasion.", + "generator": "scripts/python/generate_figures.py", + "output": "swl_s08_group_time_means.png", + "role": "descriptive_pattern_not_model_result", + "required_axis_labels": ["Occasion", "Well-being score"], + } + ) + return [("FIGURE_SPEC.json", payload)] + if study_id == "SWL-S09": + cat = dict(common) + cat.update( + { + "caption": "Improvement-status proportions by support condition.", + "generator": "scripts/python/generate_figures.py", + "output": "swl_s09_improvement_proportions.png", + "role": "categorical_outcome_descriptive", + "required_axis_labels": ["Support condition", "Proportion"], + } + ) + rank = dict(common) + rank.update( + { + "caption": "Positive-skew recovery burden by support condition.", + "generator": "scripts/python/generate_figures.py", + "output": "swl_s09_recovery_burden.png", + "role": "rank_analysis_distribution_display", + "required_axis_labels": ["Support condition", "Recovery burden (days)"], + } + ) + return [("FIGURE_SPEC_CATEGORICAL.json", cat), ("FIGURE_SPEC_RANK.json", rank)] + payload = dict(common) + payload.update( + { + "caption": "Study-level standardized effects and fixed-effect pooled estimate.", + "generator": "scripts/python/generate_figures.py", + "output": "swl_s10_fixed_effect_forest.png", + "role": "cumulative_evidence_forest_plot", + "required_axis_labels": ["Standardized mean difference", "Replication"], + } + ) + return [("FIGURE_SPEC.json", payload)] + + +def _limitations(study_id: str) -> list[tuple[str, dict[str, Any]]]: + common = { + "contract_version": "0.1", + "study_id": study_id, + "synthetic_only": True, + } + if study_id == "SWL-S08": + payload = dict(common) + payload.update( + { + "analysis_role": "longitudinal_random_intercept_model", + "limitation": ( + "The synthetic result validates the workflow rather than a " + "real-world treatment claim. Interpretation depends on the " + "declared coding, random-intercept specification, assignment, " + "measurement stability, and an ignorable-missingness assumption." + ), + } + ) + return [("MATCHED_LIMITATION.json", payload)] + if study_id == "SWL-S09": + categorical = dict(common) + categorical.update( + { + "analysis_role": "categorical_improvement_status", + "limitation": ( + "The chi-square result concerns the registered binary outcome and " + "does not recover information discarded when a continuous or " + "ordinal construct is dichotomized." + ), + } + ) + rank = dict(common) + rank.update( + { + "analysis_role": "positive_skew_recovery_burden", + "limitation": ( + "The rank-based test detects a distributional shift under the " + "independent-groups design; it is not automatically a test of means " + "or medians unless stronger shape assumptions are defended." + ), + } + ) + return [ + ("MATCHED_LIMITATION_CATEGORICAL.json", categorical), + ("MATCHED_LIMITATION_RANK.json", rank), + ] + power = dict(common) + power.update( + { + "analysis_role": "prospective_power_planning", + "limitation": ( + "The sample-size calculation is conditional on the chosen target effect, " + "two-sided alpha, equal allocation, independent observations, and the " + "normal approximation; power cannot repair invalid design or measurement." + ), + } + ) + meta = dict(common) + meta.update( + { + "analysis_role": "fixed_effect_cumulative_evidence", + "limitation": ( + "The fixed-effect synthesis assumes one common true effect and the six " + "synthetic replications are complete by construction; publication bias, " + "selective reporting, and meaningful heterogeneity are not ruled out." + ), + } + ) + return [("MATCHED_LIMITATION_POWER.json", power), ("MATCHED_LIMITATION_META.json", meta)] + + +def generate_all(root: Path) -> list[Path]: + generated: list[Path] = [] + for study_id in STUDY_IDS: + frame = GENERATORS[study_id]() + validate_dataset(study_id, frame) + data = dataset_path(root, study_id) + data.parent.mkdir(parents=True, exist_ok=True) + frame.to_csv(data, index=False, lineterminator="\n") + generated.append(data) + + contract = root / "contracts" / f"{study_id}_DESIGN_CONTRACT.json" + write_json(contract, _study_contract(study_id)) + generated.append(contract) + + fields = ANALYZERS[study_id](frame) + receipt = root / "evidence" / study_id / "PYTHON_RESULT_RECEIPT.json" + write_json( + receipt, + { + "analysis_version": "0.1", + "batch_id": BATCH_ID, + "contract_version": "0.1", + "dataset_sha256": sha256(data), + "design_contract_sha256": sha256(contract), + "python_analysis_sha256": sha256(Path(__file__)), + "reported_fields": fields, + "study_id": study_id, + "synthetic_only": True, + }, + ) + generated.append(receipt) + + for filename, payload in _apa_maps(study_id, fields): + path = root / "evidence" / study_id / filename + write_json(path, payload) + generated.append(path) + for filename, payload in _figure_specs(study_id): + path = root / "evidence" / study_id / filename + write_json(path, payload) + generated.append(path) + for filename, payload in _limitations(study_id): + path = root / "evidence" / study_id / filename + write_json(path, payload) + generated.append(path) + return generated diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/build_reviewer_packet.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/build_reviewer_packet.py new file mode 100755 index 0000000..3c04f02 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/build_reviewer_packet.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +"""Build a bounded, hash-manifested reviewer packet for one or all studies.""" +from __future__ import annotations + +import argparse +import json +import shutil +from pathlib import Path +from typing import Any + +from companion_runtime import ( + ROOT, + normalize_study_id, + sha256, + study_index, + write_json, +) + + +def _load_json(path: Path) -> dict[str, Any]: + return json.loads(path.read_text(encoding="utf-8")) + + +def _source_manifest_record(study_id: str, row: dict[str, Any]) -> dict[str, Any]: + lineage = _load_json(ROOT / "SOURCE_LINEAGE.json") + study_lineage = next( + item for item in lineage["studies"] if item["study_id"] == study_id + ) + return { + "schema_version": "psych-design-reviewer-source-manifest-record-v0.1", + "study_id": study_id, + "source_anchor": lineage["source_anchor"], + "source_batch": study_lineage["source_batch"], + "dataset_path": row["dataset_path"], + "dataset_sha256": sha256(ROOT / row["dataset_path"]), + "design_contract_path": row["design_contract_path"], + "design_contract_sha256": sha256(ROOT / row["design_contract_path"]), + "reference_python_result": row["reference_python_result"], + "reference_python_result_sha256": sha256( + ROOT / row["reference_python_result"] + ), + "lineage_origins": study_lineage["origins"], + "synthetic_only": True, + "real_data_authorized": False, + } + + +def _figure_spec_record(study_id: str, row: dict[str, Any]) -> dict[str, Any]: + records = [] + for relative in row["figure_specs"]: + path = ROOT / relative + records.append( + { + "path": relative, + "sha256": sha256(path), + "payload": _load_json(path), + } + ) + return { + "schema_version": "psych-design-reviewer-figure-spec-record-v0.1", + "study_id": study_id, + "specifications": records, + "synthetic_only": True, + } + + +def build(study_id: str) -> Path: + study_id = normalize_study_id(study_id) + row = study_index()[study_id] + slug = study_id.lower().replace("-", "_") + output = ROOT / "outputs" / slug + packet = output / "reviewer_packet" + + source_files = { + "DESIGN_CONTRACT.json": ROOT / row["design_contract_path"], + "MEASUREMENT_MAP.json": ROOT / row["measurement_map_path"], + "DATA_LAYOUT_AUDIT.json": output / "data_layout_audit.json", + "PYTHON_RESULT.json": output / "python_result.json", + "R_RESULT.csv": output / "r_result.csv", + "VERIFICATION_RECEIPT.json": output / "verification_receipt.json", + "FIGURE_MANIFEST.json": output / "figures" / "FIGURE_MANIFEST.json", + "APA_SOURCE_MAP.json": output / "apa_source_map.json", + "MATCHED_LIMITATION.md": output / "matched_limitation.md", + } + missing = [str(path) for path in source_files.values() if not path.is_file()] + if missing: + raise SystemExit( + "reviewer packet prerequisites missing:\n" + "\n".join(missing) + ) + + if packet.exists(): + shutil.rmtree(packet) + packet.mkdir(parents=True) + + write_json(packet / "STUDY_INDEX_RECORD.json", row) + write_json( + packet / "SOURCE_MANIFEST_RECORD.json", + _source_manifest_record(study_id, row), + ) + write_json(packet / "FIGURE_SPECS.json", _figure_spec_record(study_id, row)) + + for name, source in source_files.items(): + shutil.copyfile(source, packet / name) + + records = [] + for path in sorted(item for item in packet.iterdir() if item.is_file()): + records.append( + { + "path": path.name, + "bytes": path.stat().st_size, + "sha256": sha256(path), + } + ) + manifest = { + "schema_version": "psych-design-reviewer-packet-manifest-v0.1", + "study_id": study_id, + "files": records, + "file_count": len(records), + "synthetic_only": True, + "real_data_authorized": False, + } + write_json(packet / "MANIFEST.json", manifest) + print( + "PYSTATSV1_PSYCH_DESIGN_REVIEWER_PACKET_OK " + f"study={study_id} files={len(records)}" + ) + return packet / "MANIFEST.json" + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--study") + args = parser.parse_args() + study_ids = [args.study] if args.study else list(study_index()) + for study_id in study_ids: + build(study_id) + + +if __name__ == "__main__": + main() diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/companion_runtime.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/companion_runtime.py new file mode 100755 index 0000000..d5bee4d --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/companion_runtime.py @@ -0,0 +1,115 @@ +from __future__ import annotations +import csv +import hashlib +import importlib +import json +import math +import sys +from pathlib import Path +from typing import Any +import pandas as pd +ROOT = Path(__file__).resolve().parents[2] +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) +MODULES = {'SWL-S01': 'scripts.python.analysis_batches.batch_swl_s01_s06_s07', 'SWL-S06': 'scripts.python.analysis_batches.batch_swl_s01_s06_s07', 'SWL-S07': 'scripts.python.analysis_batches.batch_swl_s01_s06_s07', 'SWL-S02': 'scripts.python.analysis_batches.batch_swl_s02_s05', 'SWL-S03': 'scripts.python.analysis_batches.batch_swl_s02_s05', 'SWL-S04': 'scripts.python.analysis_batches.batch_swl_s02_s05', 'SWL-S05': 'scripts.python.analysis_batches.batch_swl_s02_s05', 'SWL-S08': 'scripts.python.analysis_batches.batch_swl_s08_s10', 'SWL-S09': 'scripts.python.analysis_batches.batch_swl_s08_s10', 'SWL-S10': 'scripts.python.analysis_batches.batch_swl_s08_s10'} + +def sha256(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + +def write_json(path: Path, payload: Any) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload, indent=2, sort_keys=True) + '\n', encoding='utf-8', newline='\n') + +def study_index() -> dict[str, dict[str, Any]]: + payload = json.loads((ROOT / 'STUDY_INDEX.json').read_text(encoding='utf-8')) + return {row['study_id']: row for row in payload['studies']} + +def normalize_study_id(value: str) -> str: + value = value.strip().upper().replace('_', '-') + if value not in MODULES: + raise ValueError(f'unknown study ID: {value}; expected SWL-S01 through SWL-S10') + return value + +def audit_frame(study_id: str, frame: pd.DataFrame, row: dict[str, Any]) -> dict[str, Any]: + key = row['unique_key'] + missing = [name for name in key if name not in frame.columns] + if missing: + raise ValueError(f'{study_id}: missing key columns {missing}') + duplicate_count = int(frame.duplicated(key).sum()) + if duplicate_count: + raise ValueError(f'{study_id}: duplicate key rows={duplicate_count}') + actual_levels = {} + for field, expected in row.get('factor_levels', {}).items(): + if field not in frame.columns: + raise ValueError(f'{study_id}: missing factor field {field}') + actual = sorted((str(value) for value in frame[field].dropna().unique())) + if set(actual) != set(expected): + raise ValueError(f'{study_id}: factor levels for {field} expected={expected} actual={actual}') + actual_levels[field] = actual + unit_key = key[0] + result = {'study_id': study_id, 'rows': int(len(frame)), 'unique_units': int(frame[unit_key].nunique()), 'unique_keys': int(frame.drop_duplicates(key).shape[0]), 'duplicate_key_rows': duplicate_count, 'factor_levels': actual_levels, 'missing_values_by_column': {name: int(value) for name, value in frame.isna().sum().items() if int(value)}} + for field in ('expected_rows', 'expected_unique_units', 'expected_unique_keys'): + actual_name = {'expected_rows': 'rows', 'expected_unique_units': 'unique_units', 'expected_unique_keys': 'unique_keys'}[field] + if result[actual_name] != int(row[field]): + raise ValueError(f'{study_id}: {actual_name} expected={row[field]} actual={result[actual_name]}') + return result + +def _compare_reference(study_id: str, fields: dict[str, Any], row: dict[str, Any]) -> None: + reference = json.loads((ROOT / row['reference_python_result']).read_text(encoding='utf-8'))['reported_fields'] + if set(fields) != set(reference): + raise ValueError(f'{study_id}: generated result field set differs from governed reference') + tolerance = 5e-06 if study_id == 'SWL-S08' else 1e-08 + failures = [] + for name, expected in reference.items(): + actual = fields[name] + if isinstance(expected, (int, float)) and isinstance(actual, (int, float)): + if not math.isclose(float(actual), float(expected), rel_tol=tolerance, abs_tol=tolerance): + failures.append(f'{name}: expected={expected} actual={actual}') + elif actual != expected: + failures.append(f'{name}: expected={expected!r} actual={actual!r}') + if failures: + raise ValueError(f'{study_id}: governed result drift\n' + '\n'.join(failures)) + +def run_registered_study(study_id: str) -> Path: + study_id = normalize_study_id(study_id) + row = study_index()[study_id] + dataset = ROOT / row['dataset_path'] + frame = pd.read_csv(dataset) + module = importlib.import_module(MODULES[study_id]) + module.validate_dataset(study_id, frame) + audit = audit_frame(study_id, frame, row) + fields = module.ANALYZERS[study_id](frame) + _compare_reference(study_id, fields, row) + slug = study_id.lower().replace('-', '_') + out = ROOT / 'outputs' / slug + result_path = out / 'python_result.json' + payload = {'schema_version': 'psych-design-runtime-python-result-v0.1', 'study_id': study_id, 'synthetic_only': True, 'companion_version': 'v0.1', 'package_release_candidate': '0.26.0', 'dataset_path': row['dataset_path'], 'dataset_sha256': sha256(dataset), 'design_contract_path': row['design_contract_path'], 'design_contract_sha256': sha256(ROOT / row['design_contract_path']), 'measurement_map_path': row['measurement_map_path'], 'measurement_map_sha256': sha256(ROOT / row['measurement_map_path']), 'analysis_entry_script': row['python_script'], 'analysis_entry_script_sha256': sha256(ROOT / row['python_script']), 'analysis_module': MODULES[study_id], 'analysis_module_sha256': sha256(Path(module.__file__)), 'data_layout_audit': audit, 'reported_fields': fields, 'main_prohibited_inference': row['main_prohibited_inference']} + write_json(result_path, payload) + write_json(out / 'data_layout_audit.json', audit) + maps = [] + for rel in row['apa_source_maps']: + maps.append({'path': rel, 'sha256': sha256(ROOT / rel), 'payload': json.loads((ROOT / rel).read_text(encoding='utf-8'))}) + write_json(out / 'apa_source_map.json', {'schema_version': 'psych-design-runtime-apa-source-map-v0.1', 'study_id': study_id, 'python_result': 'python_result.json', 'python_result_sha256': sha256(result_path), 'reference_maps': maps, 'matched_limitation': 'matched_limitation.md', 'synthetic_only': True}) + limitations = [] + for rel in row['matched_limitations']: + payload = json.loads((ROOT / rel).read_text(encoding='utf-8')) + limitations.append(payload.get('limitation', payload.get('text', 'Matched limitation is recorded in the governed source.'))) + (out / 'matched_limitation.md').write_text('\n\n'.join((f'- {text}' for text in limitations)) + '\n', encoding='utf-8', newline='\n') + print(f'PYSTATSV1_PSYCH_DESIGN_STUDY_OK study={study_id} result={result_path}') + return result_path + +def read_r_metrics(path: Path) -> dict[str, float]: + result = {} + with path.open(newline='', encoding='utf-8') as handle: + rows = csv.DictReader(handle) + if rows.fieldnames != ['metric', 'value']: + raise ValueError(f'{path}: expected metric,value columns') + for row in rows: + name = (row.get('metric') or '').strip() + if not name or name in result: + raise ValueError(f'{path}: missing or duplicate metric {name!r}') + value = float(row['value']) + if not math.isfinite(value): + raise ValueError(f'{path}: non-finite metric {name}') + result[name] = value + return result diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/design_audit.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/design_audit.py new file mode 100755 index 0000000..9cd4ec3 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/design_audit.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +from __future__ import annotations +import argparse +import pandas as pd +from companion_runtime import ROOT, audit_frame, study_index, write_json +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--check-only', action='store_true') + args = parser.parse_args() + receipts = [] + for sid, row in study_index().items(): + frame = pd.read_csv(ROOT / row['dataset_path']) + receipts.append(audit_frame(sid, frame, row)) + if not args.check_only: + write_json(ROOT / 'outputs/design_audit.json', {'schema_version': 'psych-design-data-layout-audit-v0.1', 'studies': receipts, 'synthetic_only': True}) + print('PYSTATSV1_PSYCH_DESIGN_DESIGN_AUDIT_OK studies=10') diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/generate_figures.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/generate_figures.py new file mode 100755 index 0000000..35d4d4d --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/generate_figures.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +from __future__ import annotations +import argparse +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +from companion_runtime import ROOT, normalize_study_id, sha256, study_index, write_json +from scripts.python.analysis_batches.batch_swl_s01_s06_s07 import analyze_swl_s01, analyze_swl_s07 +from scripts.python.analysis_batches.batch_swl_s08_s10 import analyze_swl_s10 + +def save(fig, path): + path.parent.mkdir(parents=True, exist_ok=True) + fig.tight_layout() + fig.savefig(path, dpi=300) + plt.close(fig) + +def mean_ci(values): + return (float(values.mean()), float(1.96 * values.std(ddof=1) / np.sqrt(len(values)))) + +def build(sid): + row = study_index()[sid] + frame = pd.read_csv(ROOT / row['dataset_path']) + out = ROOT / 'outputs' / sid.lower().replace('-', '_') / 'figures' + rows = [] + + def record(name, role): + rows.append({'study_id': sid, 'output': name, 'role': role, 'sha256': sha256(out / name), 'dpi': 300, 'grayscale': True}) + if sid == 'SWL-S01': + x = frame.sleep_quality.to_numpy(float) + y = frame.academic_confidence.to_numpy(float) + slope, inter = np.polyfit(x, y, 1) + grid = np.linspace(x.min(), x.max(), 100) + fig, ax = plt.subplots(figsize=(7.2, 4.8)) + ax.scatter(x, y, facecolors='none', edgecolors='black') + ax.plot(grid, inter + slope * grid, color='black') + ax.set(xlabel='Sleep quality', ylabel='Academic confidence', title='SWL-S01: Sleep quality and academic confidence') + save(fig, out / 'swl_s01_correlation.png') + record('swl_s01_correlation.png', 'correlation') + v = analyze_swl_s01(frame) + X = np.column_stack([np.ones(len(frame)), frame.study_hours_per_week, frame.academic_confidence]) + beta = np.array([v['intercept_b'], v['study_hours_b'], v['confidence_b']]) + fitted = X @ beta + resid = frame.assessment_performance.to_numpy(float) - fitted + fig, ax = plt.subplots(figsize=(7.2, 4.8)) + ax.axhline(0, color='black', linestyle='--') + ax.scatter(fitted, resid, facecolors='none', edgecolors='black', marker='s') + ax.set(xlabel='Fitted assessment performance', ylabel='Residual', title='SWL-S01: Registered prediction-model residuals') + save(fig, out / 'swl_s01_regression_residuals.png') + record('swl_s01_regression_residuals.png', 'regression') + elif sid == 'SWL-S02': + levels = ['standard_routine', 'structured_routine'] + stats = [mean_ci(frame.loc[frame.study_routine_group == x, 'post_session_performance'].to_numpy(float)) for x in levels] + fig, ax = plt.subplots(figsize=(6, 4)) + ax.errorbar([0, 1], [x[0] for x in stats], yerr=[x[1] for x in stats], fmt='o', color='black', capsize=5) + ax.set_xticks([0, 1], ['Standard routine', 'Structured routine']) + ax.set(ylabel='Post-session performance', title='SWL-S02: Group means and 95% CIs') + save(fig, out / 'swl_s02.png') + record('swl_s02.png', 'primary') + elif sid == 'SWL-S03': + wide = frame.pivot(index='participant_id', columns='occasion', values='academic_confidence') + fig, ax = plt.subplots(figsize=(6, 4)) + [ax.plot([0, 1], [r['pre'], r['post']], color='0.75', linewidth=0.7) for _, r in wide.iterrows()] + ax.plot([0, 1], [wide.pre.mean(), wide.post.mean()], color='black', marker='o', linewidth=2) + ax.set_xticks([0, 1], ['Pre', 'Post']) + ax.set(ylabel='Academic confidence', title='SWL-S03: Linked pre/post change') + save(fig, out / 'swl_s03.png') + record('swl_s03.png', 'primary') + elif sid == 'SWL-S04': + levels = ['standard_support', 'guided_practice', 'guided_practice_plus_feedback'] + stats = [mean_ci(frame.loc[frame.support_condition == x, 'assessment_performance'].to_numpy(float)) for x in levels] + fig, ax = plt.subplots(figsize=(6.4, 4)) + ax.errorbar(range(3), [x[0] for x in stats], yerr=[x[1] for x in stats], fmt='o', color='black', capsize=5) + ax.set_xticks(range(3), ['Standard', 'Guided', 'Guided + feedback']) + ax.set(ylabel='Assessment performance', title='SWL-S04: Condition means and 95% CIs') + save(fig, out / 'swl_s04.png') + record('swl_s04.png', 'primary') + elif sid == 'SWL-S05': + fig, ax = plt.subplots(figsize=(6, 4)) + for i, strategy in enumerate(['rereading', 'retrieval_practice']): + means = [frame.loc[(frame.study_strategy == strategy) & (frame.feedback_condition == feedback), 'assessment_performance'].mean() for feedback in ['no_feedback', 'explanatory_feedback']] + ax.plot([0, 1], means, color='black', linestyle='-' if i == 0 else '--', marker='o' if i == 0 else 's', label=strategy.replace('_', ' ').title()) + ax.set_xticks([0, 1], ['No feedback', 'Explanatory feedback']) + ax.set(ylabel='Assessment performance', title='SWL-S05: Strategy × Feedback interaction') + ax.legend() + save(fig, out / 'swl_s05.png') + record('swl_s05.png', 'primary') + elif sid == 'SWL-S06': + wide = frame.pivot(index='participant_id', columns='occasion', values='academic_confidence')[['baseline', 'post', 'follow_up']].dropna() + fig, ax = plt.subplots(figsize=(7.2, 4.8)) + [ax.plot(range(3), r.to_numpy(float), color='0.78', linewidth=0.65) for _, r in wide.iterrows()] + ax.plot(range(3), wide.mean().to_numpy(float), color='black', linewidth=2, marker='o') + ax.set_xticks(range(3), ['Baseline', 'Post', 'Follow-up']) + ax.set(ylabel='Academic confidence', title='SWL-S06: Complete-case confidence trajectories') + save(fig, out / 'swl_s06.png') + record('swl_s06.png', 'primary') + elif sid == 'SWL-S07': + v = analyze_swl_s07(frame) + base = v['baseline_overall_mean'] + grid = np.linspace((frame.baseline_performance - base).min(), (frame.baseline_performance - base).max(), 100) + fig, ax = plt.subplots(figsize=(7.2, 4.8)) + for group, marker, line, offset in [('comparison', 'o', '-', 0), ('intervention', 's', '--', v['adjusted_group_difference_intervention_minus_comparison'])]: + sub = frame[frame.intervention_group == group] + ax.scatter(sub.baseline_performance, sub.post_performance, facecolors='none', edgecolors='black', marker=marker, label=group.title()) + ax.plot(grid + base, v['adjusted_mean_comparison_at_overall_baseline'] + offset + v['baseline_slope'] * grid, color='black', linestyle=line) + ax.set(xlabel='Baseline performance', ylabel='Post performance', title='SWL-S07: Baseline-adjusted parallel regression lines') + ax.legend() + save(fig, out / 'swl_s07.png') + record('swl_s07.png', 'primary') + elif sid == 'SWL-S08': + means = frame.groupby(['treatment_group', 'occasion'], as_index=False).well_being_score.mean() + order = ['baseline', 'post', 'followup'] + fig, ax = plt.subplots(figsize=(7.2, 4.8)) + for group, marker, line in [('control', 'o', '-'), ('treatment', 's', '--')]: + sub = means[means.treatment_group == group].set_index('occasion') + ax.plot(range(3), [sub.loc[x, 'well_being_score'] for x in order], color='black', marker=marker, linestyle=line, label=group.title()) + ax.set_xticks(range(3), ['Baseline', 'Post', 'Follow-up']) + ax.set(xlabel='Occasion', ylabel='Well-being score', title='SWL-S08: Observed group-by-occasion means') + ax.legend() + save(fig, out / 'swl_s08.png') + record('swl_s08.png', 'primary') + elif sid == 'SWL-S09': + tab = pd.crosstab(frame.support_condition, frame.clinically_improved, normalize='index').reindex(index=['comparison', 'intervention'], columns=['yes', 'no']) + fig, ax = plt.subplots(figsize=(7.2, 4.8)) + ax.bar(range(2), tab.yes, edgecolor='black', facecolor='white', label='Improved') + ax.bar(range(2), tab.no, bottom=tab.yes, edgecolor='black', facecolor='0.70', hatch='///', label='Not improved') + ax.set_xticks(range(2), ['Comparison', 'Intervention']) + ax.set(xlabel='Support condition', ylabel='Proportion', title='SWL-S09: Registered binary outcome') + ax.legend() + save(fig, out / 'swl_s09_improvement.png') + record('swl_s09_improvement.png', 'categorical') + groups = [frame.loc[frame.support_condition == g, 'recovery_burden_days'].to_numpy(float) for g in ['comparison', 'intervention']] + fig, ax = plt.subplots(figsize=(7.2, 4.8)) + ax.boxplot(groups, tick_labels=['Comparison', 'Intervention'], showfliers=False) + ax.set(xlabel='Support condition', ylabel='Recovery burden (days)', title='SWL-S09: Positive-skew recovery burden') + save(fig, out / 'swl_s09_burden.png') + record('swl_s09_burden.png', 'rank') + else: + v = analyze_swl_s10(frame) + effects = frame.observed_cohens_d.to_numpy(float) + se = np.sqrt(frame.sampling_variance_d.to_numpy(float)) + y = np.arange(len(frame), 0, -1) + fig, ax = plt.subplots(figsize=(7.2, 5)) + ax.errorbar(effects, y, xerr=1.96 * se, fmt='o', color='black', capsize=3) + pooled = v['fixed_effect_pooled_d'] + ax.errorbar([pooled], [0], xerr=[[pooled - v['fixed_effect_ci_95_low']], [v['fixed_effect_ci_95_high'] - pooled]], fmt='D', color='black', capsize=4) + ax.axvline(0, color='0.35', linestyle='--') + ax.set_yticks(list(y) + [0], frame.study_id.tolist() + ['Fixed-effect pooled']) + ax.set(xlabel='Standardized mean difference', ylabel='Replication', title='SWL-S10: Cumulative synthetic evidence') + save(fig, out / 'swl_s10.png') + record('swl_s10.png', 'primary') + write_json(out / 'FIGURE_MANIFEST.json', {'schema_version': 'psych-design-figure-manifest-v0.1', 'study_id': sid, 'figures': rows, 'synthetic_only': True}) + print(f'PYSTATSV1_PSYCH_DESIGN_FIGURES_OK study={sid}') +if __name__ == '__main__': + p = argparse.ArgumentParser() + p.add_argument('--study') + a = p.parse_args() + ids = [normalize_study_id(a.study)] if a.study else list(study_index()) + [build(x) for x in ids] diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/list_studies.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/list_studies.py new file mode 100755 index 0000000..54ffcca --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/list_studies.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 +from companion_runtime import study_index +if __name__ == '__main__': + for sid, row in study_index().items(): + print(f"{sid} | chapters {','.join(row['book_chapters'])} | {row['claim_type']} | {row['reader_command']}") diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/run_all.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/run_all.py new file mode 100755 index 0000000..13e5f74 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/run_all.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 +from companion_runtime import run_registered_study +if __name__ == '__main__': + for number in range(1, 11): + run_registered_study(f'SWL-S{number:02d}') + print('PYSTATSV1_PSYCH_DESIGN_ALL_PYTHON_OK studies=10') diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/run_study.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/run_study.py new file mode 100755 index 0000000..0f18fc6 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/run_study.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 +from __future__ import annotations +import argparse +from companion_runtime import run_registered_study +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--study', required=True) + args = parser.parse_args() + run_registered_study(args.study) diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s01_survey_association_prediction.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s01_survey_association_prediction.py new file mode 100755 index 0000000..147a000 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s01_survey_association_prediction.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +"""Run SWL-S01: association and within-domain prediction. + +Dataset: data/swl_s01_student_well_being_survey.csv +Registered target: Pearson association between sleep quality and academic confidence; regression coefficients predicting assessment performance within the observed predictor domain +This script analyzes the governed synthetic fixture only. +""" +from __future__ import annotations +from companion_runtime import run_registered_study +STUDY_ID = 'SWL-S01' +DATASET = 'data/swl_s01_student_well_being_survey.csv' +REGISTERED_ESTIMAND = ['Pearson association between sleep quality and academic confidence', 'regression coefficients predicting assessment performance within the observed predictor domain'] +if __name__ == '__main__': + run_registered_study(STUDY_ID) diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s02_independent_groups.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s02_independent_groups.py new file mode 100755 index 0000000..b550bb8 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s02_independent_groups.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +"""Run SWL-S02: independent-group mean difference with conditional causal scope. + +Dataset: data/swl_s02_structured_study_routine.csv +Registered target: structured_routine minus standard_routine mean post-session performance +This script analyzes the governed synthetic fixture only. +""" +from __future__ import annotations +from companion_runtime import run_registered_study +STUDY_ID = 'SWL-S02' +DATASET = 'data/swl_s02_structured_study_routine.csv' +REGISTERED_ESTIMAND = ['structured_routine minus standard_routine mean post-session performance'] +if __name__ == '__main__': + run_registered_study(STUDY_ID) diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s03_paired_change.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s03_paired_change.py new file mode 100755 index 0000000..f1fb216 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s03_paired_change.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +"""Run SWL-S03: within-person paired change without a unique causal attribution. + +Dataset: data/swl_s03_skills_workshop_pre_post.csv +Registered target: post minus pre mean paired change +This script analyzes the governed synthetic fixture only. +""" +from __future__ import annotations +from companion_runtime import run_registered_study +STUDY_ID = 'SWL-S03' +DATASET = 'data/swl_s03_skills_workshop_pre_post.csv' +REGISTERED_ESTIMAND = ['post minus pre mean paired change'] +if __name__ == '__main__': + run_registered_study(STUDY_ID) diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s04_planned_contrasts.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s04_planned_contrasts.py new file mode 100755 index 0000000..fd7e778 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s04_planned_contrasts.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +"""Run SWL-S04: omnibus several-group comparison and two registered planned contrasts. + +Dataset: data/swl_s04_three_condition_support.csv +Registered target: guided-condition average minus standard support; guided practice plus feedback minus guided practice +This script analyzes the governed synthetic fixture only. +""" +from __future__ import annotations +from companion_runtime import run_registered_study +STUDY_ID = 'SWL-S04' +DATASET = 'data/swl_s04_three_condition_support.csv' +REGISTERED_ESTIMAND = ['guided-condition average minus standard support', 'guided practice plus feedback minus guided practice'] +if __name__ == '__main__': + run_registered_study(STUDY_ID) diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s05_factorial_interaction.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s05_factorial_interaction.py new file mode 100755 index 0000000..11d3aa8 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s05_factorial_interaction.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +"""Run SWL-S05: factorial interaction and registered simple strategy effects. + +Dataset: data/swl_s05_strategy_feedback.csv +Registered target: retrieval-practice effect with feedback minus retrieval-practice effect without feedback +This script analyzes the governed synthetic fixture only. +""" +from __future__ import annotations +from companion_runtime import run_registered_study +STUDY_ID = 'SWL-S05' +DATASET = 'data/swl_s05_strategy_feedback.csv' +REGISTERED_ESTIMAND = ['retrieval-practice effect with feedback minus retrieval-practice effect without feedback'] +if __name__ == '__main__': + run_registered_study(STUDY_ID) diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s06_repeated_measures.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s06_repeated_measures.py new file mode 100755 index 0000000..67a2fa6 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s06_repeated_measures.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +"""Run SWL-S06: complete-trajectory occasion comparison with missing-visit disclosure. + +Dataset: data/swl_s06_confidence_trajectory.csv +Registered target: occasion effect across baseline, post, and follow_up among complete trajectories +This script analyzes the governed synthetic fixture only. +""" +from __future__ import annotations +from companion_runtime import run_registered_study +STUDY_ID = 'SWL-S06' +DATASET = 'data/swl_s06_confidence_trajectory.csv' +REGISTERED_ESTIMAND = ['occasion effect across baseline, post, and follow_up among complete trajectories'] +if __name__ == '__main__': + run_registered_study(STUDY_ID) diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s07_ancova.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s07_ancova.py new file mode 100755 index 0000000..3baaa3f --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s07_ancova.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +"""Run SWL-S07: baseline-adjusted group comparison under the registered ANCOVA model. + +Dataset: data/swl_s07_baseline_adjusted_intervention.csv +Registered target: intervention minus comparison adjusted post-performance difference at the overall baseline mean +This script analyzes the governed synthetic fixture only. +""" +from __future__ import annotations +from companion_runtime import run_registered_study +STUDY_ID = 'SWL-S07' +DATASET = 'data/swl_s07_baseline_adjusted_intervention.csv' +REGISTERED_ESTIMAND = ['intervention minus comparison adjusted post-performance difference at the overall baseline mean'] +if __name__ == '__main__': + run_registered_study(STUDY_ID) diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s08_mixed_model.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s08_mixed_model.py new file mode 100755 index 0000000..d963128 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s08_mixed_model.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +"""Run SWL-S08: longitudinal treatment-versus-control differential change. + +Dataset: data/swl_s08_longitudinal_treatment.csv +Registered target: additional treatment-group change from baseline to post; additional treatment-group change from baseline to follow-up +This script analyzes the governed synthetic fixture only. +""" +from __future__ import annotations +from companion_runtime import run_registered_study +STUDY_ID = 'SWL-S08' +DATASET = 'data/swl_s08_longitudinal_treatment.csv' +REGISTERED_ESTIMAND = ['additional treatment-group change from baseline to post', 'additional treatment-group change from baseline to follow-up'] +if __name__ == '__main__': + run_registered_study(STUDY_ID) diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s09_distribution_extension.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s09_distribution_extension.py new file mode 100755 index 0000000..5b22cb7 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s09_distribution_extension.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +"""Run SWL-S09: categorical association and rank-based distributional shift. + +Dataset: data/swl_s09_outcome_distribution_extension.csv +Registered target: intervention minus comparison improvement-risk difference; distributional shift in recovery burden with intervention oriented as lower burden +This script analyzes the governed synthetic fixture only. +""" +from __future__ import annotations +from companion_runtime import run_registered_study +STUDY_ID = 'SWL-S09' +DATASET = 'data/swl_s09_outcome_distribution_extension.csv' +REGISTERED_ESTIMAND = ['intervention minus comparison improvement-risk difference', 'distributional shift in recovery burden with intervention oriented as lower burden'] +if __name__ == '__main__': + run_registered_study(STUDY_ID) diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s10_power_replication.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s10_power_replication.py new file mode 100755 index 0000000..ed102bf --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/python/swl_s10_power_replication.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +"""Run SWL-S10: prospective power planning and fixed-effect cumulative evidence. + +Dataset: data/swl_s10_replication_program.csv +Registered target: per-group sample size for d=0.40 at 80% power; fixed-effect pooled standardized mean difference across six replications +This script analyzes the governed synthetic fixture only. +""" +from __future__ import annotations +from companion_runtime import run_registered_study +STUDY_ID = 'SWL-S10' +DATASET = 'data/swl_s10_replication_program.csv' +REGISTERED_ESTIMAND = ['per-group sample size for d=0.40 at 80% power', 'fixed-effect pooled standardized mean difference across six replications'] +if __name__ == '__main__': + run_registered_study(STUDY_ID) diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s01_s06_s07_v0_1/common.R b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s01_s06_s07_v0_1/common.R new file mode 100755 index 0000000..6d3b4fc --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s01_s06_s07_v0_1/common.R @@ -0,0 +1,34 @@ +validated_metric_values <- function(metrics) { + metric_names <- names(metrics) + values <- as.numeric(unlist(metrics, use.names = FALSE)) + if (length(values) != length(metric_names)) { + stop("R metric names and values differ in length") + } + invalid <- !is.finite(values) + if (any(invalid)) { + stop(sprintf( + "non-finite R metric(s): %s", + paste(metric_names[invalid], collapse = ", ") + )) + } + values +} + +write_metrics <- function(path, metrics) { + dir.create(dirname(path), recursive = TRUE, showWarnings = FALSE) + frame <- data.frame( + metric = names(metrics), + value = validated_metric_values(metrics), + stringsAsFactors = FALSE + ) + frame <- frame[order(frame$metric), ] + write.table( + frame, + path, + sep = ",", + row.names = FALSE, + col.names = TRUE, + quote = TRUE, + eol = "\n" + ) +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s01_s06_s07_v0_1/swl_s01.R b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s01_s06_s07_v0_1/swl_s01.R new file mode 100755 index 0000000..13f5416 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s01_s06_s07_v0_1/swl_s01.R @@ -0,0 +1,47 @@ +run_swl_s01 <- function(root, output_root) { + data <- read.csv(file.path(root, "data", "swl_s01_student_well_being_survey.csv"), stringsAsFactors = FALSE) + if (nrow(data) != 120 || length(unique(data$participant_id)) != 120) stop("SWL-S01 row or ID count") + correlation <- cor.test(data$sleep_quality, data$academic_confidence, method = "pearson") + model <- lm(assessment_performance ~ study_hours_per_week + academic_confidence, data = data) + model_summary <- summary(model) + coefficients <- coef(model_summary) + intervals <- confint(model, level = 0.95) + fstat <- model_summary$fstatistic + residual_df <- unname(fstat[[3]]) + mse <- sum(residuals(model)^2) / residual_df + metrics <- c( + n = nrow(data), + correlation_r_sleep_confidence = unname(correlation$estimate), + correlation_t = unname(correlation$statistic), + correlation_df = unname(correlation$parameter), + correlation_p_two_sided = correlation$p.value, + correlation_ci_95_low = correlation$conf.int[[1]], + correlation_ci_95_high = correlation$conf.int[[2]], + regression_df_model = unname(fstat[[2]]), + regression_df_residual = residual_df, + regression_f = unname(fstat[[1]]), + regression_p = pf(unname(fstat[[1]]), unname(fstat[[2]]), residual_df, lower.tail = FALSE), + regression_r_squared = model_summary$r.squared, + regression_adjusted_r_squared = model_summary$adj.r.squared, + regression_rmse = sqrt(mse), + intercept_b = coefficients["(Intercept)", "Estimate"], + intercept_se = coefficients["(Intercept)", "Std. Error"], + intercept_t = coefficients["(Intercept)", "t value"], + intercept_p = coefficients["(Intercept)", "Pr(>|t|)"], + intercept_ci_95_low = intervals["(Intercept)", 1], + intercept_ci_95_high = intervals["(Intercept)", 2], + study_hours_b = coefficients["study_hours_per_week", "Estimate"], + study_hours_se = coefficients["study_hours_per_week", "Std. Error"], + study_hours_t = coefficients["study_hours_per_week", "t value"], + study_hours_p = coefficients["study_hours_per_week", "Pr(>|t|)"], + study_hours_ci_95_low = intervals["study_hours_per_week", 1], + study_hours_ci_95_high = intervals["study_hours_per_week", 2], + confidence_b = coefficients["academic_confidence", "Estimate"], + confidence_se = coefficients["academic_confidence", "Std. Error"], + confidence_t = coefficients["academic_confidence", "t value"], + confidence_p = coefficients["academic_confidence", "Pr(>|t|)"], + confidence_ci_95_low = intervals["academic_confidence", 1], + confidence_ci_95_high = intervals["academic_confidence", 2] + ) + write_metrics(file.path(output_root, "SWL-S01.csv"), metrics) +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s01_s06_s07_v0_1/swl_s06.R b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s01_s06_s07_v0_1/swl_s06.R new file mode 100755 index 0000000..eb9e8b9 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s01_s06_s07_v0_1/swl_s06.R @@ -0,0 +1,57 @@ +run_swl_s06 <- function(root, output_root) { + data <- read.csv(file.path(root, "data", "swl_s06_confidence_trajectory.csv"), stringsAsFactors = FALSE, na.strings = c("", "NA")) + occasions <- c("baseline", "post", "follow_up") + wide <- reshape(data[, c("participant_id", "occasion", "academic_confidence")], idvar = "participant_id", timevar = "occasion", direction = "wide") + columns <- paste0("academic_confidence.", occasions) + wide <- wide[, c("participant_id", columns)] + complete <- wide[complete.cases(wide[, columns]), columns] + y <- as.matrix(complete) + n <- nrow(y) + k <- ncol(y) + grand <- mean(y) + subject_means <- rowMeans(y) + occasion_means <- colMeans(y) + ss_total <- sum((y - grand)^2) + ss_subjects <- k * sum((subject_means - grand)^2) + ss_within <- ss_total - ss_subjects + ss_occasion <- n * sum((occasion_means - grand)^2) + ss_error <- ss_within - ss_occasion + df_occasion <- k - 1 + df_error <- (n - 1) * (k - 1) + f_value <- (ss_occasion / df_occasion) / (ss_error / df_error) + covariance <- cov(y) + centering <- diag(k) - matrix(1 / k, nrow = k, ncol = k) + transformed <- centering %*% covariance %*% centering + tr <- sum(diag(transformed)) + epsilon <- tr^2 / ((k - 1) * sum(diag(transformed %*% transformed))) + epsilon <- max(1 / (k - 1), min(1, epsilon)) + gg_df_occasion <- epsilon * df_occasion + gg_df_error <- epsilon * df_error + missing_counts <- colSums(is.na(wide[, columns])) + metrics <- c( + n_scheduled_participants = nrow(wide), + scheduled_rows = nrow(wide) * k, + observed_outcomes = sum(!is.na(wide[, columns])), + missing_outcomes = sum(is.na(wide[, columns])), + missing_baseline = missing_counts[[1]], + missing_post = missing_counts[[2]], + missing_follow_up = missing_counts[[3]], + n_complete_case = n, + n_incomplete_participants = nrow(wide) - n, + mean_baseline_complete_case = occasion_means[[1]], + mean_post_complete_case = occasion_means[[2]], + mean_follow_up_complete_case = occasion_means[[3]], + mean_change_post_minus_baseline = occasion_means[[2]] - occasion_means[[1]], + mean_change_follow_up_minus_baseline = occasion_means[[3]] - occasion_means[[1]], + rm_anova_f = f_value, + rm_anova_df_occasion = df_occasion, + rm_anova_df_error = df_error, + rm_anova_p = pf(f_value, df_occasion, df_error, lower.tail = FALSE), + greenhouse_geisser_epsilon = epsilon, + greenhouse_geisser_df_occasion = gg_df_occasion, + greenhouse_geisser_df_error = gg_df_error, + greenhouse_geisser_p = pf(f_value, gg_df_occasion, gg_df_error, lower.tail = FALSE), + partial_eta_squared = ss_occasion / (ss_occasion + ss_error) + ) + write_metrics(file.path(output_root, "SWL-S06.csv"), metrics) +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s01_s06_s07_v0_1/swl_s07.R b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s01_s06_s07_v0_1/swl_s07.R new file mode 100755 index 0000000..ac18faf --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s01_s06_s07_v0_1/swl_s07.R @@ -0,0 +1,55 @@ +run_swl_s07 <- function(root, output_root) { + data <- read.csv(file.path(root, "data", "swl_s07_baseline_adjusted_intervention.csv"), stringsAsFactors = FALSE) + data$treatment <- ifelse(data$intervention_group == "intervention", 1, 0) + baseline_mean <- mean(data$baseline_performance) + data$baseline_centered <- data$baseline_performance - baseline_mean + model <- lm(post_performance ~ treatment + baseline_centered, data = data) + summary_model <- summary(model) + coefficients <- coef(summary_model) + intervals <- confint(model, level = 0.95) + interaction_model <- lm(post_performance ~ treatment * baseline_centered, data = data) + interaction_summary <- summary(interaction_model) + interaction_coefficients <- coef(interaction_summary) + group_t <- coefficients["treatment", "t value"] + group_f <- group_t^2 + df_error <- df.residual(model) + adjusted_comparison <- coefficients["(Intercept)", "Estimate"] + adjusted_difference <- coefficients["treatment", "Estimate"] + comparison <- data$intervention_group == "comparison" + intervention <- data$intervention_group == "intervention" + metrics <- c( + n_total = nrow(data), + n_comparison = sum(comparison), + n_intervention = sum(intervention), + baseline_overall_mean = baseline_mean, + raw_baseline_mean_comparison = mean(data$baseline_performance[comparison]), + raw_baseline_mean_intervention = mean(data$baseline_performance[intervention]), + raw_post_mean_comparison = mean(data$post_performance[comparison]), + raw_post_mean_intervention = mean(data$post_performance[intervention]), + adjusted_mean_comparison_at_overall_baseline = adjusted_comparison, + adjusted_mean_intervention_at_overall_baseline = adjusted_comparison + adjusted_difference, + adjusted_group_difference_intervention_minus_comparison = adjusted_difference, + group_effect_se = coefficients["treatment", "Std. Error"], + group_effect_t = group_t, + group_effect_p = coefficients["treatment", "Pr(>|t|)"], + group_effect_ci_95_low = intervals["treatment", 1], + group_effect_ci_95_high = intervals["treatment", 2], + group_effect_f = group_f, + group_effect_df = 1, + model_df_error = df_error, + group_partial_eta_squared = group_f / (group_f + df_error), + baseline_slope = coefficients["baseline_centered", "Estimate"], + baseline_slope_se = coefficients["baseline_centered", "Std. Error"], + baseline_slope_t = coefficients["baseline_centered", "t value"], + baseline_slope_p = coefficients["baseline_centered", "Pr(>|t|)"], + baseline_slope_ci_95_low = intervals["baseline_centered", 1], + baseline_slope_ci_95_high = intervals["baseline_centered", 2], + model_r_squared = summary_model$r.squared, + model_adjusted_r_squared = summary_model$adj.r.squared, + homogeneity_interaction_b = interaction_coefficients["treatment:baseline_centered", "Estimate"], + homogeneity_interaction_se = interaction_coefficients["treatment:baseline_centered", "Std. Error"], + homogeneity_interaction_t = interaction_coefficients["treatment:baseline_centered", "t value"], + homogeneity_interaction_p = interaction_coefficients["treatment:baseline_centered", "Pr(>|t|)"] + ) + write_metrics(file.path(output_root, "SWL-S07.csv"), metrics) +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s02_s05_v0_1/common.R b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s02_s05_v0_1/common.R new file mode 100755 index 0000000..38baa92 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s02_s05_v0_1/common.R @@ -0,0 +1,31 @@ +validated_metric_values <- function(metrics) { + metric_names <- names(metrics) + values <- as.numeric(unlist(metrics, use.names = FALSE)) + if (length(values) != length(metric_names)) { + stop("R metric names and values differ in length") + } + invalid <- !is.finite(values) + if (any(invalid)) { + stop(sprintf( + "non-finite R metric(s): %s", + paste(metric_names[invalid], collapse = ", ") + )) + } + values +} + +write_metrics <- function(path, metrics) { + dir.create(dirname(path), recursive = TRUE, showWarnings = FALSE) + frame <- data.frame( + metric = names(metrics), + value = validated_metric_values(metrics), + stringsAsFactors = FALSE + ) + frame <- frame[order(frame$metric), ] + write.csv(frame, path, row.names = FALSE, quote = TRUE) +} + +pooled_sd <- function(x, y) { + sqrt(((length(x) - 1) * var(x) + (length(y) - 1) * var(y)) / + (length(x) + length(y) - 2)) +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s02_s05_v0_1/swl_s02.R b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s02_s05_v0_1/swl_s02.R new file mode 100755 index 0000000..007357a --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s02_s05_v0_1/swl_s02.R @@ -0,0 +1,30 @@ +run_swl_s02 <- function(root, output_root) { + data <- read.csv( + file.path(root, "data", "swl_s02_structured_study_routine.csv"), + stringsAsFactors = FALSE + ) + standard <- data$post_session_performance[ + data$study_routine_group == "standard_routine" + ] + structured <- data$post_session_performance[ + data$study_routine_group == "structured_routine" + ] + test <- t.test(structured, standard, var.equal = FALSE, conf.level = 0.95) + difference <- mean(structured) - mean(standard) + metrics <- list( + n_standard = length(standard), + n_structured = length(structured), + mean_standard = mean(standard), + mean_structured = mean(structured), + sd_standard = sd(standard), + sd_structured = sd(structured), + mean_difference_structured_minus_standard = difference, + welch_t = unname(test$statistic), + welch_df = unname(test$parameter), + p_value_two_sided = test$p.value, + ci_95_low = test$conf.int[1], + ci_95_high = test$conf.int[2], + cohen_d_pooled = difference / pooled_sd(structured, standard) + ) + write_metrics(file.path(output_root, "SWL-S02_r_results.csv"), metrics) +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s02_s05_v0_1/swl_s03.R b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s02_s05_v0_1/swl_s03.R new file mode 100755 index 0000000..506a8d4 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s02_s05_v0_1/swl_s03.R @@ -0,0 +1,27 @@ +run_swl_s03 <- function(root, output_root) { + data <- read.csv( + file.path(root, "data", "swl_s03_skills_workshop_pre_post.csv"), + stringsAsFactors = FALSE + ) + pre <- data[data$occasion == "pre", c("participant_id", "academic_confidence")] + post <- data[data$occasion == "post", c("participant_id", "academic_confidence")] + names(pre)[2] <- "pre" + names(post)[2] <- "post" + paired <- merge(pre, post, by = "participant_id", all = FALSE, sort = TRUE) + change <- paired$post - paired$pre + test <- t.test(paired$post, paired$pre, paired = TRUE, conf.level = 0.95) + metrics <- list( + n_paired = nrow(paired), + mean_pre = mean(paired$pre), + mean_post = mean(paired$post), + mean_change_post_minus_pre = mean(change), + sd_change = sd(change), + paired_t = unname(test$statistic), + df = unname(test$parameter), + p_value_two_sided = test$p.value, + ci_95_low = test$conf.int[1], + ci_95_high = test$conf.int[2], + cohen_dz = mean(change) / sd(change) + ) + write_metrics(file.path(output_root, "SWL-S03_r_results.csv"), metrics) +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s02_s05_v0_1/swl_s04.R b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s02_s05_v0_1/swl_s04.R new file mode 100755 index 0000000..1bc6b6a --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s02_s05_v0_1/swl_s04.R @@ -0,0 +1,90 @@ +run_swl_s04 <- function(root, output_root) { + data <- read.csv( + file.path(root, "data", "swl_s04_three_condition_support.csv"), + stringsAsFactors = FALSE + ) + levels_order <- c( + "standard_support", + "guided_practice", + "guided_practice_plus_feedback" + ) + data$support_condition <- factor( + data$support_condition, + levels = levels_order + ) + if (any(is.na(data$support_condition))) { + stop("SWL-S04 contains an unregistered support condition") + } + + group_values <- lapply( + levels_order, + function(level) { + data$assessment_performance[data$support_condition == level] + } + ) + names(group_values) <- levels_order + group_counts <- vapply(group_values, length, numeric(1)) + group_means <- vapply(group_values, mean, numeric(1)) + if (any(group_counts == 0)) { + stop("SWL-S04 requires every registered support condition") + } + + grand_mean <- mean(data$assessment_performance) + ss_between <- sum(group_counts * (group_means - grand_mean)^2) + ss_within <- sum(vapply( + levels_order, + function(level) { + sum((group_values[[level]] - group_means[[level]])^2) + }, + numeric(1) + )) + df_between <- length(levels_order) - 1 + df_within <- nrow(data) - length(levels_order) + ms_between <- ss_between / df_between + mse <- ss_within / df_within + anova_f <- ms_between / mse + p_value <- pf(anova_f, df_between, df_within, lower.tail = FALSE) + + coefficients_1 <- c(-1.0, 0.5, 0.5) + names(coefficients_1) <- levels_order + estimate_1 <- sum(coefficients_1 * group_means) + se_1 <- sqrt(mse * sum(coefficients_1^2 / group_counts)) + t_1 <- estimate_1 / se_1 + p_1 <- 2 * pt(abs(t_1), df = df_within, lower.tail = FALSE) + + coefficients_2 <- c(0.0, -1.0, 1.0) + names(coefficients_2) <- levels_order + estimate_2 <- sum(coefficients_2 * group_means) + se_2 <- sqrt(mse * sum(coefficients_2^2 / group_counts)) + t_2 <- estimate_2 / se_2 + p_2 <- 2 * pt(abs(t_2), df = df_within, lower.tail = FALSE) + adjusted <- p.adjust(c(p_1, p_2), method = "holm") + + metrics <- list( + n_total = nrow(data), + n_standard_support = group_counts[["standard_support"]], + n_guided_practice = group_counts[["guided_practice"]], + n_guided_practice_plus_feedback = group_counts[[ + "guided_practice_plus_feedback" + ]], + mean_standard_support = group_means[["standard_support"]], + mean_guided_practice = group_means[["guided_practice"]], + mean_guided_practice_plus_feedback = group_means[[ + "guided_practice_plus_feedback" + ]], + anova_f = anova_f, + df_between = df_between, + df_within = df_within, + p_value = p_value, + eta_squared = ss_between / (ss_between + ss_within), + contrast_guided_average_minus_standard_estimate = estimate_1, + contrast_guided_average_minus_standard_t = t_1, + contrast_guided_average_minus_standard_p = p_1, + contrast_guided_average_minus_standard_p_holm = adjusted[1], + contrast_feedback_increment_estimate = estimate_2, + contrast_feedback_increment_t = t_2, + contrast_feedback_increment_p = p_2, + contrast_feedback_increment_p_holm = adjusted[2] + ) + write_metrics(file.path(output_root, "SWL-S04_r_results.csv"), metrics) +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s02_s05_v0_1/swl_s05.R b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s02_s05_v0_1/swl_s05.R new file mode 100755 index 0000000..0e302e8 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s02_s05_v0_1/swl_s05.R @@ -0,0 +1,143 @@ +run_swl_s05 <- function(root, output_root) { + data <- read.csv( + file.path(root, "data", "swl_s05_strategy_feedback.csv"), + stringsAsFactors = FALSE + ) + strategies <- c("rereading", "retrieval_practice") + feedback_levels <- c("no_feedback", "explanatory_feedback") + data$study_strategy <- factor( + data$study_strategy, + levels = strategies + ) + data$feedback_condition <- factor( + data$feedback_condition, + levels = feedback_levels + ) + if (any(is.na(data$study_strategy)) || any(is.na(data$feedback_condition))) { + stop("SWL-S05 contains an unregistered factor level") + } + + cell_counts <- with( + data, + table(study_strategy, feedback_condition) + ) + observed_cell_sizes <- unique(as.numeric(cell_counts)) + if (length(observed_cell_sizes) != 1 || observed_cell_sizes[1] <= 0) { + stop("SWL-S05 requires balanced nonempty cell sizes") + } + n_per_cell <- observed_cell_sizes[1] + + cell_means <- with( + data, + tapply( + assessment_performance, + list(study_strategy, feedback_condition), + mean + ) + ) + strategy_means <- with( + data, + tapply(assessment_performance, study_strategy, mean) + ) + feedback_means <- with( + data, + tapply(assessment_performance, feedback_condition, mean) + ) + grand_mean <- mean(data$assessment_performance) + + ss_strategy <- length(feedback_levels) * n_per_cell * sum( + (strategy_means - grand_mean)^2 + ) + ss_feedback <- length(strategies) * n_per_cell * sum( + (feedback_means - grand_mean)^2 + ) + ss_interaction <- n_per_cell * sum(vapply( + strategies, + function(strategy) { + sum(vapply( + feedback_levels, + function(feedback) { + ( + cell_means[strategy, feedback] - + strategy_means[[strategy]] - + feedback_means[[feedback]] + + grand_mean + )^2 + }, + numeric(1) + )) + }, + numeric(1) + )) + + fitted_cell_means <- mapply( + function(strategy, feedback) { + cell_means[strategy, feedback] + }, + as.character(data$study_strategy), + as.character(data$feedback_condition) + ) + ss_within <- sum((data$assessment_performance - fitted_cell_means)^2) + df_within <- nrow(data) - length(cell_means) + mse <- ss_within / df_within + + strategy_f <- ss_strategy / mse + feedback_f <- ss_feedback / mse + interaction_f <- ss_interaction / mse + strategy_p <- pf(strategy_f, 1, df_within, lower.tail = FALSE) + feedback_p <- pf(feedback_f, 1, df_within, lower.tail = FALSE) + interaction_p <- pf(interaction_f, 1, df_within, lower.tail = FALSE) + + se_simple <- sqrt(mse * (2 / n_per_cell)) + simple_no <- cell_means["retrieval_practice", "no_feedback"] - + cell_means["rereading", "no_feedback"] + simple_feedback <- cell_means[ + "retrieval_practice", + "explanatory_feedback" + ] - cell_means["rereading", "explanatory_feedback"] + t_no <- simple_no / se_simple + t_feedback <- simple_feedback / se_simple + p_no <- 2 * pt(abs(t_no), df = df_within, lower.tail = FALSE) + p_feedback <- 2 * pt(abs(t_feedback), df = df_within, lower.tail = FALSE) + + metrics <- list( + n_total = nrow(data), + n_per_cell = n_per_cell, + mean_rereading_no_feedback = cell_means[ + "rereading", + "no_feedback" + ], + mean_retrieval_no_feedback = cell_means[ + "retrieval_practice", + "no_feedback" + ], + mean_rereading_explanatory_feedback = cell_means[ + "rereading", + "explanatory_feedback" + ], + mean_retrieval_explanatory_feedback = cell_means[ + "retrieval_practice", + "explanatory_feedback" + ], + strategy_f = strategy_f, + strategy_p = strategy_p, + strategy_partial_eta_squared = ss_strategy / (ss_strategy + ss_within), + feedback_f = feedback_f, + feedback_p = feedback_p, + feedback_partial_eta_squared = ss_feedback / (ss_feedback + ss_within), + interaction_f = interaction_f, + interaction_p = interaction_p, + interaction_partial_eta_squared = ss_interaction / + (ss_interaction + ss_within), + df_effect = 1, + df_within = df_within, + interaction_difference_in_differences = simple_feedback - simple_no, + simple_strategy_no_feedback_estimate = simple_no, + simple_strategy_no_feedback_t = t_no, + simple_strategy_no_feedback_p = p_no, + simple_strategy_explanatory_feedback_estimate = simple_feedback, + simple_strategy_explanatory_feedback_t = t_feedback, + simple_strategy_explanatory_feedback_p = p_feedback + ) + write_metrics(file.path(output_root, "SWL-S05_r_results.csv"), metrics) +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s08_s10_v0_1/common.R b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s08_s10_v0_1/common.R new file mode 100755 index 0000000..42aa656 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s08_s10_v0_1/common.R @@ -0,0 +1,19 @@ +write_metrics <- function(path, metrics) { + if (is.null(names(metrics)) || any(names(metrics) == "")) { + stop("All metrics must be named.") + } + numeric_metrics <- as.numeric(metrics) + if (any(!is.finite(numeric_metrics))) { + stop("All metrics must be finite numeric values.") + } + output_dir <- dirname(path) + if (!dir.exists(output_dir)) { + dir.create(output_dir, recursive = TRUE) + } + frame <- data.frame( + metric = names(metrics), + value = format(numeric_metrics, digits = 17, scientific = TRUE, trim = TRUE), + stringsAsFactors = FALSE + ) + write.csv(frame, path, row.names = FALSE, quote = FALSE) +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s08_s10_v0_1/swl_s08.R b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s08_s10_v0_1/swl_s08.R new file mode 100755 index 0000000..83b7572 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s08_s10_v0_1/swl_s08.R @@ -0,0 +1,62 @@ +run_swl_s08 <- function(root, output_root) { + if (!requireNamespace("nlme", quietly = TRUE)) { + stop("The recommended R package 'nlme' is required for SWL-S08 verification.") + } + data <- read.csv( + file.path(root, "data", "swl_s08_longitudinal_treatment.csv"), + stringsAsFactors = FALSE, + check.names = FALSE + ) + if (anyDuplicated(data[c("participant_id", "occasion")]) != 0) { + stop("Duplicate SWL-S08 participant/occasion keys detected.") + } + fit <- nlme::lme( + fixed = well_being_score ~ treatment + post + followup + treatment_post + treatment_followup, + random = ~ 1 | participant_id, + data = data, + method = "ML", + na.action = na.fail, + control = nlme::lmeControl( + maxIter = 200, + msMaxIter = 200, + niterEM = 50, + returnObject = TRUE + ) + ) + coef_table <- summary(fit)$tTable + visits <- table(data$participant_id) + mean_at <- function(group, occasion) { + mean(data$well_being_score[ + data$treatment_group == group & data$occasion == occasion + ]) + } + metrics <- c( + n_participants = length(unique(data$participant_id)), + n_control = length(unique(data$participant_id[data$treatment_group == "control"])), + n_treatment = length(unique(data$participant_id[data$treatment_group == "treatment"])), + scheduled_rows = 180, + observed_rows = nrow(data), + missing_visits = 180 - nrow(data), + complete_participants = sum(visits == 3), + incomplete_participants = sum(visits == 2), + control_baseline_mean = mean_at("control", "baseline"), + control_post_mean = mean_at("control", "post"), + control_followup_mean = mean_at("control", "followup"), + treatment_baseline_mean = mean_at("treatment", "baseline"), + treatment_post_mean = mean_at("treatment", "post"), + treatment_followup_mean = mean_at("treatment", "followup"), + intercept_b = coef_table["(Intercept)", "Value"], + intercept_se = coef_table["(Intercept)", "Std.Error"], + treatment_b = coef_table["treatment", "Value"], + treatment_se = coef_table["treatment", "Std.Error"], + post_b = coef_table["post", "Value"], + post_se = coef_table["post", "Std.Error"], + followup_b = coef_table["followup", "Value"], + followup_se = coef_table["followup", "Std.Error"], + treatment_post_b = coef_table["treatment_post", "Value"], + treatment_post_se = coef_table["treatment_post", "Std.Error"], + treatment_followup_b = coef_table["treatment_followup", "Value"], + treatment_followup_se = coef_table["treatment_followup", "Std.Error"] + ) + write_metrics(file.path(output_root, "SWL-S08.csv"), metrics) +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s08_s10_v0_1/swl_s09.R b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s08_s10_v0_1/swl_s09.R new file mode 100755 index 0000000..a39806b --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s08_s10_v0_1/swl_s09.R @@ -0,0 +1,64 @@ +run_swl_s09 <- function(root, output_root) { + data <- read.csv( + file.path(root, "data", "swl_s09_outcome_distribution_extension.csv"), + stringsAsFactors = FALSE, + check.names = FALSE + ) + data$support_condition <- factor( + data$support_condition, + levels = c("comparison", "intervention") + ) + data$clinically_improved <- factor( + data$clinically_improved, + levels = c("no", "yes") + ) + observed <- table(data$support_condition, data$clinically_improved) + chi <- suppressWarnings(chisq.test(observed, correct = FALSE)) + comparison <- data$recovery_burden_days[data$support_condition == "comparison"] + intervention <- data$recovery_burden_days[data$support_condition == "intervention"] + combined <- c(intervention, comparison) + ranks <- rank(combined, ties.method = "average") + n1 <- length(intervention) + n2 <- length(comparison) + u1 <- sum(ranks[seq_len(n1)]) - n1 * (n1 + 1) / 2 + tie_counts <- as.numeric(table(combined)) + tie_sum <- sum(tie_counts^3 - tie_counts) + n <- n1 + n2 + variance <- n1 * n2 / 12 * ((n + 1) - tie_sum / (n * (n - 1))) + expected_u <- n1 * n2 / 2 + z <- (u1 - expected_u) / sqrt(variance) + p_rank <- 2 * pnorm(-abs(z)) + rank_biserial_lower <- 1 - 2 * u1 / (n1 * n2) + metrics <- c( + n_total = nrow(data), + n_comparison = length(comparison), + n_intervention = length(intervention), + comparison_improved = observed["comparison", "yes"], + comparison_not_improved = observed["comparison", "no"], + intervention_improved = observed["intervention", "yes"], + intervention_not_improved = observed["intervention", "no"], + comparison_improved_proportion = observed["comparison", "yes"] / length(comparison), + intervention_improved_proportion = observed["intervention", "yes"] / length(intervention), + improved_risk_difference_intervention_minus_comparison = + observed["intervention", "yes"] / length(intervention) - + observed["comparison", "yes"] / length(comparison), + chi_square = unname(chi$statistic), + chi_square_df = unname(chi$parameter), + chi_square_p = chi$p.value, + cramers_v = sqrt(unname(chi$statistic) / nrow(data)), + minimum_expected_cell_count = min(chi$expected), + comparison_burden_median = median(comparison), + intervention_burden_median = median(intervention), + burden_median_difference_intervention_minus_comparison = + median(intervention) - median(comparison), + mann_whitney_u_intervention = u1, + mann_whitney_z_no_continuity = z, + mann_whitney_p_two_sided = p_rank, + rank_biserial_intervention_lower = rank_biserial_lower, + comparison_burden_min = min(comparison), + comparison_burden_max = max(comparison), + intervention_burden_min = min(intervention), + intervention_burden_max = max(intervention) + ) + write_metrics(file.path(output_root, "SWL-S09.csv"), metrics) +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s08_s10_v0_1/swl_s10.R b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s08_s10_v0_1/swl_s10.R new file mode 100755 index 0000000..3a1ee4d --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/batches/swl_s08_s10_v0_1/swl_s10.R @@ -0,0 +1,52 @@ +run_swl_s10 <- function(root, output_root) { + data <- read.csv( + file.path(root, "data", "swl_s10_replication_program.csv"), + stringsAsFactors = FALSE, + check.names = FALSE + ) + d <- data$observed_cohens_d + variance <- data$sampling_variance_d + weights <- 1 / variance + pooled <- sum(weights * d) / sum(weights) + se <- sqrt(1 / sum(weights)) + z <- pooled / se + p <- 2 * pnorm(-abs(z)) + ci_low <- pooled - 1.96 * se + ci_high <- pooled + 1.96 * se + q <- sum(weights * (d - pooled)^2) + df_q <- length(d) - 1 + p_q <- pchisq(q, df = df_q, lower.tail = FALSE) + i2 <- if (q > 0) max(0, (q - df_q) / q * 100) else 0 + n_for_power <- function(effect_size, power, alpha = 0.05) { + z_alpha <- qnorm(1 - alpha / 2) + z_power <- qnorm(power) + 2 * ((z_alpha + z_power) / effect_size)^2 + } + n80 <- n_for_power(0.40, 0.80) + n90 <- n_for_power(0.40, 0.90) + metrics <- c( + study_count = nrow(data), + total_participants = sum(data$total_n), + target_effect_size_d = 0.40, + planning_alpha_two_sided = 0.05, + planning_power_primary = 0.80, + planning_n_per_group_unrounded_80 = n80, + planning_n_per_group_ceiling_80 = ceiling(n80), + planning_power_sensitivity = 0.90, + planning_n_per_group_unrounded_90 = n90, + planning_n_per_group_ceiling_90 = ceiling(n90), + minimum_observed_d = min(d), + maximum_observed_d = max(d), + fixed_effect_pooled_d = pooled, + fixed_effect_se = se, + fixed_effect_z = z, + fixed_effect_p = p, + fixed_effect_ci_95_low = ci_low, + fixed_effect_ci_95_high = ci_high, + cochran_q = q, + cochran_q_df = df_q, + cochran_q_p = p_q, + i_squared_percent = i2 + ) + write_metrics(file.path(output_root, "SWL-S10.csv"), metrics) +} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/run_all.R b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/run_all.R new file mode 100755 index 0000000..8c3d2aa --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/run_all.R @@ -0,0 +1,9 @@ +args <- commandArgs(trailingOnly=TRUE) +if (length(args)!=1) stop("usage: Rscript scripts/r/run_all.R ROOT") +root <- normalizePath(args[[1]],mustWork=TRUE) +for (i in 1:10) { + study <- sprintf("SWL-S%02d",i) + status <- system2("Rscript",c(file.path(root,"scripts/r/run_study.R"),root,study)) + if (status!=0) stop(sprintf("R study failed: %s",study)) +} +cat("PYSTATSV1_PSYCH_DESIGN_ALL_R_OK\n") diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/run_study.R b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/run_study.R new file mode 100755 index 0000000..215b827 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/r/run_study.R @@ -0,0 +1,30 @@ +args <- commandArgs(trailingOnly = TRUE) +if (length(args) != 2) stop("usage: Rscript scripts/r/run_study.R ROOT STUDY_ID") +root <- normalizePath(args[[1]], mustWork = TRUE) +study_id <- toupper(gsub("_", "-", args[[2]])) +map <- list( + "SWL-S01"=c("swl_s01_s06_s07_v0_1","swl_s01","run_swl_s01","SWL-S01.csv"), + "SWL-S02"=c("swl_s02_s05_v0_1","swl_s02","run_swl_s02","SWL-S02_r_results.csv"), + "SWL-S03"=c("swl_s02_s05_v0_1","swl_s03","run_swl_s03","SWL-S03_r_results.csv"), + "SWL-S04"=c("swl_s02_s05_v0_1","swl_s04","run_swl_s04","SWL-S04_r_results.csv"), + "SWL-S05"=c("swl_s02_s05_v0_1","swl_s05","run_swl_s05","SWL-S05_r_results.csv"), + "SWL-S06"=c("swl_s01_s06_s07_v0_1","swl_s06","run_swl_s06","SWL-S06.csv"), + "SWL-S07"=c("swl_s01_s06_s07_v0_1","swl_s07","run_swl_s07","SWL-S07.csv"), + "SWL-S08"=c("swl_s08_s10_v0_1","swl_s08","run_swl_s08","SWL-S08.csv"), + "SWL-S09"=c("swl_s08_s10_v0_1","swl_s09","run_swl_s09","SWL-S09.csv"), + "SWL-S10"=c("swl_s08_s10_v0_1","swl_s10","run_swl_s10","SWL-S10.csv") +) +entry <- map[[study_id]] +if (is.null(entry)) stop(sprintf("unknown study ID: %s", study_id)) +batch <- entry[[1]]; slug <- entry[[2]]; function_name <- entry[[3]]; raw_name <- entry[[4]] +base <- file.path(root,"scripts","r","batches",batch) +source(file.path(base,"common.R"), local=.GlobalEnv) +source(file.path(base,paste0(slug,".R")), local=.GlobalEnv) +out <- file.path(root,"outputs",tolower(gsub("-","_",study_id))) +dir.create(out,recursive=TRUE,showWarnings=FALSE) +do.call(function_name,list(root,out)) +raw <- file.path(out,raw_name); final <- file.path(out,"r_result.csv") +if (!file.exists(raw)) stop(sprintf("R study did not write expected file: %s", raw)) +if (file.exists(final)) file.remove(final) +if (!file.rename(raw,final)) stop("could not normalize R result filename") +cat(sprintf("PYSTATSV1_PSYCH_DESIGN_R_STUDY_OK study=%s result=%s\n",study_id,final)) diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/__init__.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/__init__.py new file mode 100755 index 0000000..8b13789 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/__init__.py @@ -0,0 +1 @@ + diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_all.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_all.py new file mode 100755 index 0000000..6ab8629 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_all.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 +from verify_study import verify +if __name__ == '__main__': + for i in range(1, 11): + verify(f'SWL-S{i:02d}') + print('PYSTATSV1_PSYCH_DESIGN_ALL_VERIFICATION_OK') diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_study.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_study.py new file mode 100755 index 0000000..7086778 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_study.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +from __future__ import annotations +import argparse +import json +import sys +from pathlib import Path +PYTHON_SCRIPTS = Path(__file__).resolve().parents[1] / 'python' +if str(PYTHON_SCRIPTS) not in sys.path: + sys.path.insert(0, str(PYTHON_SCRIPTS)) +from companion_runtime import ( # noqa: E402 + ROOT, + normalize_study_id, + read_r_metrics, + sha256, + study_index, + write_json, +) + +def verify(study_id: str): + sid = normalize_study_id(study_id) + row = study_index()[sid] + slug = sid.lower().replace('-', '_') + out = ROOT / 'outputs' / slug + py_path = out / 'python_result.json' + r_path = out / 'r_result.csv' + if not py_path.is_file(): + raise SystemExit(f'missing Python result: {py_path}') + if not r_path.is_file(): + raise SystemExit(f'missing independent R result: {r_path}') + py = json.loads(py_path.read_text(encoding='utf-8'))['reported_fields'] + r = read_r_metrics(r_path) + policy = json.loads((ROOT / 'VERIFICATION_POLICY.json').read_text(encoding='utf-8')) + p = next((x for x in policy['studies'] if x['study_id'] == sid)) + rows = [] + failures = [] + for field in p['parity_fields']: + if field not in py or field not in r: + failures.append(f'missing field {field}') + continue + tolerance = float(p.get('absolute_tolerance_by_field', {}).get(field, p['default_absolute_tolerance'])) + difference = abs(float(py[field]) - float(r[field])) + passed = difference <= tolerance + rows.append({'field': field, 'python': float(py[field]), 'r': float(r[field]), 'absolute_difference': difference, 'allowed_absolute_difference': tolerance, 'status': 'pass' if passed else 'fail'}) + if not passed: + failures.append(f'{field}: difference={difference} allowed={tolerance}') + payload = {'schema_version': 'psych-design-verification-receipt-v0.1', 'study_id': sid, 'verification_category': row['verification_category'], 'status': 'fail' if failures else 'pass', 'comparisons': rows, 'python_result_sha256': sha256(py_path), 'r_result_sha256': sha256(r_path), 'v4_documented_not_equal_fields': p.get('v4_documented_not_equal_fields', []), 'synthetic_only': True} + path = out / 'verification_receipt.json' + write_json(path, payload) + if failures: + raise SystemExit('\n'.join(failures)) + print(f"PYSTATSV1_PSYCH_DESIGN_VERIFICATION_OK study={sid} category={row['verification_category']}") +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--study', required=True) + args = parser.parse_args() + verify(args.study) diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s01.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s01.py new file mode 100755 index 0000000..8f83ba0 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s01.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python3 +from verify_study import verify +if __name__ == '__main__': + verify('SWL-S01') diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s02.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s02.py new file mode 100755 index 0000000..a0b0fb8 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s02.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python3 +from verify_study import verify +if __name__ == '__main__': + verify('SWL-S02') diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s03.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s03.py new file mode 100755 index 0000000..284aaad --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s03.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python3 +from verify_study import verify +if __name__ == '__main__': + verify('SWL-S03') diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s04.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s04.py new file mode 100755 index 0000000..7a87401 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s04.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python3 +from verify_study import verify +if __name__ == '__main__': + verify('SWL-S04') diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s05.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s05.py new file mode 100755 index 0000000..48db4ea --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s05.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python3 +from verify_study import verify +if __name__ == '__main__': + verify('SWL-S05') diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s06.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s06.py new file mode 100755 index 0000000..2f2c2bf --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s06.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python3 +from verify_study import verify +if __name__ == '__main__': + verify('SWL-S06') diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s07.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s07.py new file mode 100755 index 0000000..1a52d2d --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s07.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python3 +from verify_study import verify +if __name__ == '__main__': + verify('SWL-S07') diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s08.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s08.py new file mode 100755 index 0000000..39d35c7 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s08.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python3 +from verify_study import verify +if __name__ == '__main__': + verify('SWL-S08') diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s09.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s09.py new file mode 100755 index 0000000..7665dd4 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s09.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python3 +from verify_study import verify +if __name__ == '__main__': + verify('SWL-S09') diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s10.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s10.py new file mode 100755 index 0000000..393174b --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/scripts/verification/verify_swl_s10.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python3 +from verify_study import verify +if __name__ == '__main__': + verify('SWL-S10') diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/ADAPTATION_DELTA_LEDGER.yaml b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/ADAPTATION_DELTA_LEDGER.yaml new file mode 100644 index 0000000..ac09caa --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/ADAPTATION_DELTA_LEDGER.yaml @@ -0,0 +1,12 @@ +synthetic_example: Psychological Statistics by Design companion v0.1 +reader_project_authorized: false +deltas: + unit: {synthetic: DECLARE, reader_project: DECLARE, revalidation: independence_and_clustering} + outcome: {synthetic: DECLARE, reader_project: DECLARE, revalidation: scoring_validity_and_missing_items} + group_or_exposure: {synthetic: DECLARE, reader_project: DECLARE, revalidation: selection_and_causal_scope} + occasion: {synthetic: DECLARE, reader_project: DECLARE, revalidation: linkage_and_missing_visits} + estimand: {synthetic: DECLARE, reader_project: DECLARE, revalidation: scientific_meaning} + rows: {synthetic: DECLARE, reader_project: DECLARE, revalidation: exclusions_and_missingness} + model: {synthetic: DECLARE, reader_project: DECLARE, revalidation: assumptions_and_diagnostics} + verification: {synthetic: DECLARE, reader_project: DECLARE, revalidation: category_and_tolerance} + reporting: {synthetic: DECLARE, reader_project: DECLARE, revalidation: claim_and_limitation} diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/ANALYSIS_PLAN.yaml b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/ANALYSIS_PLAN.yaml new file mode 100644 index 0000000..8894c8c --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/ANALYSIS_PLAN.yaml @@ -0,0 +1,6 @@ +research_target: DECLARE +estimand_and_direction: DECLARE +analysis_rows: DECLARE +model: DECLARE +assumptions_and_diagnostics: [DECLARE] +comparison_family: [DECLARE] diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/CLAIM_MAP.yaml b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/CLAIM_MAP.yaml new file mode 100644 index 0000000..beba296 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/CLAIM_MAP.yaml @@ -0,0 +1,4 @@ +primary_claim: DECLARE +claim_type: DECLARE +stronger_prohibited_claims: [DECLARE] +generalization_boundary: DECLARE diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/DATA_LAYOUT_CONTRACT.yaml b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/DATA_LAYOUT_CONTRACT.yaml new file mode 100644 index 0000000..ed12433 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/DATA_LAYOUT_CONTRACT.yaml @@ -0,0 +1,7 @@ +unit: DECLARE +rows: DECLARE +identifiers: [DECLARE] +unique_key: [DECLARE] +factor_levels: {} +occasion_schedule: [] +missingness_representation: DECLARE diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/MATCHED_LIMITATION.md b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/MATCHED_LIMITATION.md new file mode 100644 index 0000000..e9720eb --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/MATCHED_LIMITATION.md @@ -0,0 +1,3 @@ +# Matched limitation + +State the limitation that follows from the declared design, measurement, model, missingness, and target. diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/MEASUREMENT_MAP.yaml b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/MEASUREMENT_MAP.yaml new file mode 100644 index 0000000..fc20779 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/MEASUREMENT_MAP.yaml @@ -0,0 +1,5 @@ +constructs: [DECLARE] +operational_definitions: [DECLARE] +score_direction: DECLARE +missing_item_rule: DECLARE +validity_evidence_status: DECLARE diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/REPORTING_SOURCE_MAP.yaml b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/REPORTING_SOURCE_MAP.yaml new file mode 100644 index 0000000..20bb812 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/REPORTING_SOURCE_MAP.yaml @@ -0,0 +1,4 @@ +result_receipt: DECLARE +field_bindings: [] +figure_sources: [] +rendered_sentence_source: DECLARE diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/STUDY_MAP.yaml b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/STUDY_MAP.yaml new file mode 100644 index 0000000..1435972 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/STUDY_MAP.yaml @@ -0,0 +1,6 @@ +study_id: NEW-AUTHORIZED-STUDY +unit_of_analysis: DECLARE +unique_key: [DECLARE] +expected_rows: DECLARE +row_relationships: DECLARE +authorization_reference: DECLARE_OUTSIDE_PUBLIC_COMPANION diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/VERIFICATION_CONTRACT.yaml b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/VERIFICATION_CONTRACT.yaml new file mode 100644 index 0000000..b4b79e2 --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/templates/VERIFICATION_CONTRACT.yaml @@ -0,0 +1,5 @@ +verification_category: DECLARE_V1_V2_V3_OR_V4 +independent_implementation: DECLARE +common_fields: [DECLARE] +field_tolerances: {} +software_specific_fields: [DECLARE] diff --git a/psych_design_companion/psychological_statistics_by_design_companion_v0_1/tests/test_companion_contract.py b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/tests/test_companion_contract.py new file mode 100644 index 0000000..710804b --- /dev/null +++ b/psych_design_companion/psychological_statistics_by_design_companion_v0_1/tests/test_companion_contract.py @@ -0,0 +1,12 @@ +from __future__ import annotations +import json +from pathlib import Path +ROOT=Path(__file__).resolve().parents[1] +def test_study_index_has_ten_nonpublic_synthetic_studies(): + data=json.loads((ROOT/'STUDY_INDEX.json').read_text()) + assert [x['study_id'] for x in data['studies']] == [f'SWL-S{i:02d}' for i in range(1,11)] + assert data['status']=='release_candidate_implemented_not_public' + assert data['synthetic_only'] is True and data['real_data_authorized'] is False +def test_no_placeholder_contract_text(): + text=(ROOT/'STUDY_INDEX.json').read_text().lower() + assert 'to be copied' not in text and 'not yet implemented' not in text diff --git a/pyproject.toml b/pyproject.toml index 009498c..3c01cbf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pystatsv1" -version = "0.25.2" +version = "0.26.0" description = "PyStatsV1: applied statistics labs in Python." readme = "README.md" requires-python = ">=3.10" @@ -37,6 +37,15 @@ book1 = [ "matplotlib>=3.8", ] +# Psychological Statistics by Design companion release-candidate environment. +psych-design = [ + "numpy==2.2.6", + "pandas==2.2.3", + "scipy==1.15.3", + "statsmodels==0.14.6", + "matplotlib==3.10.8", +] + # Contributor bundle: formatting + lint + tests. dev = [ "pytest>=8.2", diff --git a/src/pystatsv1/assets/psychological_statistics_by_design_companion_v0_1.zip b/src/pystatsv1/assets/psychological_statistics_by_design_companion_v0_1.zip new file mode 100644 index 0000000000000000000000000000000000000000..ad56ed7e4b97efb3693a2eb09a35c644857438cd GIT binary patch literal 136801 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

2E? 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 IxI#(21B{{vjsO4v literal 0 HcmV?d00001 diff --git a/src/pystatsv1/cli.py b/src/pystatsv1/cli.py index 2af308a..0c94b56 100644 --- a/src/pystatsv1/cli.py +++ b/src/pystatsv1/cli.py @@ -1,6 +1,7 @@ from __future__ import annotations import argparse +import json import os import platform import re @@ -13,6 +14,14 @@ from typing import Final from pystatsv1.book1 import DEFAULT_DEST_NAME, initialize_book1, packaged_book1_info, verify_book1_directory +from pystatsv1.psych_design import ( + DEFAULT_DEST_NAME as PSYCH_DESIGN_DEFAULT_DEST_NAME, + initialize_psych_design, + packaged_psych_design_info, + packaged_study_index, + psych_design_doctor, + verify_psych_design_directory, +) PKG: Final[str] = "pystatsv1" @@ -225,6 +234,91 @@ def cmd_book1_verify(args: argparse.Namespace) -> int: return 0 +def cmd_psych_design_info(_: argparse.Namespace) -> int: + manifest = packaged_psych_design_info() + print( + textwrap.dedent( + f"""\ + Psychological Statistics by Design — Executable Companion + Companion version: {manifest["companion_version"]} + PyStatsV1 release candidate: {manifest["package_version"]} + Source files: {len(manifest["files"])} + Studies: {len(manifest["study_ids"])} + First result: {manifest["first_result_study"]} + Synthetic data only: {manifest["synthetic_data_only"]} + Public release status: {manifest["release_status"]} + Default destination: {PSYCH_DESIGN_DEFAULT_DEST_NAME} + """ + ).rstrip() + ) + return 0 + + +def cmd_psych_design_init(args: argparse.Namespace) -> int: + try: + result = initialize_psych_design(Path(args.dest)) + except RuntimeError as exc: + raise SystemExit(str(exc)) from exc + print( + textwrap.dedent( + f"""\ + OK: Psychological Statistics by Design companion created at: + + {result.destination} + + Companion version: {result.companion_version} + Verified source files: {result.file_count} + + Next steps: + 1) cd {result.destination} + 2) python -m pip install -r requirements-psych-design-companion.txt + 3) make first-result # requires Rscript for the complete V2 chain + 4) make all # reproduces all ten synthetic studies + 5) pystatsv1 psych-design verify --dest . + + This is a synthetic-only release candidate. It does not authorize real data. + """ + ).rstrip() + ) + return 0 + + +def cmd_psych_design_verify(args: argparse.Namespace) -> int: + try: + result = verify_psych_design_directory(Path(args.dest)) + except RuntimeError as exc: + raise SystemExit(str(exc)) from exc + print( + f"PYSTATSV1_PSYCH_DESIGN_VERIFY_OK companion_version={result.companion_version} " + f"source_files={result.file_count} destination={result.destination}" + ) + return 0 + + +def cmd_psych_design_list_studies(_: argparse.Namespace) -> int: + index = packaged_study_index() + for row in index["studies"]: + chapters = ",".join(row["book_chapters"]) + print( + f"{row['study_id']} | chapters {chapters} | {row['claim_type']} | " + f"{row['reader_command']}" + ) + return 0 + + +def cmd_psych_design_doctor(args: argparse.Namespace) -> int: + try: + payload = psych_design_doctor(Path(args.dest)) + except RuntimeError as exc: + raise SystemExit(str(exc)) from exc + print(json.dumps(payload, indent=2, sort_keys=True)) + if payload["source_manifest_status"] == "pass": + print("PYSTATSV1_PSYCH_DESIGN_DOCTOR_OK") + else: + print("PYSTATSV1_PSYCH_DESIGN_DOCTOR_INFO source_manifest=not_checked") + return 0 + + def cmd_workbook_list(args: argparse.Namespace) -> int: track = _normalize_track(getattr(args, "track", "c")) @@ -618,6 +712,58 @@ def build_parser() -> argparse.ArgumentParser: ) p_book1_verify.set_defaults(func=cmd_book1_verify) + p_psych_design = sub.add_parser( + "psych-design", + help="Psychological Statistics by Design companion helpers.", + ) + psych_design_sub = p_psych_design.add_subparsers( + dest="psych_design_cmd", required=True + ) + + p_psych_design_info = psych_design_sub.add_parser( + "info", help="Show packaged psych-design companion identity." + ) + p_psych_design_info.set_defaults(func=cmd_psych_design_info) + + p_psych_design_init = psych_design_sub.add_parser( + "init", help="Create a new local psych-design companion folder." + ) + p_psych_design_init.add_argument( + "--dest", + default=PSYCH_DESIGN_DEFAULT_DEST_NAME, + help=( + "New destination directory (default: " + + PSYCH_DESIGN_DEFAULT_DEST_NAME + + "). The launcher never overwrites an existing directory." + ), + ) + p_psych_design_init.set_defaults(func=cmd_psych_design_init) + + p_psych_design_verify = psych_design_sub.add_parser( + "verify", help="Verify immutable psych-design source files." + ) + p_psych_design_verify.add_argument( + "--dest", + default=PSYCH_DESIGN_DEFAULT_DEST_NAME, + help="Psych-design companion directory to verify.", + ) + p_psych_design_verify.set_defaults(func=cmd_psych_design_verify) + + p_psych_design_list = psych_design_sub.add_parser( + "list-studies", help="List the ten registered synthetic studies." + ) + p_psych_design_list.set_defaults(func=cmd_psych_design_list_studies) + + p_psych_design_doctor = psych_design_sub.add_parser( + "doctor", help="Report companion-specific environment and source facts." + ) + p_psych_design_doctor.add_argument( + "--dest", + default=PSYCH_DESIGN_DEFAULT_DEST_NAME, + help="Companion directory to verify when it exists.", + ) + p_psych_design_doctor.set_defaults(func=cmd_psych_design_doctor) + p_wb = sub.add_parser("workbook", help="Workbook helpers (student labs).") wb_sub = p_wb.add_subparsers(dest="workbook_cmd", required=True) diff --git a/src/pystatsv1/psych_design.py b/src/pystatsv1/psych_design.py new file mode 100644 index 0000000..1e6606a --- /dev/null +++ b/src/pystatsv1/psych_design.py @@ -0,0 +1,270 @@ +"""Launcher and source verification for the Psychological Statistics by Design companion. + +This module provisions and verifies a versioned synthetic-only teaching companion. +It does not choose statistical methods, analyze real data, grant data authorization, +or establish scientific or causal validity. +""" +from __future__ import annotations + +import hashlib +import json +import os +import platform +import shutil +import stat +import sys +import tempfile +import zipfile +from dataclasses import dataclass +from importlib import metadata, resources +from pathlib import Path, PurePosixPath +from typing import Final + +PKG: Final[str] = "pystatsv1" +ASSET_PACKAGE: Final[str] = f"{PKG}.assets" +ASSET_NAME: Final[str] = "psychological_statistics_by_design_companion_v0_1.zip" +DEFAULT_DEST_NAME: Final[str] = "psychological_statistics_by_design_companion_v0_1" +MANIFEST_NAME: Final[str] = "COMPANION_MANIFEST.json" +MANIFEST_SCHEMA: Final[str] = "pystatsv1-psych-design-bundle-manifest-v0.1" +CURRENT_COMPANION_VERSION: Final[str] = "v0.1" +EXPECTED_PACKAGE_VERSION: Final[str] = "0.26.0" +STUDY_INDEX_NAME: Final[str] = "STUDY_INDEX.json" + + +@dataclass(frozen=True) +class PsychDesignVerification: + destination: Path + companion_version: str + file_count: int + + +def _sha256(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def _safe_relative(name: str) -> PurePosixPath: + pure = PurePosixPath(name) + if not name or pure.is_absolute() or ".." in pure.parts or "." in pure.parts: + raise ValueError(f"unsafe psych-design bundle member path: {name!r}") + if "\\" in name: + raise ValueError( + f"psych-design bundle member must use POSIX separators: {name!r}" + ) + return pure + + +def _asset_path(): + return resources.files(ASSET_PACKAGE) / ASSET_NAME + + +def _read_manifest_from_zip(zf: zipfile.ZipFile) -> dict: + try: + payload = json.loads(zf.read(MANIFEST_NAME).decode("utf-8")) + except KeyError as exc: + raise RuntimeError(f"packaged psych-design asset is missing {MANIFEST_NAME}") from exc + except json.JSONDecodeError as exc: + raise RuntimeError(f"packaged psych-design asset has invalid {MANIFEST_NAME}") from exc + if payload.get("schema_version") != MANIFEST_SCHEMA: + raise RuntimeError("packaged psych-design manifest schema is not supported") + if payload.get("companion_version") != CURRENT_COMPANION_VERSION: + raise RuntimeError("packaged psych-design companion version is not supported") + if payload.get("package_version") != EXPECTED_PACKAGE_VERSION: + raise RuntimeError("packaged psych-design package version is not 0.26.0") + if payload.get("synthetic_data_only") is not True: + raise RuntimeError("packaged psych-design asset must remain synthetic-only") + if payload.get("real_data_authorized") is not False: + raise RuntimeError("packaged psych-design asset may not authorize real data") + rows = payload.get("files") + if not isinstance(rows, list) or not rows: + raise RuntimeError("packaged psych-design manifest has no source file records") + return payload + + +def _validate_zip_members(zf: zipfile.ZipFile, manifest: dict) -> None: + names: set[str] = set() + for info in zf.infolist(): + if info.is_dir(): + continue + _safe_relative(info.filename) + if stat.S_ISLNK(info.external_attr >> 16): + raise RuntimeError( + f"packaged psych-design asset contains a symlink: {info.filename}" + ) + if info.filename in names: + raise RuntimeError( + f"packaged psych-design asset contains a duplicate member: {info.filename}" + ) + names.add(info.filename) + expected = {str(row.get("path", "")) for row in manifest["files"]} + if not expected or any(not value for value in expected): + raise RuntimeError("packaged psych-design manifest contains an invalid path") + if expected | {MANIFEST_NAME} != names: + missing = sorted(expected - names) + extra = sorted(names - expected - {MANIFEST_NAME}) + details = [] + if missing: + details.append("missing: " + ", ".join(missing)) + if extra: + details.append("unexpected: " + ", ".join(extra)) + raise RuntimeError( + "packaged psych-design members do not match manifest (" + + "; ".join(details) + + ")" + ) + for row in manifest["files"]: + name = str(row["path"]) + _safe_relative(name) + actual = hashlib.sha256(zf.read(name)).hexdigest() + if actual != row.get("sha256"): + raise RuntimeError(f"packaged psych-design asset hash mismatch: {name}") + + +def packaged_psych_design_info() -> dict: + asset = _asset_path() + with resources.as_file(asset) as asset_path: + with zipfile.ZipFile(asset_path) as zf: + manifest = _read_manifest_from_zip(zf) + _validate_zip_members(zf, manifest) + return manifest + + +def packaged_study_index() -> dict: + asset = _asset_path() + with resources.as_file(asset) as asset_path: + with zipfile.ZipFile(asset_path) as zf: + manifest = _read_manifest_from_zip(zf) + _validate_zip_members(zf, manifest) + try: + payload = json.loads(zf.read(STUDY_INDEX_NAME).decode("utf-8")) + except (KeyError, json.JSONDecodeError) as exc: + raise RuntimeError("packaged psych-design study index is invalid") from exc + if payload.get("status") != "release_candidate_implemented_not_public": + raise RuntimeError("packaged psych-design study index has an invalid lifecycle status") + studies = payload.get("studies") + if not isinstance(studies, list) or [row.get("study_id") for row in studies] != [ + f"SWL-S{number:02d}" for number in range(1, 11) + ]: + raise RuntimeError("packaged psych-design study index is incomplete") + return payload + + +def initialize_psych_design(destination: Path) -> PsychDesignVerification: + dest = destination.expanduser().resolve() + if dest.exists(): + raise RuntimeError( + f"refusing to overwrite an existing destination: {dest}\n" + "Choose a new --dest path or inspect the existing folder first." + ) + dest.parent.mkdir(parents=True, exist_ok=True) + staging = Path(tempfile.mkdtemp(prefix=f".{dest.name}.staging-", dir=dest.parent)) + try: + asset = _asset_path() + with resources.as_file(asset) as asset_path: + with zipfile.ZipFile(asset_path) as zf: + manifest = _read_manifest_from_zip(zf) + _validate_zip_members(zf, manifest) + for row in manifest["files"]: + rel = _safe_relative(str(row["path"])) + output = staging.joinpath(*rel.parts) + output.parent.mkdir(parents=True, exist_ok=True) + output.write_bytes(zf.read(rel.as_posix())) + output.chmod(int(str(row.get("mode", "644")), 8)) + (staging / MANIFEST_NAME).write_bytes(zf.read(MANIFEST_NAME)) + verified = verify_psych_design_directory(staging) + os.replace(staging, dest) + return PsychDesignVerification( + dest, verified.companion_version, verified.file_count + ) + except Exception: + shutil.rmtree(staging, ignore_errors=True) + raise + + +def verify_psych_design_directory(destination: Path) -> PsychDesignVerification: + dest = destination.expanduser().resolve() + manifest_path = dest / MANIFEST_NAME + if not manifest_path.is_file(): + raise RuntimeError(f"missing psych-design manifest: {manifest_path}") + try: + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + except json.JSONDecodeError as exc: + raise RuntimeError(f"invalid psych-design manifest: {manifest_path}") from exc + if manifest.get("schema_version") != MANIFEST_SCHEMA: + raise RuntimeError("psych-design manifest schema is not supported") + if manifest.get("companion_version") != CURRENT_COMPANION_VERSION: + raise RuntimeError("psych-design companion version is not supported") + if manifest.get("package_version") != EXPECTED_PACKAGE_VERSION: + raise RuntimeError("psych-design manifest package version is not 0.26.0") + if manifest.get("synthetic_data_only") is not True: + raise RuntimeError("psych-design bundle must remain synthetic-only") + if manifest.get("real_data_authorized") is not False: + raise RuntimeError("psych-design bundle may not authorize real data") + rows = manifest.get("files") + if not isinstance(rows, list) or not rows: + raise RuntimeError("psych-design manifest has no source file records") + for row in rows: + if not isinstance(row, dict): + raise RuntimeError("psych-design manifest has an invalid file record") + rel = _safe_relative(str(row.get("path", ""))) + path = dest.joinpath(*rel.parts) + if not path.is_file(): + raise RuntimeError(f"psych-design source file is missing: {rel.as_posix()}") + expected = row.get("sha256") + if not isinstance(expected, str) or len(expected) != 64: + raise RuntimeError( + f"psych-design manifest has invalid hash for: {rel.as_posix()}" + ) + if _sha256(path) != expected: + raise RuntimeError( + f"psych-design source file hash mismatch: {rel.as_posix()}" + ) + index = json.loads((dest / STUDY_INDEX_NAME).read_text(encoding="utf-8")) + if len(index.get("studies", [])) != 10: + raise RuntimeError("psych-design extracted study index is incomplete") + return PsychDesignVerification(dest, CURRENT_COMPANION_VERSION, len(rows)) + + +def psych_design_doctor(destination: Path) -> dict: + dest = destination.expanduser().resolve() + package_version: str + try: + package_version = metadata.version(PKG) + except metadata.PackageNotFoundError: + package_version = "not installed" + dependencies = {} + for module_name, dist_name in ( + ("numpy", "numpy"), + ("pandas", "pandas"), + ("scipy", "scipy"), + ("statsmodels", "statsmodels"), + ("matplotlib", "matplotlib"), + ): + try: + __import__(module_name) + dependencies[module_name] = metadata.version(dist_name) + except Exception: + dependencies[module_name] = "not installed" + verification = "not checked" + source_files = 0 + if dest.is_dir(): + result = verify_psych_design_directory(dest) + verification = "pass" + source_files = result.file_count + return { + "python_version": platform.python_version(), + "python_executable": sys.executable, + "platform": platform.platform(), + "supported_platform_status": ( + "Ubuntu/WSL proof target; macOS and Windows wheel source checks only" + ), + "pystatsv1_version": package_version, + "companion_version": CURRENT_COMPANION_VERSION, + "companion_root": str(dest), + "source_manifest_status": verification, + "source_file_count": source_files, + "dependencies": dependencies, + "rscript_available": shutil.which("Rscript") is not None, + "rscript_path": shutil.which("Rscript"), + "synthetic_data_only": True, + "real_data_authorized": False, + } diff --git a/tests/test_book1_launcher.py b/tests/test_book1_launcher.py index e664b6c..e80ac0d 100644 --- a/tests/test_book1_launcher.py +++ b/tests/test_book1_launcher.py @@ -412,7 +412,7 @@ def test_book1_historical_and_current_release_bindings_are_explicit() -> None: pyproject = (ROOT / "pyproject.toml").read_text(encoding="utf-8") assert "pystatsv1>=0.25.0,<0.26.0" in old_requirements assert "pystatsv1>=0.25.2,<0.26.0" in new_requirements - assert 'version = "0.25.2"' in pyproject + assert 'version = "0.26.0"' in pyproject def test_book1_v021_design_audit_source_route_passes_without_writing( tmp_path: Path, diff --git a/tests/test_psych_design_release_candidate.py b/tests/test_psych_design_release_candidate.py new file mode 100644 index 0000000..7976028 --- /dev/null +++ b/tests/test_psych_design_release_candidate.py @@ -0,0 +1,366 @@ +from __future__ import annotations + +import csv +import hashlib +import json +import stat +import subprocess +import sys +import zipfile +from pathlib import Path + +import pytest + +from pystatsv1.psych_design import ( + ASSET_NAME, + CURRENT_COMPANION_VERSION, + EXPECTED_PACKAGE_VERSION, + MANIFEST_NAME, + _read_manifest_from_zip, + _validate_zip_members, + initialize_psych_design, + packaged_psych_design_info, + packaged_study_index, + psych_design_doctor, + verify_psych_design_directory, +) + +ROOT = Path(__file__).resolve().parents[1] +SOURCE = ( + ROOT + / "psych_design_companion" + / "psychological_statistics_by_design_companion_v0_1" +) +ASSET = ROOT / "src" / "pystatsv1" / "assets" / ASSET_NAME +BUILDER = ROOT / "tools" / "build_psych_design_companion_asset.py" +EXPECTED_STUDIES = [f"SWL-S{number:02d}" for number in range(1, 11)] + + +def run(*command: str, cwd: Path = ROOT) -> subprocess.CompletedProcess[str]: + return subprocess.run( + list(command), + cwd=cwd, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=False, + ) + + +def sha256(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def test_psych_design_registry_is_complete_and_has_no_placeholders() -> None: + index = json.loads((SOURCE / "STUDY_INDEX.json").read_text(encoding="utf-8")) + assert index["status"] == "release_candidate_implemented_not_public" + assert index["companion_version"] == CURRENT_COMPANION_VERSION + assert index["package_release_candidate"] == EXPECTED_PACKAGE_VERSION + assert index["synthetic_only"] is True + assert index["real_data_authorized"] is False + assert [row["study_id"] for row in index["studies"]] == EXPECTED_STUDIES + assert len({row["python_script"] for row in index["studies"]}) == 10 + serialized = json.dumps(index).lower() + for placeholder in ( + "to be copied from the governed", + "target_contract_not_yet_implemented", + "contract_not_implemented", + ): + assert placeholder not in serialized + for row in index["studies"]: + for key in ( + "dataset_path", + "design_contract_path", + "measurement_map_path", + "python_script", + "r_launcher", + "r_script", + "verification_script", + "reference_python_result", + ): + assert (SOURCE / row[key]).is_file(), (row["study_id"], key) + assert row["reader_command"] == f"make study STUDY={row['study_id']}" + assert row["verification_command"] == ( + f"make verify-study STUDY={row['study_id']}" + ) + assert row["packet_command"] == f"make packet STUDY={row['study_id']}" + + +def test_psych_design_asset_is_exactly_reproducible(tmp_path: Path) -> None: + first = tmp_path / "first.zip" + second = tmp_path / "second.zip" + for destination in (first, second): + completed = run( + sys.executable, + str(BUILDER), + "--dest", + str(destination), + ) + assert completed.returncode == 0, completed.stdout + completed.stderr + assert sha256(first) == sha256(second) == sha256(ASSET) + + +def test_packaged_psych_design_manifest_and_index_are_bound() -> None: + manifest = packaged_psych_design_info() + index = packaged_study_index() + assert manifest["companion_version"] == "v0.1" + assert manifest["package_version"] == "0.26.0" + assert manifest["release_status"] == "release_candidate_not_public" + assert manifest["synthetic_data_only"] is True + assert manifest["real_data_authorized"] is False + assert manifest["study_ids"] == EXPECTED_STUDIES + assert manifest["first_result_study"] == "SWL-S02" + assert [row["study_id"] for row in index["studies"]] == EXPECTED_STUDIES + assert len(manifest["files"]) >= 140 + + +def test_psych_design_init_verify_and_generated_output_boundary(tmp_path: Path) -> None: + destination = tmp_path / "companion" + result = initialize_psych_design(destination) + assert result.companion_version == "v0.1" + assert result.file_count >= 140 + assert (destination / "STUDY_INDEX.json").is_file() + before = verify_psych_design_directory(destination) + generated = destination / "outputs" / "reader-note.txt" + generated.parent.mkdir(parents=True) + generated.write_text("reader-generated output\n", encoding="utf-8") + after = verify_psych_design_directory(destination) + assert before.file_count == after.file_count + + +def test_psych_design_init_refuses_existing_destination(tmp_path: Path) -> None: + destination = tmp_path / "existing" + destination.mkdir() + with pytest.raises(RuntimeError, match="refusing to overwrite"): + initialize_psych_design(destination) + + +def test_psych_design_verify_detects_source_tampering(tmp_path: Path) -> None: + destination = tmp_path / "companion" + initialize_psych_design(destination) + readme = destination / "README.md" + readme.write_text( + readme.read_text(encoding="utf-8") + "\ntampered\n", + encoding="utf-8", + ) + with pytest.raises(RuntimeError, match="hash mismatch"): + verify_psych_design_directory(destination) + + +def test_psych_design_doctor_reports_bounded_roles(tmp_path: Path) -> None: + destination = tmp_path / "companion" + initialize_psych_design(destination) + payload = psych_design_doctor(destination) + assert payload["companion_version"] == "v0.1" + assert payload["source_manifest_status"] == "pass" + assert payload["synthetic_data_only"] is True + assert payload["real_data_authorized"] is False + assert "method" not in payload + assert "Ubuntu/WSL" in payload["supported_platform_status"] + + +def test_psych_design_cli_info_list_init_verify_and_doctor(tmp_path: Path) -> None: + destination = tmp_path / "cli-companion" + cases = ( + (["psych-design", "info"], "Companion version: v0.1"), + (["psych-design", "list-studies"], "SWL-S10"), + ( + ["psych-design", "init", "--dest", str(destination)], + "Verified source files:", + ), + ( + ["psych-design", "verify", "--dest", str(destination)], + "PYSTATSV1_PSYCH_DESIGN_VERIFY_OK", + ), + ( + ["psych-design", "doctor", "--dest", str(destination)], + "PYSTATSV1_PSYCH_DESIGN_DOCTOR_OK", + ), + ) + for arguments, marker in cases: + completed = run(sys.executable, "-m", "pystatsv1.cli", *arguments) + assert completed.returncode == 0, completed.stdout + completed.stderr + assert marker in completed.stdout + + +def test_psych_design_archive_rejects_traversal_member(tmp_path: Path) -> None: + path = tmp_path / "unsafe.zip" + with zipfile.ZipFile(path, "w") as archive: + archive.writestr("../escape.txt", "bad") + archive.writestr( + MANIFEST_NAME, + json.dumps( + { + "schema_version": "pystatsv1-psych-design-bundle-manifest-v0.1", + "companion_version": "v0.1", + "package_version": "0.26.0", + "synthetic_data_only": True, + "real_data_authorized": False, + "files": [{"path": "../escape.txt", "sha256": "0" * 64}], + } + ), + ) + with zipfile.ZipFile(path) as archive: + manifest = _read_manifest_from_zip(archive) + with pytest.raises(ValueError, match="unsafe"): + _validate_zip_members(archive, manifest) + + +def test_psych_design_archive_rejects_symlink_member(tmp_path: Path) -> None: + path = tmp_path / "symlink.zip" + data = b"target" + info = zipfile.ZipInfo("README.md") + info.create_system = 3 + info.external_attr = (stat.S_IFLNK | 0o777) << 16 + with zipfile.ZipFile(path, "w") as archive: + archive.writestr(info, data) + archive.writestr( + MANIFEST_NAME, + json.dumps( + { + "schema_version": "pystatsv1-psych-design-bundle-manifest-v0.1", + "companion_version": "v0.1", + "package_version": "0.26.0", + "synthetic_data_only": True, + "real_data_authorized": False, + "files": [ + { + "path": "README.md", + "sha256": hashlib.sha256(data).hexdigest(), + } + ], + } + ), + ) + with zipfile.ZipFile(path) as archive: + manifest = _read_manifest_from_zip(archive) + with pytest.raises(RuntimeError, match="symlink"): + _validate_zip_members(archive, manifest) + + +def test_unified_python_workflow_reproduces_all_ten_reference_results( + tmp_path: Path, +) -> None: + destination = tmp_path / "companion" + initialize_psych_design(destination) + completed = run( + sys.executable, + "scripts/python/run_all.py", + cwd=destination, + ) + assert completed.returncode == 0, completed.stdout + completed.stderr + assert "PYSTATSV1_PSYCH_DESIGN_ALL_PYTHON_OK studies=10" in completed.stdout + for study in EXPECTED_STUDIES: + slug = study.lower().replace("-", "_") + result = destination / "outputs" / slug / "python_result.json" + assert result.is_file(), study + payload = json.loads(result.read_text(encoding="utf-8")) + assert payload["study_id"] == study + assert payload["synthetic_only"] is True + + +def test_swl_s02_mutation_cannot_preserve_governed_result(tmp_path: Path) -> None: + destination = tmp_path / "companion" + initialize_psych_design(destination) + dataset = destination / "data/swl_s02_structured_study_routine.csv" + rows = list(csv.DictReader(dataset.open(encoding="utf-8", newline=""))) + assert rows + for row in rows: + row["study_routine_group"] = ( + "standard_routine" + if row["study_routine_group"] == "structured_routine" + else "structured_routine" + ) + with dataset.open("w", encoding="utf-8", newline="") as handle: + writer = csv.DictWriter(handle, fieldnames=list(rows[0])) + writer.writeheader() + writer.writerows(rows) + completed = run( + sys.executable, + "scripts/python/swl_s02_independent_groups.py", + cwd=destination, + ) + assert completed.returncode != 0 + assert "governed result drift" in (completed.stdout + completed.stderr) + + +def test_reviewer_packet_is_bounded_and_hash_manifested(tmp_path: Path) -> None: + destination = tmp_path / "companion" + initialize_psych_design(destination) + study = "SWL-S02" + slug = "swl_s02" + completed = run( + sys.executable, + "scripts/python/swl_s02_independent_groups.py", + cwd=destination, + ) + assert completed.returncode == 0, completed.stdout + completed.stderr + policy = json.loads( + (destination / "VERIFICATION_POLICY.json").read_text(encoding="utf-8") + ) + fields = next(row for row in policy["studies"] if row["study_id"] == study)[ + "parity_fields" + ] + python_result = json.loads( + (destination / "outputs" / slug / "python_result.json").read_text( + encoding="utf-8" + ) + )["reported_fields"] + r_result = destination / "outputs" / slug / "r_result.csv" + with r_result.open("w", encoding="utf-8", newline="") as handle: + writer = csv.writer(handle) + writer.writerow(["metric", "value"]) + for field in fields: + writer.writerow([field, python_result[field]]) + for command in ( + [sys.executable, "scripts/verification/verify_swl_s02.py"], + [sys.executable, "scripts/python/generate_figures.py", "--study", study], + [sys.executable, "scripts/python/build_reviewer_packet.py", "--study", study], + ): + completed = run(*command, cwd=destination) + assert completed.returncode == 0, completed.stdout + completed.stderr + packet = destination / "outputs" / slug / "reviewer_packet" + manifest = json.loads((packet / "MANIFEST.json").read_text(encoding="utf-8")) + assert manifest["study_id"] == study + assert manifest["synthetic_only"] is True + names = {row["path"] for row in manifest["files"]} + assert { + "STUDY_INDEX_RECORD.json", + "SOURCE_MANIFEST_RECORD.json", + "DESIGN_CONTRACT.json", + "MEASUREMENT_MAP.json", + "DATA_LAYOUT_AUDIT.json", + "PYTHON_RESULT.json", + "R_RESULT.csv", + "VERIFICATION_RECEIPT.json", + "FIGURE_SPECS.json", + "APA_SOURCE_MAP.json", + "MATCHED_LIMITATION.md", + }.issubset(names) + for row in manifest["files"]: + path = packet / row["path"] + assert sha256(path) == row["sha256"] + source_record = json.loads( + (packet / "SOURCE_MANIFEST_RECORD.json").read_text(encoding="utf-8") + ) + assert source_record["source_anchor"] == ( + "e88ff0f6f1aa1c4a946c72b97387f47b3c5311ae" + ) + assert source_record["dataset_path"] == ( + "data/swl_s02_structured_study_routine.csv" + ) + assert source_record["dataset_sha256"] == sha256( + destination / source_record["dataset_path"] + ) + figure_specs = json.loads( + (packet / "FIGURE_SPECS.json").read_text(encoding="utf-8") + ) + assert figure_specs["study_id"] == study + assert len(figure_specs["specifications"]) == 1 + + +def test_packaged_companion_excludes_outputs_and_caches() -> None: + forbidden = {"outputs", "__pycache__", ".pytest_cache", ".venv", "venv"} + with zipfile.ZipFile(ASSET) as archive: + for name in archive.namelist(): + assert not (set(Path(name).parts) & forbidden), name diff --git a/tests/test_psych_design_release_contract.py b/tests/test_psych_design_release_contract.py index 3d6696b..93af471 100644 --- a/tests/test_psych_design_release_contract.py +++ b/tests/test_psych_design_release_contract.py @@ -44,6 +44,7 @@ def test_contract_does_not_claim_release_or_real_data_authorization() -> None: "public_companion_released": False, "pypi_release_authorized": False, "real_data_authorized": False, + "release_candidate_implemented": True, } @@ -56,15 +57,15 @@ def test_source_batch_mutation_is_detected(tmp_path: Path) -> None: assert "preserved source file drift" in (completed.stdout + completed.stderr) -def test_false_command_implementation_claim_is_detected(tmp_path: Path) -> None: +def test_false_command_nonimplementation_claim_is_detected(tmp_path: Path) -> None: repo=copy_repo(tmp_path) path=repo/"psych_design_companion/psych_design_release_contract_v0_1/COMMAND_INTERFACE_CONTRACT.json" data=json.loads(path.read_text()) - data["commands"][0]["implemented"]=True + data["commands"][0]["implemented"]=False path.write_text(json.dumps(data,indent=2,sort_keys=True)+"\n") completed=run(repo) assert completed.returncode != 0 - assert "incorrectly marked implemented" in (completed.stdout + completed.stderr) + assert "marked unavailable" in (completed.stdout + completed.stderr) def test_missing_study_path_is_detected(tmp_path: Path) -> None: diff --git a/tests/test_release_025_metadata.py b/tests/test_release_025_metadata.py index 57a0687..9924772 100644 --- a/tests/test_release_025_metadata.py +++ b/tests/test_release_025_metadata.py @@ -9,16 +9,17 @@ ROOT = Path(__file__).resolve().parents[1] -def test_pyproject_version_is_0252(): +def test_pyproject_version_is_0260_release_candidate(): data = tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8")) - assert data["project"]["version"] == "0.25.2" + assert data["project"]["version"] == "0.26.0" -def test_release_notes_preserve_history_and_add_v0252(): +def test_release_notes_preserve_history_and_add_v0260_candidate(): text = ( ROOT / "docs" / "source" / "release_notes.rst" ).read_text(encoding="utf-8") assert text.startswith("Release notes\n=============\n\n") + assert "v0.26.0 release candidate — Psychological Statistics by Design companion v0.1" in text assert "v0.25.2 — Book 1 Companion v0.2.1 identity correction" in text assert "v0.25.1 — Chapter 20 power stability and release guard" in text assert "v0.25.0 — Book 1 Companion v0.2" in text diff --git a/tools/build_psych_design_companion_asset.py b/tools/build_psych_design_companion_asset.py new file mode 100755 index 0000000..06732d2 --- /dev/null +++ b/tools/build_psych_design_companion_asset.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python3 +"""Build the deterministic Psychological Statistics by Design companion asset.""" +from __future__ import annotations + +import argparse +import hashlib +import json +import os +import stat +import subprocess +import sys +import zipfile +from pathlib import Path, PurePosixPath + +ROOT = Path(__file__).resolve().parents[1] +DEFAULT_SOURCE = ROOT / "psych_design_companion" / "psychological_statistics_by_design_companion_v0_1" +DEFAULT_DEST = ROOT / "src" / "pystatsv1" / "assets" / "psychological_statistics_by_design_companion_v0_1.zip" +MANIFEST_NAME = "COMPANION_MANIFEST.json" +SCHEMA_VERSION = "pystatsv1-psych-design-bundle-manifest-v0.1" +COMPANION_VERSION = "v0.1" +PACKAGE_VERSION = "0.26.0" +EXCLUDED_DIRS = {"outputs", "__pycache__", ".pytest_cache", ".mypy_cache", ".ruff_cache", ".venv", "venv"} +EXCLUDED_SUFFIXES = {".pyc", ".pyo", ".swp", "~"} +REQUIRED_FILES = { + "README.md", + "LICENSE_NOTES.md", + "Makefile", + "COMPANION_PROVENANCE.json", + "SOURCE_LINEAGE.json", + "STUDY_INDEX.json", + "VERIFICATION_POLICY.json", + "requirements-psych-design-companion.txt", + "scripts/python/companion_runtime.py", + "scripts/python/run_study.py", + "scripts/python/run_all.py", + "scripts/python/design_audit.py", + "scripts/python/generate_figures.py", + "scripts/python/build_reviewer_packet.py", + "scripts/r/run_study.R", + "scripts/r/run_all.R", + "scripts/verification/verify_study.py", + "scripts/verification/verify_all.py", + "templates/ADAPTATION_DELTA_LEDGER.yaml", +} +FORBIDDEN_TEXT = ( + "to be copied from the governed", + "target_contract_not_yet_implemented", + "contract_not_implemented", +) + + +def _safe_relative(path: Path, source: Path) -> str: + relative = path.relative_to(source).as_posix() + pure = PurePosixPath(relative) + if not relative or pure.is_absolute() or ".." in pure.parts: + raise ValueError(f"unsafe source path: {relative!r}") + return relative + + +def _include(path: Path, source: Path) -> bool: + relative = Path(_safe_relative(path, source)) + if any(part in EXCLUDED_DIRS for part in relative.parts): + return False + if path.suffix in EXCLUDED_SUFFIXES: + return False + if relative.as_posix() == MANIFEST_NAME: + return False + return True + + +def _sha256_bytes(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() + + +def _load_json(path: Path) -> dict: + try: + return json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as exc: + raise SystemExit(f"invalid JSON source file: {path}") from exc + + +def _logical_mode(relative: str) -> int: + if relative.startswith(("scripts/python/", "scripts/r/", "scripts/verification/")): + return 0o755 + return 0o644 + + +def _source_entries(source: Path) -> list[tuple[str, bytes, int]]: + entries = [] + paths = [path for path in source.rglob("*") if path.is_file()] + for path in sorted(paths, key=lambda item: _safe_relative(item, source)): + if not _include(path, source): + continue + relative = _safe_relative(path, source) + entries.append((relative, path.read_bytes(), _logical_mode(relative))) + return entries + + +def _validate_source(source: Path) -> dict: + if not source.is_dir(): + raise SystemExit(f"missing psych-design companion source: {source}") + names = { + _safe_relative(path, source) + for path in source.rglob("*") + if path.is_file() and _include(path, source) + } + missing = sorted(REQUIRED_FILES - names) + if missing: + raise SystemExit("psych-design companion source is incomplete:\n" + "\n".join(missing)) + index = _load_json(source / "STUDY_INDEX.json") + if index.get("companion_version") != COMPANION_VERSION: + raise SystemExit("psych-design study index companion version is invalid") + if index.get("package_release_candidate") != PACKAGE_VERSION: + raise SystemExit("psych-design study index package version is invalid") + if index.get("status") != "release_candidate_implemented_not_public": + raise SystemExit("psych-design study index lifecycle status is invalid") + if index.get("synthetic_only") is not True or index.get("real_data_authorized") is not False: + raise SystemExit("psych-design study index data boundary is invalid") + studies = index.get("studies", []) + expected_ids = [f"SWL-S{number:02d}" for number in range(1, 11)] + if [row.get("study_id") for row in studies] != expected_ids: + raise SystemExit("psych-design study index must contain SWL-S01 through SWL-S10") + text = (source / "STUDY_INDEX.json").read_text(encoding="utf-8").lower() + for forbidden in FORBIDDEN_TEXT: + if forbidden in text: + raise SystemExit(f"psych-design study index retains placeholder text: {forbidden}") + for row in studies: + required = { + row["dataset_path"], row["design_contract_path"], row["measurement_map_path"], + row["python_script"], row["r_script"], row["verification_script"], + row["reference_python_result"], *row["figure_specs"], *row["apa_source_maps"], *row["matched_limitations"], + } + absent = sorted(path for path in required if path not in names) + if absent: + raise SystemExit(f"{row['study_id']} registry paths are missing:\n" + "\n".join(absent)) + provenance = _load_json(source / "COMPANION_PROVENANCE.json") + expected_false = ( + "real_data_authorized", "portal_release_authorized", "book_binding_authorized", "pypi_release_authorized" + ) + if any(provenance.get(key) is not False for key in expected_false): + raise SystemExit("psych-design source opens a closed lifecycle gate") + if provenance.get("release_status") != "release_candidate_not_public": + raise SystemExit("psych-design source incorrectly implies public release") + environment = os.environ.copy() + environment["PYTHONDONTWRITEBYTECODE"] = "1" + completed = subprocess.run( + [sys.executable, str(source / "scripts/python/design_audit.py"), "--check-only"], + cwd=source, + env=environment, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=False, + ) + if completed.returncode != 0 or "PYSTATSV1_PSYCH_DESIGN_DESIGN_AUDIT_OK" not in completed.stdout: + raise SystemExit("psych-design source design audit failed:\n" + completed.stdout + completed.stderr) + return index + + +def _manifest(entries: list[tuple[str, bytes, int]], index: dict) -> bytes: + payload = { + "schema_version": SCHEMA_VERSION, + "companion_version": COMPANION_VERSION, + "package_version": PACKAGE_VERSION, + "release_status": "release_candidate_not_public", + "synthetic_data_only": True, + "real_data_authorized": False, + "study_ids": [row["study_id"] for row in index["studies"]], + "first_result_study": "SWL-S02", + "files": [ + {"path": name, "sha256": _sha256_bytes(data), "mode": f"{mode:o}"} + for name, data, mode in entries + ], + } + return (json.dumps(payload, indent=2, sort_keys=True) + "\n").encode("utf-8") + + +def _zip_info(name: str, mode: int) -> zipfile.ZipInfo: + info = zipfile.ZipInfo(name, date_time=(1980, 1, 1, 0, 0, 0)) + info.create_system = 3 + info.external_attr = ((stat.S_IFREG | mode) & 0xFFFF) << 16 + info.compress_type = zipfile.ZIP_DEFLATED + return info + + +def build(source: Path, dest: Path) -> None: + source = source.resolve() + index = _validate_source(source) + entries = _source_entries(source) + manifest = _manifest(entries, index) + dest.parent.mkdir(parents=True, exist_ok=True) + with zipfile.ZipFile(dest, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as zf: + for name, data, mode in entries: + zf.writestr(_zip_info(name, mode), data) + zf.writestr(_zip_info(MANIFEST_NAME, 0o644), manifest) + print( + f"PYSTATSV1_PSYCH_DESIGN_ASSET_BUILT companion={COMPANION_VERSION} " + f"source_files={len(entries)} dest={dest}" + ) + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--source", type=Path, default=DEFAULT_SOURCE) + parser.add_argument("--dest", type=Path, default=DEFAULT_DEST) + args = parser.parse_args() + build(args.source, args.dest) + + +if __name__ == "__main__": + main() diff --git a/tools/check_psych_design_release_candidate.py b/tools/check_psych_design_release_candidate.py new file mode 100755 index 0000000..1963ddf --- /dev/null +++ b/tools/check_psych_design_release_candidate.py @@ -0,0 +1,339 @@ +#!/usr/bin/env python3 +"""Deep structural and packaging checks for psych-design companion v0.1 candidate.""" +from __future__ import annotations + +import hashlib +import json +import os +import re +import subprocess +import sys +import tempfile +import zipfile +from pathlib import Path, PurePosixPath + +ROOT = Path(__file__).resolve().parents[1] +SOURCE = ( + ROOT + / "psych_design_companion" + / "psychological_statistics_by_design_companion_v0_1" +) +ASSET = ( + ROOT + / "src" + / "pystatsv1" + / "assets" + / "psychological_statistics_by_design_companion_v0_1.zip" +) +BUILDER = ROOT / "tools" / "build_psych_design_companion_asset.py" +EXPECTED_STUDIES = [f"SWL-S{number:02d}" for number in range(1, 11)] +FORBIDDEN_PLACEHOLDERS = ( + "to be copied from the governed", + "target_contract_not_yet_implemented", + "contract_not_implemented", + "", + "", +) +FORBIDDEN_MEMBER_PARTS = { + "outputs", + "__pycache__", + ".pytest_cache", + ".mypy_cache", + ".ruff_cache", + ".venv", + "venv", +} +SENSITIVE_NAME_PATTERNS = ( + re.compile(r"(^|/)(\.env|id_rsa|id_ed25519)$", re.IGNORECASE), + re.compile(r"(^|/)(credentials?|secrets?)(\.|/|$)", re.IGNORECASE), +) + + +def fail(message: str) -> None: + raise SystemExit(f"PYSTATSV1_PSYCH_DESIGN_RELEASE_CANDIDATE_ERROR: {message}") + + +def sha256(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def load_json(path: Path) -> dict: + try: + return json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as exc: + fail(f"invalid JSON file {path.relative_to(ROOT)}: {exc}") + + +def run(command: list[str], *, cwd: Path = ROOT) -> subprocess.CompletedProcess[str]: + environment = os.environ.copy() + environment["PYTHONDONTWRITEBYTECODE"] = "1" + completed = subprocess.run( + command, + cwd=cwd, + env=environment, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=False, + ) + if completed.returncode != 0: + fail( + "command failed: " + + " ".join(command) + + "\n" + + completed.stdout + + completed.stderr + ) + return completed + + +def validate_registry() -> dict: + index = load_json(SOURCE / "STUDY_INDEX.json") + if index.get("schema_version") != "psych-design-study-index-v0.1": + fail("study-index schema changed") + if index.get("status") != "release_candidate_implemented_not_public": + fail("study index incorrectly implies a different lifecycle") + if index.get("companion_version") != "v0.1": + fail("companion version changed") + if index.get("package_release_candidate") != "0.26.0": + fail("candidate package version changed") + if index.get("synthetic_only") is not True: + fail("study index lost the synthetic-only boundary") + if index.get("real_data_authorized") is not False: + fail("study index authorizes real data") + studies = index.get("studies", []) + if [row.get("study_id") for row in studies] != EXPECTED_STUDIES: + fail("study index must contain SWL-S01 through SWL-S10 in order") + + path_fields = ( + "dataset_path", + "design_contract_path", + "measurement_map_path", + "python_script", + "r_launcher", + "r_script", + "verification_script", + "reference_python_result", + ) + list_path_fields = ("figure_specs", "apa_source_maps", "matched_limitations") + for row in studies: + study = row["study_id"] + for key in path_fields: + rel = row.get(key) + if not isinstance(rel, str) or not (SOURCE / rel).is_file(): + fail(f"{study} missing registered path {key}: {rel}") + for key in list_path_fields: + values = row.get(key) + if not isinstance(values, list) or not values: + fail(f"{study} has no registered {key}") + for rel in values: + if not (SOURCE / rel).is_file(): + fail(f"{study} missing registered {key}: {rel}") + if row.get("status") != "release_candidate_implemented_not_public": + fail(f"{study} lifecycle status changed") + if not row.get("reader_command", "").endswith(study): + fail(f"{study} reader command is not registry-bound") + if not row.get("verification_command", "").endswith(study): + fail(f"{study} verification command is not registry-bound") + if not row.get("packet_command", "").endswith(study): + fail(f"{study} packet command is not registry-bound") + for number_key in ( + "expected_rows", + "expected_unique_units", + "expected_unique_keys", + ): + if not isinstance(row.get(number_key), int) or row[number_key] <= 0: + fail(f"{study} has invalid {number_key}") + return index + + +def validate_source_tree(index: dict) -> None: + if not SOURCE.is_dir(): + fail("unified companion source tree is missing") + text_extensions = {".md", ".json", ".py", ".R", ".txt", ".yaml", ".csv"} + for path in SOURCE.rglob("*"): + if not path.is_file(): + continue + rel = path.relative_to(SOURCE).as_posix() + parts = set(PurePosixPath(rel).parts) + if parts & FORBIDDEN_MEMBER_PARTS: + continue + if any(pattern.search(rel) for pattern in SENSITIVE_NAME_PATTERNS): + fail(f"sensitive-looking source member is present: {rel}") + if path.suffix in text_extensions: + try: + text = path.read_text(encoding="utf-8") + except UnicodeDecodeError as exc: + fail(f"text source is not UTF-8: {rel}: {exc}") + lowered = text.lower() + for placeholder in FORBIDDEN_PLACEHOLDERS: + if placeholder in lowered: + fail(f"placeholder text remains in {rel}: {placeholder}") + if "/home/nicky/" in text or re.search(r"[A-Za-z]:\\Users\\", text): + fail(f"absolute personal path remains in {rel}") + + lineage = load_json(SOURCE / "SOURCE_LINEAGE.json") + if lineage.get("source_anchor") != "e88ff0f6f1aa1c4a946c72b97387f47b3c5311ae": + fail("source-lineage anchor changed") + if lineage.get("source_batches_preserved_in_place") is not True: + fail("source lineage no longer records preserved batches") + if [row.get("study_id") for row in lineage.get("studies", [])] != EXPECTED_STUDIES: + fail("source lineage does not cover all ten studies") + for study in lineage["studies"]: + for origin in study.get("origins", []): + source_path = ROOT / origin["source_path"] + companion_path = SOURCE / origin["companion_path"] + if not source_path.is_file() or not companion_path.is_file(): + fail(f"lineage path missing for {study['study_id']}") + source_hash = sha256(source_path) + companion_hash = sha256(companion_path) + if source_hash != origin["source_sha256"]: + fail(f"source lineage hash drift for {origin['source_path']}") + if companion_hash != origin["companion_sha256"]: + fail(f"companion lineage hash drift for {origin['companion_path']}") + if origin["transformation"] == "byte-for-byte copy" and ( + source_hash != companion_hash + ): + fail(f"byte-for-byte lineage mismatch for {origin['companion_path']}") + + policy = load_json(SOURCE / "VERIFICATION_POLICY.json") + if policy.get("independent_r_required_for_complete_verification") is not True: + fail("complete verification no longer requires independent R") + if [row.get("study_id") for row in policy.get("studies", [])] != EXPECTED_STUDIES: + fail("verification policy does not cover all ten studies") + swl8 = next(row for row in policy["studies"] if row["study_id"] == "SWL-S08") + if swl8.get("verification_category") != "V3_estimand_aligned": + fail("SWL-S08 V3 estimand-alignment category changed") + if not swl8.get("v4_documented_not_equal_fields"): + fail("SWL-S08 has no explicit V4 non-equality documentation") + + provenance = load_json(SOURCE / "COMPANION_PROVENANCE.json") + if provenance.get("release_status") != "release_candidate_not_public": + fail("companion provenance implies public release") + for key in ( + "real_data_authorized", + "portal_release_authorized", + "book_binding_authorized", + "pypi_release_authorized", + ): + if provenance.get(key) is not False: + fail(f"companion provenance opens closed gate: {key}") + + required_templates = { + "STUDY_MAP.yaml", + "CLAIM_MAP.yaml", + "MEASUREMENT_MAP.yaml", + "DATA_LAYOUT_CONTRACT.yaml", + "ANALYSIS_PLAN.yaml", + "VERIFICATION_CONTRACT.yaml", + "REPORTING_SOURCE_MAP.yaml", + "MATCHED_LIMITATION.md", + "ADAPTATION_DELTA_LEDGER.yaml", + } + actual_templates = { + path.name for path in (SOURCE / "templates").iterdir() if path.is_file() + } + if actual_templates != required_templates: + fail( + "adaptation template set changed " + f"missing={sorted(required_templates - actual_templates)} " + f"extra={sorted(actual_templates - required_templates)}" + ) + + expected_entry_scripts = {row["python_script"] for row in index["studies"]} + if len(expected_entry_scripts) != 10: + fail("ten distinct visible Python scripts are not registered") + + +def validate_asset(index: dict) -> None: + if not ASSET.is_file(): + fail("deterministic packaged asset is missing") + with zipfile.ZipFile(ASSET) as archive: + infos = [info for info in archive.infolist() if not info.is_dir()] + names = [info.filename for info in infos] + if names != sorted(names[:-1]) + ["COMPANION_MANIFEST.json"]: + fail("packaged asset member order is not deterministic") + if len(names) != len(set(names)): + fail("packaged asset contains duplicate members") + for info in infos: + pure = PurePosixPath(info.filename) + if pure.is_absolute() or ".." in pure.parts or "\\" in info.filename: + fail(f"unsafe packaged member: {info.filename}") + if set(pure.parts) & FORBIDDEN_MEMBER_PARTS: + fail(f"generated or cached packaged member: {info.filename}") + if any(pattern.search(info.filename) for pattern in SENSITIVE_NAME_PATTERNS): + fail(f"sensitive-looking packaged member: {info.filename}") + if info.date_time != (1980, 1, 1, 0, 0, 0): + fail(f"unstable ZIP timestamp: {info.filename}") + try: + manifest = json.loads(archive.read("COMPANION_MANIFEST.json")) + except (KeyError, json.JSONDecodeError) as exc: + fail(f"invalid packaged companion manifest: {exc}") + if manifest.get("schema_version") != ( + "pystatsv1-psych-design-bundle-manifest-v0.1" + ): + fail("packaged manifest schema changed") + if manifest.get("package_version") != "0.26.0": + fail("packaged manifest package version changed") + if manifest.get("companion_version") != "v0.1": + fail("packaged manifest companion version changed") + if manifest.get("release_status") != "release_candidate_not_public": + fail("packaged manifest incorrectly implies public release") + if manifest.get("synthetic_data_only") is not True: + fail("packaged manifest lost synthetic-only status") + if manifest.get("real_data_authorized") is not False: + fail("packaged manifest authorizes real data") + if manifest.get("study_ids") != EXPECTED_STUDIES: + fail("packaged manifest study registry changed") + if manifest.get("first_result_study") != "SWL-S02": + fail("packaged first-result study changed") + source_rows = manifest.get("files", []) + expected_names = {row["path"] for row in source_rows} + if expected_names | {"COMPANION_MANIFEST.json"} != set(names): + fail("packaged members differ from manifest") + for row in source_rows: + data = archive.read(row["path"]) + if hashlib.sha256(data).hexdigest() != row["sha256"]: + fail(f"packaged source hash mismatch: {row['path']}") + expected_mode = "755" if row["path"].startswith( + ("scripts/python/", "scripts/r/", "scripts/verification/") + ) else "644" + if row.get("mode") != expected_mode: + fail(f"packaged logical mode mismatch: {row['path']}") + packaged_index = json.loads(archive.read("STUDY_INDEX.json")) + if packaged_index != index: + fail("packaged study registry differs from source") + + with tempfile.TemporaryDirectory(prefix="psych-design-determinism-") as temp: + first = Path(temp) / "first.zip" + second = Path(temp) / "second.zip" + run([sys.executable, str(BUILDER), "--dest", str(first)]) + run([sys.executable, str(BUILDER), "--dest", str(second)]) + if sha256(first) != sha256(second) or sha256(first) != sha256(ASSET): + fail("psych-design companion asset is not exactly reproducible") + + +def main() -> int: + index = validate_registry() + validate_source_tree(index) + audit = run( + [ + sys.executable, + str(SOURCE / "scripts/python/design_audit.py"), + "--check-only", + ], + cwd=SOURCE, + ) + if "PYSTATSV1_PSYCH_DESIGN_DESIGN_AUDIT_OK studies=10" not in audit.stdout: + fail("design-audit marker is missing") + validate_asset(index) + print( + "PYSTATSV1_PSYCH_DESIGN_RELEASE_CANDIDATE_VERIFY_OK " + "studies=10 visible_python=10 r_paths=10 templates=9 package=0.26.0 " + "companion=v0.1 public=false" + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/check_psych_design_release_contract.py b/tools/check_psych_design_release_contract.py index 87b1d06..568b80e 100755 --- a/tools/check_psych_design_release_contract.py +++ b/tools/check_psych_design_release_contract.py @@ -1,12 +1,23 @@ +#!/usr/bin/env python3 +"""Verify the frozen psych-design contract and its implemented release candidate.""" from __future__ import annotations import hashlib import json -import re from pathlib import Path +try: + import tomllib +except ModuleNotFoundError: # Python 3.10 + import tomli as tomllib + ROOT = Path(__file__).resolve().parents[1] CONTRACT_ROOT = ROOT / "psych_design_companion/psych_design_release_contract_v0_1" +SOURCE_ROOT = ( + ROOT + / "psych_design_companion" + / "psychological_statistics_by_design_companion_v0_1" +) EXPECTED = { "COMPANION_RELEASE_CONTRACT.json", "COMMAND_INTERFACE_CONTRACT.json", @@ -18,6 +29,14 @@ "PUBLIC_ASSET_LICENSE_CONTRACT.json", "README.md", } +EXPECTED_STUDIES = [f"SWL-S{number:02d}" for number in range(1, 11)] +CLOSED_GATES = ( + "public_companion_released", + "pypi_release_authorized", + "portal_release_authorized", + "real_data_authorized", + "book_binding_authorized", +) def fail(message: str) -> None: @@ -28,7 +47,7 @@ def read(name: str) -> dict: path = CONTRACT_ROOT / name try: return json.loads(path.read_text(encoding="utf-8")) - except Exception as exc: + except (OSError, json.JSONDecodeError) as exc: fail(f"cannot read {path.relative_to(ROOT)}: {exc}") @@ -36,56 +55,102 @@ def digest(path: Path) -> str: return hashlib.sha256(path.read_bytes()).hexdigest() +def verify_preservation() -> None: + preservation = read("SOURCE_BATCH_PRESERVATION_MANIFEST.json") + total = 0 + for batch in preservation["batches"]: + actual_rows = [] + for row in batch["files"]: + path = ROOT / row["path"] + if not path.is_file(): + fail(f"preserved source file missing: {row['path']}") + if path.stat().st_size != row["bytes"] or digest(path) != row["sha256"]: + fail(f"preserved source file drift: {row['path']}") + actual_rows.append(f"{row['sha256']} {row['path']}\n") + tree = hashlib.sha256("".join(actual_rows).encode()).hexdigest() + if tree != batch["tree_sha256"] or len(actual_rows) != batch["file_count"]: + fail(f"preserved source tree drift: {batch['batch_root']}") + total += len(actual_rows) + if total != 136: + fail(f"preserved source file total changed: {total}") + + def main() -> int: if not CONTRACT_ROOT.is_dir(): fail("contract root is missing") - names = {p.name for p in CONTRACT_ROOT.iterdir() if p.is_file()} + names = {path.name for path in CONTRACT_ROOT.iterdir() if path.is_file()} if names != EXPECTED: - fail(f"contract member set changed missing={sorted(EXPECTED-names)} extra={sorted(names-EXPECTED)}") + fail( + "contract member set changed " + f"missing={sorted(EXPECTED - names)} extra={sorted(names - EXPECTED)}" + ) - pyproject = (ROOT / "pyproject.toml").read_text(encoding="utf-8") - if not re.search(r'^version = "0\.25\.2"$', pyproject, flags=re.MULTILINE): - fail("baseline package version is not 0.25.2") - if re.search(r'^psych-design\s*=', pyproject, flags=re.MULTILINE): - fail("psych-design extra was implemented inside the contract-only unit") - cli = (ROOT / "src/pystatsv1/cli.py").read_text(encoding="utf-8") - if 'add_parser("psych-design"' in cli or "add_parser('psych-design'" in cli: - fail("psych-design CLI was implemented inside the contract-only unit") + project = tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8"))[ + "project" + ] + if project.get("version") != "0.26.0": + fail("release-candidate package version is not 0.26.0") + extra = project.get("optional-dependencies", {}).get("psych-design") + expected_extra = { + "numpy==2.2.6", + "pandas==2.2.3", + "scipy==1.15.3", + "statsmodels==0.14.6", + "matplotlib==3.10.8", + } + if set(extra or []) != expected_extra: + fail("psych-design optional dependency set changed") release = read("COMPANION_RELEASE_CONTRACT.json") - if release["baseline"] != {"main_commit":"6cd680812c7a149d4909eff4ddaf6e1679f6aba1","package_version":"0.25.2","repository":"PyStatsV1"}: - fail("baseline identity changed") + expected_baseline = { + "main_commit": "6cd680812c7a149d4909eff4ddaf6e1679f6aba1", + "package_version": "0.25.2", + "repository": "PyStatsV1", + } + if release.get("baseline") != expected_baseline: + fail("Drop-in 032 baseline identity changed") + if release.get("release_candidate_baseline_main") != ( + "e88ff0f6f1aa1c4a946c72b97387f47b3c5311ae" + ): + fail("Drop-in 033 baseline identity is missing or changed") planned = release["planned_release"] required = { - "package_version":"0.26.0", - "release_tag":"v0.26.0", - "optional_extra":"psych-design", - "cli_namespace":"psych-design", - "companion_version":"v0.1", - "default_destination":"psychological_statistics_by_design_companion_v0_1", - "asset_name":"psychological_statistics_by_design_companion_v0_1.zip", + "package_version": "0.26.0", + "release_tag": "v0.26.0", + "optional_extra": "psych-design", + "cli_namespace": "psych-design", + "companion_version": "v0.1", + "default_destination": "psychological_statistics_by_design_companion_v0_1", + "asset_name": "psychological_statistics_by_design_companion_v0_1.zip", } - for key,value in required.items(): + for key, value in required.items(): if planned.get(key) != value: fail(f"planned release field changed: {key}") life = release["lifecycle"] if life.get("contract_frozen") is not True: fail("contract is not frozen") - for key in ("public_companion_released","pypi_release_authorized","portal_release_authorized","real_data_authorized","book_binding_authorized"): + if life.get("release_candidate_implemented") is not True: + fail("release candidate is not recorded as implemented") + for key in CLOSED_GATES: if life.get(key) is not False: fail(f"closed lifecycle gate changed: {key}") - if release.get("study_ids") != [f"SWL-S{i:02d}" for i in range(1,11)]: + if release.get("study_ids") != EXPECTED_STUDIES: fail("study ID registry is incomplete or out of order") if release.get("first_result_study") != "SWL-S02": fail("first-result study changed") + for rel in (release["release_candidate_source"], release["release_candidate_asset"]): + if not (ROOT / rel).exists(): + fail(f"release-candidate path is missing: {rel}") commands = read("COMMAND_INTERFACE_CONTRACT.json") - if commands.get("status") != "frozen_proposed_not_implemented_not_public": - fail("command status may not imply implementation or release") - if commands.get("package_install") != 'python -m pip install "pystatsv1[psych-design]==0.26.0"': + if commands.get("status") != "release_candidate_implemented_not_public": + fail("command status does not identify the non-public release candidate") + if commands.get("package_install") != ( + 'python -m pip install "pystatsv1[psych-design]==0.26.0"' + ): fail("planned install command changed") - if any(row.get("implemented") is not False for row in commands.get("commands", [])): - fail("a proposed command is incorrectly marked implemented") + if any(row.get("implemented") is not True for row in commands.get("commands", [])): + fail("an implemented candidate command is marked unavailable") expected_commands = { "pystatsv1 psych-design info", "pystatsv1 psych-design init", @@ -95,66 +160,100 @@ def main() -> int: } if {row["command"] for row in commands["commands"]} != expected_commands: fail("command family changed") + cli = (ROOT / "src/pystatsv1/cli.py").read_text(encoding="utf-8") + if '"psych-design"' not in cli or "cmd_psych_design_doctor" not in cli: + fail("psych-design CLI implementation is missing") inventory = read("CURRENT_STUDY_ASSET_INVENTORY.json")["studies"] - if {row["study_id"] for row in inventory} != {f"SWL-S{i:02d}" for i in range(1,11)}: + if {row["study_id"] for row in inventory} != set(EXPECTED_STUDIES): fail("current study inventory is incomplete") for row in inventory: - for key in ("dataset_path","design_contract_path","current_python_implementation","current_r_script","current_verification_script","python_result_receipt"): - path = ROOT / row[key] - if not path.is_file(): + for key in ( + "dataset_path", + "design_contract_path", + "current_python_implementation", + "current_r_script", + "current_verification_script", + "python_result_receipt", + ): + if not (ROOT / row[key]).is_file(): fail(f"{row['study_id']} missing {key}: {row[key]}") - for key in ("figure_sources","apa_source_maps","matched_limitations"): + for key in ("figure_sources", "apa_source_maps", "matched_limitations"): for rel in row[key]: if not (ROOT / rel).is_file(): fail(f"{row['study_id']} missing {key}: {rel}") - if row.get("current_public_release") is not False or row.get("current_reader_command") is not None: - fail(f"{row['study_id']} current release boundary changed") - if not row.get("release_blockers"): - fail(f"{row['study_id']} has no explicit release blockers") + if row.get("current_public_release") is not False: + fail(f"{row['study_id']} source batch incorrectly implies public release") target = read("TARGET_STUDY_INDEX.json") - if target.get("status") != "contract_not_implemented": - fail("target study index incorrectly implies implementation") - for row in target["studies"]: - if row.get("status") != "target_contract_not_yet_implemented": - fail(f"{row['study_id']} target status changed") - if not row["reader_command"].endswith(row["study_id"]): - fail(f"{row['study_id']} reader command does not bind the study ID") + actual_path = ROOT / target.get("actual_registry", "") + if target.get("status") != "release_candidate_implemented_not_public": + fail("target study index lifecycle status changed") + if not actual_path.is_file() or digest(actual_path) != target.get( + "actual_registry_sha256" + ): + fail("target study index is not bound to the actual registry") + actual = json.loads(actual_path.read_text(encoding="utf-8")) + if target.get("studies") != actual.get("studies"): + fail("contract registry mirror differs from the companion registry") + if [row.get("study_id") for row in actual["studies"]] != EXPECTED_STUDIES: + fail("actual study registry is incomplete or out of order") + if any(row.get("status") != "release_candidate_implemented_not_public" for row in actual["studies"]): + fail("actual study registry opens an invalid lifecycle status") - preservation = read("SOURCE_BATCH_PRESERVATION_MANIFEST.json") - for batch in preservation["batches"]: - actual_rows=[] - for row in batch["files"]: - path=ROOT/row["path"] - if not path.is_file(): - fail(f"preserved source file missing: {row['path']}") - if path.stat().st_size != row["bytes"] or digest(path) != row["sha256"]: - fail(f"preserved source file drift: {row['path']}") - actual_rows.append(f"{row['sha256']} {row['path']}\n") - tree=hashlib.sha256(''.join(actual_rows).encode()).hexdigest() - if tree != batch["tree_sha256"] or len(actual_rows) != batch["file_count"]: - fail(f"preserved source tree drift: {batch['batch_root']}") + verify_preservation() gaps = read("CROSS_REPOSITORY_GAP_MATRIX.json")["rows"] - required_areas={"Source studies","CLI and PyPI extra","Visible per-study Python scripts","R verification","Study registry","Measurement maps","Reviewer packets","Licensing","Portal","Book interior","Clean reader proofs"} + required_areas = { + "Source studies", + "CLI and PyPI extra", + "Visible per-study Python scripts", + "R verification", + "Study registry", + "Measurement maps", + "Reviewer packets", + "Licensing", + "Portal", + "Book interior", + "Clean reader proofs", + } if {row["area"] for row in gaps} != required_areas: fail("cross-repository gap matrix is incomplete") + deferred = {row["area"]: row for row in gaps} + if "deferred" not in deferred["Book interior"]["gap"].lower(): + fail("book edits are not clearly deferred") + if "no route" not in deferred["Portal"]["gap"].lower(): + fail("portal release is not clearly deferred") - license_contract=read("PUBLIC_ASSET_LICENSE_CONTRACT.json") - if license_contract.get("current_repository_license") != "MIT" or not (ROOT/"LICENSE").is_file(): + license_contract = read("PUBLIC_ASSET_LICENSE_CONTRACT.json") + if license_contract.get("current_repository_license") != "MIT" or not ( + ROOT / "LICENSE" + ).is_file(): fail("repository license boundary changed") - if license_contract.get("real_data_included") is not False or license_contract.get("book_prose_included") is not False: - fail("public asset exclusion boundary changed") + if license_contract.get("real_data_included") is not False: + fail("public asset real-data exclusion changed") + if license_contract.get("book_prose_included") is not False: + fail("public asset book-prose exclusion changed") - anchors=read("CROSS_REPOSITORY_AUDIT_ANCHORS.json")["attachments"] - if anchors["book"]["archive_commit"] != "ec14f859f4ce686ec0091f879382b5ac5bfaf5b3" or anchors["book"]["tests_passed"] != 676 or anchors["book"]["paperback_pages"] != 131: + anchors = read("CROSS_REPOSITORY_AUDIT_ANCHORS.json")["attachments"] + if anchors["book"]["archive_commit"] != ( + "ec14f859f4ce686ec0091f879382b5ac5bfaf5b3" + ): fail("book audit anchor changed") - if anchors["portal"]["archive_commit"] != "b9c0f3a1a6dd4392e6e877d75b3c7d73aa249811": + if anchors["book"]["tests_passed"] != 676 or anchors["book"]["paperback_pages"] != 131: + fail("book proof anchor changed") + if anchors["portal"]["archive_commit"] != ( + "b9c0f3a1a6dd4392e6e877d75b3c7d73aa249811" + ): fail("portal audit anchor changed") - print("PYSTATSV1_PSYCH_DESIGN_RELEASE_CONTRACT_VERIFY_OK studies=10 source_batches=3 planned_package=0.26.0 companion=v0.1") + print( + "PYSTATSV1_PSYCH_DESIGN_RELEASE_CONTRACT_VERIFY_OK " + "studies=10 source_batches=3 package=0.26.0 companion=v0.1 " + "candidate_implemented=true public=false" + ) return 0 + if __name__ == "__main__": raise SystemExit(main())