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 0000000..ad56ed7 Binary files /dev/null and b/src/pystatsv1/assets/psychological_statistics_by_design_companion_v0_1.zip differ 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())