rasch-per 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. rasch_per-0.1.0/.github/workflows/ci.yml +39 -0
  2. rasch_per-0.1.0/.github/workflows/docs.yml +45 -0
  3. rasch_per-0.1.0/.github/workflows/publish.yml +60 -0
  4. rasch_per-0.1.0/.gitignore +29 -0
  5. rasch_per-0.1.0/.pre-commit-config.yaml +14 -0
  6. rasch_per-0.1.0/.readthedocs.yaml +16 -0
  7. rasch_per-0.1.0/.validation.sh +29 -0
  8. rasch_per-0.1.0/CHANGELOG.md +12 -0
  9. rasch_per-0.1.0/CITATION.cff +25 -0
  10. rasch_per-0.1.0/CONTRIBUTING.md +58 -0
  11. rasch_per-0.1.0/GRILL.md +20 -0
  12. rasch_per-0.1.0/LICENSE +21 -0
  13. rasch_per-0.1.0/PKG-INFO +191 -0
  14. rasch_per-0.1.0/README.md +147 -0
  15. rasch_per-0.1.0/docsrc/api.md +38 -0
  16. rasch_per-0.1.0/docsrc/cli.md +64 -0
  17. rasch_per-0.1.0/docsrc/contributing.md +51 -0
  18. rasch_per-0.1.0/docsrc/index.md +50 -0
  19. rasch_per-0.1.0/docsrc/methodology.md +111 -0
  20. rasch_per-0.1.0/docsrc/quickstart.md +73 -0
  21. rasch_per-0.1.0/docsrc/report.md +43 -0
  22. rasch_per-0.1.0/examples/notebooks/quickstart.ipynb +109 -0
  23. rasch_per-0.1.0/examples/notebooks/report_walkthrough.ipynb +109 -0
  24. rasch_per-0.1.0/images/dif_contrasts.png +0 -0
  25. rasch_per-0.1.0/images/icc.png +0 -0
  26. rasch_per-0.1.0/images/item_difficulty.png +0 -0
  27. rasch_per-0.1.0/images/item_discrimination.png +0 -0
  28. rasch_per-0.1.0/images/test_information.png +0 -0
  29. rasch_per-0.1.0/images/wright_map.png +0 -0
  30. rasch_per-0.1.0/mkdocs.yml +31 -0
  31. rasch_per-0.1.0/pyproject.toml +89 -0
  32. rasch_per-0.1.0/scripts/cfa_extra.py +52 -0
  33. rasch_per-0.1.0/scripts/export_pdf.py +38 -0
  34. rasch_per-0.1.0/scripts/stocking_lord.py +74 -0
  35. rasch_per-0.1.0/scripts/validate_against_r.R +47 -0
  36. rasch_per-0.1.0/src/rasch_per/__init__.py +22 -0
  37. rasch_per-0.1.0/src/rasch_per/cli.py +142 -0
  38. rasch_per-0.1.0/src/rasch_per/ctt.py +218 -0
  39. rasch_per-0.1.0/src/rasch_per/data.py +230 -0
  40. rasch_per-0.1.0/src/rasch_per/dif.py +225 -0
  41. rasch_per-0.1.0/src/rasch_per/dimensionality.py +104 -0
  42. rasch_per-0.1.0/src/rasch_per/plots.py +185 -0
  43. rasch_per-0.1.0/src/rasch_per/py.typed +0 -0
  44. rasch_per-0.1.0/src/rasch_per/rasch/__init__.py +42 -0
  45. rasch_per-0.1.0/src/rasch_per/rasch/dimensionality.py +104 -0
  46. rasch_per-0.1.0/src/rasch_per/rasch/estimation.py +323 -0
  47. rasch_per-0.1.0/src/rasch_per/rasch/fit.py +241 -0
  48. rasch_per-0.1.0/src/rasch_per/rasch/info.py +153 -0
  49. rasch_per-0.1.0/src/rasch_per/rasch/model.py +199 -0
  50. rasch_per-0.1.0/src/rasch_per/reliability.py +184 -0
  51. rasch_per-0.1.0/src/rasch_per/report.py +186 -0
  52. rasch_per-0.1.0/src/rasch_per/simulate.py +119 -0
  53. rasch_per-0.1.0/templates/report_template.html.j2 +11 -0
  54. rasch_per-0.1.0/tests/conftest.py +10 -0
  55. rasch_per-0.1.0/tests/data/README.md +2 -0
  56. rasch_per-0.1.0/tests/data/reference_values.json +16 -0
  57. rasch_per-0.1.0/tests/data/synthetic_small.csv +6 -0
  58. rasch_per-0.1.0/tests/test_cli.py +81 -0
  59. rasch_per-0.1.0/tests/test_ctt.py +131 -0
  60. rasch_per-0.1.0/tests/test_data.py +115 -0
  61. rasch_per-0.1.0/tests/test_dif.py +112 -0
  62. rasch_per-0.1.0/tests/test_dimensionality.py +58 -0
  63. rasch_per-0.1.0/tests/test_fit.py +151 -0
  64. rasch_per-0.1.0/tests/test_info.py +72 -0
  65. rasch_per-0.1.0/tests/test_plots.py +101 -0
  66. rasch_per-0.1.0/tests/test_rasch_estimation.py +123 -0
  67. rasch_per-0.1.0/tests/test_rasch_model.py +94 -0
  68. rasch_per-0.1.0/tests/test_reliability.py +80 -0
  69. rasch_per-0.1.0/tests/test_report.py +55 -0
  70. rasch_per-0.1.0/tests/test_simulate.py +94 -0
@@ -0,0 +1,39 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: ${{ matrix.python-version }}
20
+ - name: Install
21
+ run: |
22
+ python -m pip install --upgrade pip
23
+ pip install -e ".[dev]"
24
+ - name: Lint
25
+ run: |
26
+ ruff check .
27
+ ruff format --check .
28
+ - name: Type check
29
+ run: mypy
30
+ - name: Test with coverage gate
31
+ # Coverage gate (85%) is enforced now that the library modules are
32
+ # implemented (Phases 0-5).
33
+ run: pytest --cov=rasch_per --cov-fail-under=85 --cov-report=xml
34
+ - name: Upload coverage
35
+ if: matrix.python-version == '3.13'
36
+ uses: actions/upload-artifact@v4
37
+ with:
38
+ name: coverage.xml
39
+ path: coverage.xml
@@ -0,0 +1,45 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - "docsrc/**"
8
+ - "mkdocs.yml"
9
+ - ".readthedocs.yaml"
10
+ - "pyproject.toml"
11
+ workflow_dispatch:
12
+
13
+ permissions:
14
+ contents: read
15
+ pages: write
16
+ id-token: write
17
+
18
+ concurrency:
19
+ group: pages
20
+ cancel-in-progress: false
21
+
22
+ jobs:
23
+ build:
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ - uses: actions/setup-python@v5
28
+ with:
29
+ python-version: "3.12"
30
+ - name: Install docs extra
31
+ run: python -m pip install --upgrade pip && python -m pip install ".[docs]"
32
+ - name: Build site
33
+ run: mkdocs build --strict
34
+ - uses: actions/upload-pages-artifact@v3
35
+ with:
36
+ path: site
37
+ deploy:
38
+ needs: build
39
+ runs-on: ubuntu-latest
40
+ environment:
41
+ name: github-pages
42
+ url: ${{ steps.deployment.outputs.page_url }}
43
+ steps:
44
+ - id: deployment
45
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,60 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ tags: ["v*.*.*"]
7
+
8
+ jobs:
9
+ build:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-python@v5
14
+ with:
15
+ python-version: "3.13"
16
+ - name: Build distributions
17
+ run: |
18
+ python -m pip install --upgrade build twine
19
+ python -m build
20
+ twine check dist/*
21
+ - uses: actions/upload-artifact@v4
22
+ with:
23
+ name: dist
24
+ path: dist/
25
+
26
+ publish-testpypi:
27
+ # Requires Trusted Publishing configured on testpypi.org for this repo
28
+ # (manual one-time setup, done in Phase 7). Until then, opt in by setting
29
+ # the repository variable ENABLE_TESTPYPI_PUBLISH=true; the job skips
30
+ # otherwise so pushes to main stay green.
31
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/heads/main') && vars.ENABLE_TESTPYPI_PUBLISH == 'true'
32
+ needs: build
33
+ runs-on: ubuntu-latest
34
+ environment: testpypi
35
+ permissions:
36
+ id-token: write
37
+ steps:
38
+ - uses: actions/download-artifact@v4
39
+ with:
40
+ name: dist
41
+ path: dist/
42
+ - name: Publish to TestPyPI
43
+ uses: pypa/gh-action-pypi-publish@release/v1
44
+ with:
45
+ repository-url: https://test.pypi.org/legacy/
46
+
47
+ publish-pypi:
48
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
49
+ needs: build
50
+ runs-on: ubuntu-latest
51
+ environment: pypi
52
+ permissions:
53
+ id-token: write
54
+ steps:
55
+ - uses: actions/download-artifact@v4
56
+ with:
57
+ name: dist
58
+ path: dist/
59
+ - name: Publish to PyPI (Trusted Publisher)
60
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,29 @@
1
+ # FirstHQ workflow files (never commit)
2
+ docs/
3
+ AGENT.md
4
+ CONTEXT.json
5
+ TODO.md
6
+ update.md
7
+ log.md
8
+ .opencode/
9
+
10
+ # Python
11
+ __pycache__/
12
+ *.py[cod]
13
+ *.egg-info/
14
+ dist/
15
+ build/
16
+ site/
17
+ .mkdocs_cache/
18
+ .venv/
19
+ .mypy_cache/
20
+ .ruff_cache/
21
+ .pytest_cache/
22
+ .coverage
23
+ coverage.xml
24
+ htmlcov/
25
+
26
+ # Editors / OS
27
+ .DS_Store
28
+ .idea/
29
+ .vscode/
@@ -0,0 +1,14 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.6.9
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
8
+ - repo: local
9
+ hooks:
10
+ - id: mypy
11
+ name: mypy
12
+ entry: mypy
13
+ language: system
14
+ pass_filenames: false
@@ -0,0 +1,16 @@
1
+ version: 2
2
+
3
+ build:
4
+ os: ubuntu-22.04
5
+ tools:
6
+ python: "3.12"
7
+
8
+ mkdocs:
9
+ configuration: mkdocs.yml
10
+
11
+ python:
12
+ install:
13
+ - method: pip
14
+ path: .
15
+ extra_requirements:
16
+ - docs
@@ -0,0 +1,29 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ cd "$(dirname "$0")"
5
+
6
+ if [ ! -d ".venv" ]; then
7
+ echo "Creating virtualenv..."
8
+ uv venv --python 3.13 .venv
9
+ uv pip install -e ".[dev]" -p .venv
10
+ fi
11
+ PY=".venv/bin/python"
12
+
13
+ echo "=== Validation Loop ==="
14
+
15
+ echo "[1/4] Building..."
16
+ $PY -m compileall -q src/rasch_per
17
+
18
+ echo "[2/4] Type checking..."
19
+ $PY -m mypy src/
20
+
21
+ echo "[3/4] Linting..."
22
+ $PY -m ruff check .
23
+ $PY -m ruff format --check .
24
+
25
+ echo "[4/4] Testing..."
26
+ $PY -m pytest --cov=rasch_per -q
27
+ # Coverage gate (85%) is enforced in CI (.github/workflows/ci.yml), not locally.
28
+
29
+ echo "=== All checks passed ==="
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+
12
+ - Project scaffolding: package skeleton, CLI entry point, CI and publish workflows.
@@ -0,0 +1,25 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software, please cite it as below."
3
+ title: rasch-per
4
+ abstract: >-
5
+ Rasch model and Classical Test Theory (CTT) psychometric analysis for
6
+ education research, built for physics / STEM / discipline-based education
7
+ researchers (PER / DBER). Provides person abilities, item difficulties,
8
+ fit statistics, dimensionality checks, differential item functioning (DIF)
9
+ analysis, and a self-contained HTML validity report from dichotomous
10
+ item-response data.
11
+ authors:
12
+ - family-names: Anil
13
+ given-names: Aditya
14
+ email: aditya.anil.productions@gmail.com
15
+ repository-code: "https://github.com/aditya-an1l/rasch-per"
16
+ url: "https://github.com/aditya-an1l/rasch-per"
17
+ license: MIT
18
+ version: 0.1.0
19
+ date-released: 2026-08-27
20
+ keywords:
21
+ - psychometrics
22
+ - rasch
23
+ - item response theory
24
+ - education research
25
+ - physics education research
@@ -0,0 +1,58 @@
1
+ # Contributing to rasch-per
2
+
3
+ Thanks for your interest in contributing.
4
+
5
+ ## Development setup
6
+
7
+ ```bash
8
+ git clone https://github.com/aditya-an1l/rasch-per
9
+ cd rasch-per
10
+ uv venv --python 3.13 .venv
11
+ uv pip install -e ".[dev]" -p .venv
12
+ ```
13
+
14
+ Or with plain pip:
15
+
16
+ ```bash
17
+ python -m venv .venv && source .venv/bin/activate
18
+ pip install -e ".[dev]"
19
+ ```
20
+
21
+ ## Running checks
22
+
23
+ ```bash
24
+ ./.validation.sh # build + type check + lint + tests
25
+ python -m pytest # tests only (with coverage)
26
+ python -m ruff check . # lint only
27
+ python -m mypy # types only
28
+ ```
29
+
30
+ CI runs the same checks on Python 3.10-3.13 and enforces a 85% coverage gate.
31
+ Both `ruff check` and `mypy` must pass before any PR is merged.
32
+
33
+ ## Code style expectations
34
+
35
+ - Full NumPy-style docstrings (Parameters, Returns, worked mini-example) on
36
+ every public function/class.
37
+ - Type hints everywhere; no new hard dependencies without discussion first.
38
+ - Statistical formulas follow the conventions fixed in the project spec; do
39
+ not swap in alternative conventions from other sources without raising it
40
+ for discussion first.
41
+ - All example/test data comes from `rasch_per.simulate` - never vendor
42
+ third-party data or item text.
43
+
44
+ ## Publishing (maintainers)
45
+
46
+ The GitHub Actions workflow `.github/workflows/publish.yml` builds and
47
+ publishes via PyPI Trusted Publishing on tagged releases (`v*.*.*`). TestPyPI
48
+ publishes on every push to `main`.
49
+
50
+ Manual publish (first-time alternative):
51
+
52
+ ```bash
53
+ python -m pip install --upgrade build twine
54
+ python -m build
55
+ twine check dist/*
56
+ twine upload --repository testpypi dist/* # sanity check first
57
+ twine upload dist/* # real release
58
+ ```
@@ -0,0 +1,20 @@
1
+ # Grill Session — 2026-08-27
2
+
3
+ ## Project: rasch-per
4
+
5
+ ### Shortcomings
6
+
7
+ - AGENT.md references `docs/skills/SPEC.md` as the authoritative source for resolved conventions (Q3 adjustment, ETS delta sign, Ferguson delta), but that file does not exist in the repo. Phase 3 implemented `fit.py` (Yen's Q3) and `dimensionality.py` (PCAR) using the standard published formulas rather than a spec-pinned variant. Risk: if the spec specified a non-standard Q3 adjustment or PCAR centering, current output diverges from intended behavior.
8
+
9
+ ### Unresolved Questions
10
+
11
+ - Where is the original build spec (the SPEC.md source)? It drove Phases 0-2 but is not on disk. Does the user have it, or was it only in the earlier chat session?
12
+ - Should the Q3 / PCAR implementation use the classic residual-correlation form (current) or a spec-specific adjustment once recovered?
13
+
14
+ ### Next Steps
15
+
16
+ - Recover or re-supply SPEC.md (or the relevant convention snippets) and re-verify fit.py / dimensionality.py against it before Phase 5 (DIF, which also depends on the ETS delta sign convention).
17
+
18
+ ## Resolved 2026-08-27
19
+
20
+ - The SPEC.md gap (shortcoming above) was closed by reconstructing `docs/skills/SPEC.md` from the implemented code. The original Phase 0-2 spec remains unavailable; the reconstructed conventions use the standard published forms (Yen 1984 Q3, ETS delta sign positive = easier for focal, Ferguson delta per Ferguson 1949). If the original spec is recovered, diff and correct before a public release.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aditya Anil
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,191 @@
1
+ Metadata-Version: 2.5
2
+ Name: rasch-per
3
+ Version: 0.1.0
4
+ Summary: Rasch model and CTT psychometric analysis for education research
5
+ Project-URL: Homepage, https://github.com/aditya-an1l/rasch-per
6
+ Project-URL: Repository, https://github.com/aditya-an1l/rasch-per
7
+ Project-URL: Issues, https://github.com/aditya-an1l/rasch-per/issues
8
+ Author-email: Aditya Anil <aditya.anil.productions@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: education research,item response theory,physics education research,psychometrics,rasch
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Education
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: jinja2>=3.1
24
+ Requires-Dist: matplotlib>=3.7
25
+ Requires-Dist: numpy>=1.24
26
+ Requires-Dist: pandas>=2.0
27
+ Requires-Dist: scipy>=1.10
28
+ Requires-Dist: typer>=0.9
29
+ Provides-Extra: cfa
30
+ Requires-Dist: semopy; extra == 'cfa'
31
+ Provides-Extra: dev
32
+ Requires-Dist: mypy; extra == 'dev'
33
+ Requires-Dist: pandas-stubs; extra == 'dev'
34
+ Requires-Dist: pytest; extra == 'dev'
35
+ Requires-Dist: pytest-cov; extra == 'dev'
36
+ Requires-Dist: ruff; extra == 'dev'
37
+ Requires-Dist: scipy-stubs; extra == 'dev'
38
+ Provides-Extra: docs
39
+ Requires-Dist: mkdocs-material>=9.0; extra == 'docs'
40
+ Requires-Dist: mkdocs>=1.5; extra == 'docs'
41
+ Provides-Extra: pdf
42
+ Requires-Dist: weasyprint; extra == 'pdf'
43
+ Description-Content-Type: text/markdown
44
+
45
+ # rasch-per
46
+
47
+ Rasch model and Classical Test Theory (CTT) psychometric analysis for
48
+ education research, built for physics / STEM / discipline-based education
49
+ researchers (PER / DBER).
50
+
51
+ Feed it a CSV of dichotomous (0/1) item responses and get person abilities,
52
+ item difficulties, fit statistics, dimensionality checks, DIF analysis, and a
53
+ full self-contained HTML validity report.
54
+
55
+ Status: implemented and validated (119 tests, ~97% coverage). The library is
56
+ Beta; the public API is stable for the analyses listed below.
57
+
58
+ ## Quickstart (CLI)
59
+
60
+ ```bash
61
+ pip install rasch-per
62
+ rasch-per simulate --output demo.csv
63
+ rasch-per analyze demo.csv --output report.html
64
+ ```
65
+
66
+ `simulate` writes a synthetic response CSV (with a `person_id` index). `analyze`
67
+ reads it back (the first column is the person index), runs the full pipeline,
68
+ and writes a self-contained HTML report.
69
+
70
+ ```bash
71
+ # With differential item functioning (DIF) by a group column
72
+ rasch-per analyze demo.csv --groups groups.csv --dif-group gender \
73
+ --reference Man --focal Non-man --output report.html
74
+ ```
75
+
76
+ ## Python API
77
+
78
+ ```python
79
+ import pandas as pd
80
+ from rasch_per import (
81
+ ResponseData,
82
+ CTTAnalysis,
83
+ RaschModel,
84
+ DIFAnalysis,
85
+ generate_report,
86
+ )
87
+
88
+ # Load a response matrix (persons as rows, items as columns)
89
+ df = pd.read_csv("responses.csv", index_col=0)
90
+ data = ResponseData(df)
91
+
92
+ # Classical Test Theory
93
+ ctt = CTTAnalysis(data).run()
94
+ print(ctt.summary())
95
+ print(ctt.reliability.cronbach_alpha) # attribute, not a method
96
+
97
+ # Rasch (MML is the default estimator)
98
+ model = RaschModel().fit(data, estimator="MML")
99
+ print(model.item_difficulties) # pandas Series indexed by item name
100
+ print(model.fit_statistics()) # infit / outfit mean-squares
101
+
102
+ # Differential Item Functioning
103
+ groups = pd.read_csv("groups.csv", index_col=0)["gender"].reindex(data.person_ids)
104
+ dif = DIFAnalysis(model, groups=groups.to_numpy(), reference="Man", focal="Non-man").analyze()
105
+ print(dif.summary()) # ETS delta classification + BH flags
106
+
107
+ # Self-contained HTML validity report
108
+ generate_report(
109
+ df,
110
+ output="validity_report.html",
111
+ groups=groups.to_numpy(),
112
+ reference="Man",
113
+ focal="Non-man",
114
+ )
115
+ ```
116
+
117
+ Notes on the API:
118
+
119
+ - `generate_report` takes a `pandas.DataFrame`, not a `ResponseData`. When you
120
+ pass `groups`, they must be aligned to the DataFrame's row order (here, the
121
+ person index).
122
+ - `CTTResults.reliability` is an attribute (`cronbach_alpha`, `mcdonald_omega`,
123
+ `ferguson_delta`), not a callable.
124
+ - `DIFAnalysis` is run with `.analyze()` (it returns a `DIFResults`).
125
+
126
+ ## What's inside
127
+
128
+ - **CTT**: item difficulty (p-values), discrimination (corrected item-total,
129
+ rest-score based) with bootstrap SEs; Cronbach's alpha, McDonald's omega,
130
+ Ferguson's delta.
131
+ - **Rasch**: JML and MML estimation (MML default), standard errors.
132
+ - **Fit**: infit/outfit mean-square with low-stakes/high-stakes presets;
133
+ Yen's Q3 local independence check.
134
+ - **Dimensionality**: PCAR first-contrast eigenvalue diagnostic.
135
+ - **DIF**: Lord's chi-square, mean/mean linking, ETS delta effect sizes,
136
+ Benjamini-Hochberg correction.
137
+ - **Report**: single-file HTML validity report with embedded plots.
138
+
139
+ ## Methodology & References
140
+
141
+ This package implements standard, published psychometric methods used
142
+ throughout physics / discipline-based education research:
143
+
144
+ - Rasch model (Rasch, 1960; Wright & Stone, 1979)
145
+ - Joint and marginal maximum likelihood estimation (e.g., as in R packages
146
+ `TAM`, `eRm`)
147
+ - Infit/outfit mean-square fit statistics (Smith, 2000; Linacre, 2002)
148
+ - Yen's Q3 local independence statistic (Yen, 1984)
149
+ - Principal components analysis of residuals (Linacre, 1998)
150
+ - Lord's chi-square DIF test (Lord, 1980)
151
+ - ETS delta scale DIF classification (ETS categories A/B/C)
152
+ - Benjamini-Hochberg false discovery rate control (Benjamini & Hochberg, 1995)
153
+ - Cronbach's alpha (Cronbach, 1951), McDonald's omega (McDonald, 1999),
154
+ Ferguson's delta (Ferguson, 1949)
155
+
156
+ No third-party assessment content or data is included; all examples use
157
+ synthetic data from the package's own simulator.
158
+
159
+ ## Documentation & Examples
160
+
161
+ - API and usage docs: `mkdocs serve` (source in `docsrc/`, requires the
162
+ `docs` extra: `pip install "rasch-per[docs]"`).
163
+ - Worked notebooks: `examples/notebooks/quickstart.ipynb` and
164
+ `examples/notebooks/report_walkthrough.ipynb`.
165
+ - Optional analyses (PDF export, CFA, Stocking-Lord linking, R cross-validation)
166
+ live in `scripts/` and use the `pdf` / `cfa` extras where needed.
167
+
168
+ ## Screenshots
169
+
170
+ Key diagnostic plots produced by the package's plotting API on simulated data
171
+ (500 persons, 20 items, seed 42):
172
+
173
+ | Plot | Description |
174
+ |------|-------------|
175
+ | ![Wright map](images/wright_map.png) | Person ability vs item difficulty (Wright map) |
176
+ | ![Test information](images/test_information.png) | Test information and standard error of measurement across the ability scale |
177
+ | ![ICC](images/icc.png) | Item characteristic curve with empirical overlay (item 1) |
178
+ | ![Item difficulty](images/item_difficulty.png) | CTT item difficulty with bootstrap standard-error bars |
179
+ | ![Item discrimination](images/item_discrimination.png) | CTT item point-biserial discrimination |
180
+ | ![DIF contrasts](images/dif_contrasts.png) | DIF contrasts with ETS A/B/C classification |
181
+
182
+ The figures are generated from `simulate_rasch_data` and the analysis pipeline
183
+ (CTT, Rasch MML, DIF).
184
+
185
+ ## Development
186
+
187
+ See [CONTRIBUTING.md](CONTRIBUTING.md). Validation loop: `./.validation.sh`.
188
+
189
+ ## License
190
+
191
+ MIT