pysofra 0.1.0a1__tar.gz → 0.1.0a3__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.
- pysofra-0.1.0a3/.gitignore +45 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/CHANGELOG.md +15 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/PKG-INFO +23 -30
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/README.md +22 -29
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/pyproject.toml +4 -5
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/__init__.py +1 -1
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/stats.py +6 -6
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_extract_edges.py +1 -1
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_extras_edges.py +3 -3
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_joss_api_stability.py +2 -3
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_joss_property_invariants.py +1 -1
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_joss_renderer_consistency.py +1 -1
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_joss_statistical_correctness.py +6 -6
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_misc_fixes.py +7 -8
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_regressions.py +7 -7
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_validation_fixes.py +2 -3
- pysofra-0.1.0a1/.gitignore +0 -90
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/LICENSE +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/NOTICE +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/core/__init__.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/core/compose.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/core/format.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/core/frames.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/core/schema.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/core/table.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/io/__init__.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/models/__init__.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/models/extract.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/models/pool.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/models/regression.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/models/survival.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/models/uvregression.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/notebook/__init__.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/plot/__init__.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/plot/_backend.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/plot/forest.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/plot/inline.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/plot/km.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/render/__init__.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/render/_zip_determinism.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/render/base.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/render/docx.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/render/html.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/render/image.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/render/latex.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/render/markdown.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/render/pptx.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/render/xlsx.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/__init__.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/calibrate.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/design.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/effect_size.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/extras.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/smd.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/tbl_cross.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/tbl_one.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/tbl_summary.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/tests.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/typing.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/weights.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/themes/__init__.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/themes/registry.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/conftest.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/fixtures/scipy_validation/README.md +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/fixtures/scipy_validation/anova_oneway.json +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/fixtures/scipy_validation/chi_square.json +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/fixtures/scipy_validation/fisher_2x2.json +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/fixtures/scipy_validation/kruskal_wallis.json +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/fixtures/scipy_validation/student_t.json +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/fixtures/scipy_validation/svyttest.json +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/fixtures/scipy_validation/weighted_mean.json +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/fixtures/scipy_validation/welch_t_test.json +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/fixtures/scipy_validation/wilcoxon_rank_sum.json +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_compose.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_compose_edges.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_conditional_formatting.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_design_regression.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_extras_edges_2.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_format.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_latex_pptx.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_modifier_edges.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_multi_model.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_partial_modifiers.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_partials.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_plot_determinism.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_plot_embedding.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_plots.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_polars.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_pptx_overflow.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_rao_scott.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_regression.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_render_edges.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_render_edges_2.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_rendering.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_scipy_validation.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_snapshot.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_stats.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_summary_edges.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_summary_edges_2.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_survey_design.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_survey_extensions.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_survival.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_table_edges.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_tbl_one.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_test_overrides.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_uvregression_factors.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_weights.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_wishlist.py +0 -0
- {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_xlsx.py +0 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*.egg-info/
|
|
4
|
+
dist/
|
|
5
|
+
build/
|
|
6
|
+
.venv/
|
|
7
|
+
venv/
|
|
8
|
+
.env
|
|
9
|
+
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
.mypy_cache/
|
|
12
|
+
.ruff_cache/
|
|
13
|
+
.coverage
|
|
14
|
+
htmlcov/
|
|
15
|
+
|
|
16
|
+
.DS_Store
|
|
17
|
+
.idea/
|
|
18
|
+
.vscode/
|
|
19
|
+
.claude/
|
|
20
|
+
|
|
21
|
+
site/
|
|
22
|
+
docs/_build/
|
|
23
|
+
paper/
|
|
24
|
+
|
|
25
|
+
# Tutorial / example export artefacts — produced on demand by
|
|
26
|
+
# scripts/render_tutorial.py, not part of the source tree.
|
|
27
|
+
*.docx
|
|
28
|
+
*.pptx
|
|
29
|
+
*.xlsx
|
|
30
|
+
examples/tutorial_*.docx
|
|
31
|
+
examples/tutorial_*.pptx
|
|
32
|
+
examples/tutorial_*.xlsx
|
|
33
|
+
examples/tutorial_*.png
|
|
34
|
+
!tests/fixtures/**/*.docx
|
|
35
|
+
!tests/fixtures/**/*.pptx
|
|
36
|
+
!tests/fixtures/**/*.xlsx
|
|
37
|
+
|
|
38
|
+
# Hypothesis example database (auto-managed)
|
|
39
|
+
.hypothesis/
|
|
40
|
+
|
|
41
|
+
# uv writes a lockfile when ``uv add`` / ``uv pip install`` is used
|
|
42
|
+
# locally; the project pins its runtime versions explicitly in
|
|
43
|
+
# pyproject.toml, so the uv lockfile is a local convenience that
|
|
44
|
+
# should not ship.
|
|
45
|
+
uv.lock
|
|
@@ -5,6 +5,21 @@ All notable changes to PySofra will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.0a3] — 2026-05-24
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Documentation polish: scrubbed internal-process references from test
|
|
12
|
+
docstrings and inline comments. No public API or behavioural changes.
|
|
13
|
+
|
|
14
|
+
## [0.1.0a2] — 2026-05-23
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Theme styling now survives notebook viewers that strip `<style>` blocks
|
|
18
|
+
(e.g. GitHub's notebook viewer). Critical theme properties (font, border,
|
|
19
|
+
padding) are emitted as inline `style` attributes on each table element, so
|
|
20
|
+
`jama` vs `nejm` vs `clinical` vs `minimal` stay visibly distinct everywhere.
|
|
21
|
+
- README image and link URLs are now absolute so they render on PyPI.
|
|
22
|
+
|
|
8
23
|
## [0.1.0a1] — 2026-05-20
|
|
9
24
|
|
|
10
25
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pysofra
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0a3
|
|
4
4
|
Summary: Statistical reporting and table preparation framework for Python — the missing reporting layer.
|
|
5
5
|
Project-URL: Homepage, https://github.com/jturner-uofl/pysofra
|
|
6
6
|
Project-URL: Documentation, https://github.com/jturner-uofl/pysofra
|
|
@@ -72,7 +72,7 @@ Description-Content-Type: text/markdown
|
|
|
72
72
|
|
|
73
73
|
[](https://github.com/jturner-uofl/pysofra)
|
|
74
74
|
[](https://www.python.org/downloads/)
|
|
75
|
-
[](LICENSE)
|
|
75
|
+
[](https://github.com/jturner-uofl/pysofra/blob/main/LICENSE)
|
|
76
76
|
[](https://github.com/astral-sh/ruff)
|
|
77
77
|
[](http://mypy-lang.org/)
|
|
78
78
|
[](#status)
|
|
@@ -111,14 +111,14 @@ Description-Content-Type: text/markdown
|
|
|
111
111
|
- **One immutable object, seven output formats** — build a `SofraTable` once, render to HTML / Markdown / LaTeX / DOCX / PPTX / XLSX / PNG, all byte-deterministic across processes
|
|
112
112
|
- **Auto-dispatched statistical tests** — Welch, Wilcoxon, ANOVA, Kruskal–Wallis, Fisher, χ², Rao–Scott, design-adjusted *t* — picked by variable kind, overridable per-row
|
|
113
113
|
- **Inline forest plots and KM curves** — embed matplotlib figures directly into the table; the same `SofraTable` renders them across every backend
|
|
114
|
-
- **Statistically correct** — every numeric output validated against `scipy` / `statsmodels` / `lifelines` at machine precision
|
|
114
|
+
- **Statistically correct** — every numeric output validated against `scipy` / `statsmodels` / `lifelines` at machine precision, with cross-checks against R's `gtsummary`
|
|
115
115
|
- **Method-chainable and immutable** — every modifier returns a new table; no in-place mutation, no global state, fully reproducible
|
|
116
116
|
|
|
117
117
|
<div align="center">
|
|
118
118
|
|
|
119
|
-
**[Showcase notebook](examples/pysofra_showcase.ipynb)**
|
|
119
|
+
**[Showcase notebook](https://github.com/jturner-uofl/pysofra/blob/main/examples/pysofra_showcase.ipynb)** — *47 cells, every section a side-by-side numeric proof. Start here if you have 60 seconds.*
|
|
120
120
|
|
|
121
|
-
**[End-to-end tutorial](examples/pysofra_tutorial.ipynb)**
|
|
121
|
+
**[End-to-end tutorial](https://github.com/jturner-uofl/pysofra/blob/main/examples/pysofra_tutorial.ipynb)** — *126 cells walking every public feature on a synthetic two-arm trial.*
|
|
122
122
|
|
|
123
123
|
</div>
|
|
124
124
|
|
|
@@ -171,10 +171,10 @@ fit = sm.Logit(df["event"], X).fit(disp=False)
|
|
|
171
171
|
)
|
|
172
172
|
```
|
|
173
173
|
|
|
174
|
-
The
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
[
|
|
174
|
+
The end-to-end worked example — baseline table by treatment arm,
|
|
175
|
+
regression table with forest plot, and Kaplan-Meier survival summary —
|
|
176
|
+
is in the
|
|
177
|
+
[showcase notebook](https://github.com/jturner-uofl/pysofra/blob/main/examples/pysofra_showcase.ipynb).
|
|
178
178
|
|
|
179
179
|
---
|
|
180
180
|
|
|
@@ -255,47 +255,40 @@ pip install "pysofra[dev]" # testing + linting (pytest, ruff, mypy, hypot
|
|
|
255
255
|
|
|
256
256
|
## Status
|
|
257
257
|
|
|
258
|
-
PySofra is in **alpha** (`0.1.
|
|
258
|
+
PySofra is in **alpha** (`0.1.0a3`). The public API surface is pinned
|
|
259
259
|
by an explicit
|
|
260
|
-
[API-stability test](tests/test_joss_api_stability.py)
|
|
261
|
-
unintended rename, removal, or signature change surfaces as
|
|
262
|
-
test. Quality bar at this release:
|
|
260
|
+
[API-stability test](https://github.com/jturner-uofl/pysofra/blob/main/tests/test_joss_api_stability.py)
|
|
261
|
+
so that any unintended rename, removal, or signature change surfaces as
|
|
262
|
+
a failed test. Quality bar at this release:
|
|
263
263
|
|
|
264
264
|
* **More than 800 tests passing**, **100% line coverage**, mypy strict, ruff clean.
|
|
265
265
|
* Every numeric output is validated against `scipy`, `lifelines`,
|
|
266
266
|
`statsmodels`, or a hand-computed textbook formula
|
|
267
|
-
([test_joss_statistical_correctness.py](tests/test_joss_statistical_correctness.py)).
|
|
267
|
+
([test_joss_statistical_correctness.py](https://github.com/jturner-uofl/pysofra/blob/main/tests/test_joss_statistical_correctness.py)).
|
|
268
268
|
* Universal invariants enforced via Hypothesis on 720 randomized
|
|
269
269
|
examples per CI run
|
|
270
|
-
([test_joss_property_invariants.py](tests/test_joss_property_invariants.py)).
|
|
270
|
+
([test_joss_property_invariants.py](https://github.com/jturner-uofl/pysofra/blob/main/tests/test_joss_property_invariants.py)).
|
|
271
271
|
* Renderer output is byte-deterministic — identical input always
|
|
272
272
|
produces identical HTML/Markdown/LaTeX, required for reproducible
|
|
273
273
|
publication artifacts
|
|
274
|
-
([test_joss_renderer_consistency.py](tests/test_joss_renderer_consistency.py)).
|
|
274
|
+
([test_joss_renderer_consistency.py](https://github.com/jturner-uofl/pysofra/blob/main/tests/test_joss_renderer_consistency.py)).
|
|
275
275
|
|
|
276
276
|
Bug reports and use-case feedback are very welcome.
|
|
277
277
|
|
|
278
|
-
A **Journal of Statistical Software** paper ([`paper/paper.tex`](paper/paper.tex),
|
|
279
|
-
bibliography in [`paper/paper.bib`](paper/paper.bib)) is in
|
|
280
|
-
preparation. The [`paper/replication/`](paper/replication/) directory
|
|
281
|
-
regenerates every numeric output and figure shown in the paper and
|
|
282
|
-
includes an R cross-check script (`example_trial.R`) using
|
|
283
|
-
`gtsummary` for digit-level verification. The
|
|
284
|
-
[`paper/figures/`](paper/figures/) directory holds the rendered PDF
|
|
285
|
-
and PNG embedded in the manuscript via `\includegraphics{}`.
|
|
286
|
-
|
|
287
278
|
## Contributing
|
|
288
279
|
|
|
289
280
|
Bug reports, feature requests, and pull requests are all very welcome.
|
|
290
|
-
Please read
|
|
291
|
-
|
|
292
|
-
|
|
281
|
+
Please read
|
|
282
|
+
[`CONTRIBUTING.md`](https://github.com/jturner-uofl/pysofra/blob/main/CONTRIBUTING.md)
|
|
283
|
+
for the workflow, the quality gates, and the
|
|
284
|
+
[Code of Conduct](https://github.com/jturner-uofl/pysofra/blob/main/CODE_OF_CONDUCT.md).
|
|
293
285
|
|
|
294
286
|
## License
|
|
295
287
|
|
|
296
|
-
GPL-3.0-or-later. See
|
|
288
|
+
GPL-3.0-or-later. See
|
|
289
|
+
[`LICENSE`](https://github.com/jturner-uofl/pysofra/blob/main/LICENSE).
|
|
297
290
|
|
|
298
291
|
## Citation
|
|
299
292
|
|
|
300
293
|
If you use PySofra in academic work, please cite the project — see
|
|
301
|
-
[`CITATION.cff`](CITATION.cff).
|
|
294
|
+
[`CITATION.cff`](https://github.com/jturner-uofl/pysofra/blob/main/CITATION.cff).
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
[](https://github.com/jturner-uofl/pysofra)
|
|
8
8
|
[](https://www.python.org/downloads/)
|
|
9
|
-
[](LICENSE)
|
|
9
|
+
[](https://github.com/jturner-uofl/pysofra/blob/main/LICENSE)
|
|
10
10
|
[](https://github.com/astral-sh/ruff)
|
|
11
11
|
[](http://mypy-lang.org/)
|
|
12
12
|
[](#status)
|
|
@@ -45,14 +45,14 @@
|
|
|
45
45
|
- **One immutable object, seven output formats** — build a `SofraTable` once, render to HTML / Markdown / LaTeX / DOCX / PPTX / XLSX / PNG, all byte-deterministic across processes
|
|
46
46
|
- **Auto-dispatched statistical tests** — Welch, Wilcoxon, ANOVA, Kruskal–Wallis, Fisher, χ², Rao–Scott, design-adjusted *t* — picked by variable kind, overridable per-row
|
|
47
47
|
- **Inline forest plots and KM curves** — embed matplotlib figures directly into the table; the same `SofraTable` renders them across every backend
|
|
48
|
-
- **Statistically correct** — every numeric output validated against `scipy` / `statsmodels` / `lifelines` at machine precision
|
|
48
|
+
- **Statistically correct** — every numeric output validated against `scipy` / `statsmodels` / `lifelines` at machine precision, with cross-checks against R's `gtsummary`
|
|
49
49
|
- **Method-chainable and immutable** — every modifier returns a new table; no in-place mutation, no global state, fully reproducible
|
|
50
50
|
|
|
51
51
|
<div align="center">
|
|
52
52
|
|
|
53
|
-
**[Showcase notebook](examples/pysofra_showcase.ipynb)**
|
|
53
|
+
**[Showcase notebook](https://github.com/jturner-uofl/pysofra/blob/main/examples/pysofra_showcase.ipynb)** — *47 cells, every section a side-by-side numeric proof. Start here if you have 60 seconds.*
|
|
54
54
|
|
|
55
|
-
**[End-to-end tutorial](examples/pysofra_tutorial.ipynb)**
|
|
55
|
+
**[End-to-end tutorial](https://github.com/jturner-uofl/pysofra/blob/main/examples/pysofra_tutorial.ipynb)** — *126 cells walking every public feature on a synthetic two-arm trial.*
|
|
56
56
|
|
|
57
57
|
</div>
|
|
58
58
|
|
|
@@ -105,10 +105,10 @@ fit = sm.Logit(df["event"], X).fit(disp=False)
|
|
|
105
105
|
)
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
The
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
[
|
|
108
|
+
The end-to-end worked example — baseline table by treatment arm,
|
|
109
|
+
regression table with forest plot, and Kaplan-Meier survival summary —
|
|
110
|
+
is in the
|
|
111
|
+
[showcase notebook](https://github.com/jturner-uofl/pysofra/blob/main/examples/pysofra_showcase.ipynb).
|
|
112
112
|
|
|
113
113
|
---
|
|
114
114
|
|
|
@@ -189,47 +189,40 @@ pip install "pysofra[dev]" # testing + linting (pytest, ruff, mypy, hypot
|
|
|
189
189
|
|
|
190
190
|
## Status
|
|
191
191
|
|
|
192
|
-
PySofra is in **alpha** (`0.1.
|
|
192
|
+
PySofra is in **alpha** (`0.1.0a3`). The public API surface is pinned
|
|
193
193
|
by an explicit
|
|
194
|
-
[API-stability test](tests/test_joss_api_stability.py)
|
|
195
|
-
unintended rename, removal, or signature change surfaces as
|
|
196
|
-
test. Quality bar at this release:
|
|
194
|
+
[API-stability test](https://github.com/jturner-uofl/pysofra/blob/main/tests/test_joss_api_stability.py)
|
|
195
|
+
so that any unintended rename, removal, or signature change surfaces as
|
|
196
|
+
a failed test. Quality bar at this release:
|
|
197
197
|
|
|
198
198
|
* **More than 800 tests passing**, **100% line coverage**, mypy strict, ruff clean.
|
|
199
199
|
* Every numeric output is validated against `scipy`, `lifelines`,
|
|
200
200
|
`statsmodels`, or a hand-computed textbook formula
|
|
201
|
-
([test_joss_statistical_correctness.py](tests/test_joss_statistical_correctness.py)).
|
|
201
|
+
([test_joss_statistical_correctness.py](https://github.com/jturner-uofl/pysofra/blob/main/tests/test_joss_statistical_correctness.py)).
|
|
202
202
|
* Universal invariants enforced via Hypothesis on 720 randomized
|
|
203
203
|
examples per CI run
|
|
204
|
-
([test_joss_property_invariants.py](tests/test_joss_property_invariants.py)).
|
|
204
|
+
([test_joss_property_invariants.py](https://github.com/jturner-uofl/pysofra/blob/main/tests/test_joss_property_invariants.py)).
|
|
205
205
|
* Renderer output is byte-deterministic — identical input always
|
|
206
206
|
produces identical HTML/Markdown/LaTeX, required for reproducible
|
|
207
207
|
publication artifacts
|
|
208
|
-
([test_joss_renderer_consistency.py](tests/test_joss_renderer_consistency.py)).
|
|
208
|
+
([test_joss_renderer_consistency.py](https://github.com/jturner-uofl/pysofra/blob/main/tests/test_joss_renderer_consistency.py)).
|
|
209
209
|
|
|
210
210
|
Bug reports and use-case feedback are very welcome.
|
|
211
211
|
|
|
212
|
-
A **Journal of Statistical Software** paper ([`paper/paper.tex`](paper/paper.tex),
|
|
213
|
-
bibliography in [`paper/paper.bib`](paper/paper.bib)) is in
|
|
214
|
-
preparation. The [`paper/replication/`](paper/replication/) directory
|
|
215
|
-
regenerates every numeric output and figure shown in the paper and
|
|
216
|
-
includes an R cross-check script (`example_trial.R`) using
|
|
217
|
-
`gtsummary` for digit-level verification. The
|
|
218
|
-
[`paper/figures/`](paper/figures/) directory holds the rendered PDF
|
|
219
|
-
and PNG embedded in the manuscript via `\includegraphics{}`.
|
|
220
|
-
|
|
221
212
|
## Contributing
|
|
222
213
|
|
|
223
214
|
Bug reports, feature requests, and pull requests are all very welcome.
|
|
224
|
-
Please read
|
|
225
|
-
|
|
226
|
-
|
|
215
|
+
Please read
|
|
216
|
+
[`CONTRIBUTING.md`](https://github.com/jturner-uofl/pysofra/blob/main/CONTRIBUTING.md)
|
|
217
|
+
for the workflow, the quality gates, and the
|
|
218
|
+
[Code of Conduct](https://github.com/jturner-uofl/pysofra/blob/main/CODE_OF_CONDUCT.md).
|
|
227
219
|
|
|
228
220
|
## License
|
|
229
221
|
|
|
230
|
-
GPL-3.0-or-later. See
|
|
222
|
+
GPL-3.0-or-later. See
|
|
223
|
+
[`LICENSE`](https://github.com/jturner-uofl/pysofra/blob/main/LICENSE).
|
|
231
224
|
|
|
232
225
|
## Citation
|
|
233
226
|
|
|
234
227
|
If you use PySofra in academic work, please cite the project — see
|
|
235
|
-
[`CITATION.cff`](CITATION.cff).
|
|
228
|
+
[`CITATION.cff`](https://github.com/jturner-uofl/pysofra/blob/main/CITATION.cff).
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pysofra"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.0a3"
|
|
8
8
|
description = "Statistical reporting and table preparation framework for Python — the missing reporting layer."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "GPL-3.0-or-later" }
|
|
@@ -101,9 +101,8 @@ packages = ["src/pysofra"]
|
|
|
101
101
|
|
|
102
102
|
[tool.hatch.build.targets.sdist]
|
|
103
103
|
# The sdist that goes on PyPI ships the library + tests + license +
|
|
104
|
-
# changelog.
|
|
105
|
-
#
|
|
106
|
-
# download it from the submission tarball, not from PyPI.
|
|
104
|
+
# changelog. Docs, examples, build scripts, and CI config are excluded
|
|
105
|
+
# — users get the importable package, not the development surround.
|
|
107
106
|
include = [
|
|
108
107
|
"src/pysofra",
|
|
109
108
|
"tests",
|
|
@@ -114,13 +113,13 @@ include = [
|
|
|
114
113
|
"pyproject.toml",
|
|
115
114
|
]
|
|
116
115
|
exclude = [
|
|
117
|
-
"paper",
|
|
118
116
|
"docs",
|
|
119
117
|
"examples",
|
|
120
118
|
"scripts",
|
|
121
119
|
"site",
|
|
122
120
|
".github",
|
|
123
121
|
".claude",
|
|
122
|
+
"paper",
|
|
124
123
|
"uv.lock",
|
|
125
124
|
]
|
|
126
125
|
|
|
@@ -45,12 +45,12 @@ def continuous_stats(series: pd.Series) -> ContinuousStats:
|
|
|
45
45
|
# subtract`` (and similar). Under ``filterwarnings = error`` — which
|
|
46
46
|
# this project's own pyproject.toml sets and which is a common
|
|
47
47
|
# user-side ``-W error`` posture — those warnings escalate to
|
|
48
|
-
# exceptions and crash ``tbl_one`` on perfectly legal data.
|
|
49
|
-
#
|
|
50
|
-
# ``
|
|
51
|
-
# Wrap arithmetic in ``np.errstate`` + ``catch_warnings`` so
|
|
52
|
-
# stats compute cleanly to ``nan`` / ``inf`` (which the
|
|
53
|
-
# then render as em-dash).
|
|
48
|
+
# exceptions and crash ``tbl_one`` on perfectly legal data. An
|
|
49
|
+
# earlier fix in ``infer_kind`` handled the ``int(np.inf) →
|
|
50
|
+
# OverflowError`` path but did not reach this downstream stats
|
|
51
|
+
# site. Wrap arithmetic in ``np.errstate`` + ``catch_warnings`` so
|
|
52
|
+
# the stats compute cleanly to ``nan`` / ``inf`` (which the
|
|
53
|
+
# formatters then render as em-dash).
|
|
54
54
|
with np.errstate(invalid="ignore", over="ignore"), warnings.catch_warnings():
|
|
55
55
|
warnings.simplefilter("ignore", RuntimeWarning)
|
|
56
56
|
mean = float(np.mean(arr))
|
|
@@ -236,7 +236,7 @@ class TestExtrasFinal:
|
|
|
236
236
|
def test_add_global_p_metadata_missing_f_test(self):
|
|
237
237
|
# A SofraTable whose metadata['model'] doesn't have .f_test now
|
|
238
238
|
# raises NotImplementedError rather than silently inserting an
|
|
239
|
-
# em-dash column (which
|
|
239
|
+
# em-dash column (which would be misleading).
|
|
240
240
|
from pysofra.core.schema import Cell, HeaderCell, HeaderRow, Row
|
|
241
241
|
from pysofra.core.table import SofraTable
|
|
242
242
|
|
|
@@ -353,9 +353,9 @@ class TestTypingCorners:
|
|
|
353
353
|
def test_datetime_falls_through_to_categorical(self):
|
|
354
354
|
# Backwards-compatible fallback: PySofra doesn't natively
|
|
355
355
|
# summarise datetimes, so it returns ``"categorical"`` (so the
|
|
356
|
-
# caller's table doesn't crash). The warning emitted
|
|
357
|
-
#
|
|
358
|
-
# tests/
|
|
356
|
+
# caller's table doesn't crash). The warning emitted alongside
|
|
357
|
+
# the fallback is verified in
|
|
358
|
+
# tests/test_regressions.py::TestInferKindWarnsOnTemporal.
|
|
359
359
|
import pytest
|
|
360
360
|
|
|
361
361
|
from pysofra.summary.typing import infer_kind
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Public API-stability snapshot.
|
|
2
2
|
|
|
3
3
|
Freezes the public surface of ``pysofra`` so any unintended rename,
|
|
4
4
|
removal, or signature change surfaces as a failed test instead of a
|
|
5
5
|
silent breakage downstream.
|
|
6
6
|
|
|
7
7
|
If you intentionally change the API, update the constants in this file
|
|
8
|
-
**and** bump the project version per semver.
|
|
9
|
-
downstream users that JOSS will scrutinise.
|
|
8
|
+
**and** bump the project version per semver.
|
|
10
9
|
"""
|
|
11
10
|
|
|
12
11
|
from __future__ import annotations
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Statistical correctness validation against independent references.
|
|
2
2
|
|
|
3
3
|
Every numeric routine in PySofra is verified against an independent
|
|
4
4
|
reference: scipy / lifelines / statsmodels direct calls, hand-computed
|
|
5
|
-
textbook formulas, or published reference values.
|
|
6
|
-
|
|
7
|
-
``
|
|
8
|
-
|
|
5
|
+
textbook formulas, or published reference values. Any number rendered
|
|
6
|
+
by ``tbl_one`` / ``tbl_regression`` / ``tbl_survival`` /
|
|
7
|
+
``tbl_uvregression`` can be traced back through this file to where it
|
|
8
|
+
came from.
|
|
9
9
|
|
|
10
|
-
Each test
|
|
10
|
+
Each test names its reference source in the docstring.
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
from __future__ import annotations
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Targeted regression tests for previously-fixed defects.
|
|
2
2
|
|
|
3
|
-
Each test pins a
|
|
4
|
-
|
|
5
|
-
locate.
|
|
3
|
+
Each test pins a specific bug so that any regression is easy to
|
|
4
|
+
locate by name.
|
|
6
5
|
"""
|
|
7
6
|
|
|
8
7
|
from __future__ import annotations
|
|
@@ -15,7 +14,7 @@ import pysofra as ps
|
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
# ----------------------------------------------------------------------
|
|
18
|
-
#
|
|
17
|
+
# Predictor / adjust_for overlap raises cleanly
|
|
19
18
|
# ----------------------------------------------------------------------
|
|
20
19
|
class TestUvregressionOverlapErrors:
|
|
21
20
|
def test_predictor_also_in_adjust_for_raises(self):
|
|
@@ -46,7 +45,7 @@ class TestUvregressionOverlapErrors:
|
|
|
46
45
|
|
|
47
46
|
|
|
48
47
|
# ----------------------------------------------------------------------
|
|
49
|
-
#
|
|
48
|
+
# Formula-API model works through the design refit
|
|
50
49
|
# ----------------------------------------------------------------------
|
|
51
50
|
class TestFormulaAPIRoundTrip:
|
|
52
51
|
def test_design_refit_with_smf_ols(self):
|
|
@@ -69,7 +68,7 @@ class TestFormulaAPIRoundTrip:
|
|
|
69
68
|
|
|
70
69
|
|
|
71
70
|
# ----------------------------------------------------------------------
|
|
72
|
-
#
|
|
71
|
+
# High-cardinality factor scales reasonably
|
|
73
72
|
# ----------------------------------------------------------------------
|
|
74
73
|
class TestHighCardinalityFactor:
|
|
75
74
|
def test_50_level_factor_completes_quickly(self):
|
|
@@ -91,7 +90,7 @@ class TestHighCardinalityFactor:
|
|
|
91
90
|
|
|
92
91
|
|
|
93
92
|
# ----------------------------------------------------------------------
|
|
94
|
-
#
|
|
93
|
+
# Newcombe handles imbalanced n
|
|
95
94
|
# ----------------------------------------------------------------------
|
|
96
95
|
class TestNewcombeImbalanced:
|
|
97
96
|
def test_imbalanced_n_produces_valid_ci(self):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Targeted regression tests for previously-fixed defects.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Each test corresponds to a real bug and the patch that closed it.
|
|
4
|
+
Each test should fail if the patch is reverted.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
@@ -630,9 +630,9 @@ class TestInferKindInfSafe:
|
|
|
630
630
|
through. Under ``filterwarnings = error`` (the project's own
|
|
631
631
|
pyproject.toml gate, and a common user ``-W error`` posture), that
|
|
632
632
|
warning escalates to an exception and the table build crashes
|
|
633
|
-
despite the
|
|
634
|
-
below pins the
|
|
635
|
-
end-to-end clean.
|
|
633
|
+
despite the earlier ``int(np.inf)`` fix in ``infer_kind``.
|
|
634
|
+
``test_inf_does_not_leak_runtime_warning`` below pins the
|
|
635
|
+
strict-warning behaviour so the inf path is end-to-end clean.
|
|
636
636
|
"""
|
|
637
637
|
|
|
638
638
|
def test_inf_in_numeric_column_does_not_crash(self):
|
|
@@ -685,7 +685,7 @@ class TestInferKindInfSafe:
|
|
|
685
685
|
|
|
686
686
|
|
|
687
687
|
# ----------------------------------------------------------------------
|
|
688
|
-
# SofraTable must round-trip through pickle
|
|
688
|
+
# SofraTable must round-trip through pickle
|
|
689
689
|
# ----------------------------------------------------------------------
|
|
690
690
|
class TestSofraTablePicklability:
|
|
691
691
|
"""A SofraTable produced by a builder must survive ``pickle.dumps``/
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"""Regression tests for input-validation edge cases.
|
|
2
2
|
|
|
3
|
-
Each test pins a specific defect
|
|
4
|
-
|
|
5
|
-
finding.
|
|
3
|
+
Each test pins a specific defect so any future regression surfaces
|
|
4
|
+
with a clear name pointing back at the finding.
|
|
6
5
|
"""
|
|
7
6
|
|
|
8
7
|
from __future__ import annotations
|
pysofra-0.1.0a1/.gitignore
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
__pycache__/
|
|
2
|
-
*.py[cod]
|
|
3
|
-
*.egg-info/
|
|
4
|
-
dist/
|
|
5
|
-
build/
|
|
6
|
-
.venv/
|
|
7
|
-
venv/
|
|
8
|
-
.env
|
|
9
|
-
|
|
10
|
-
.pytest_cache/
|
|
11
|
-
.mypy_cache/
|
|
12
|
-
.ruff_cache/
|
|
13
|
-
.coverage
|
|
14
|
-
htmlcov/
|
|
15
|
-
|
|
16
|
-
.DS_Store
|
|
17
|
-
.idea/
|
|
18
|
-
.vscode/
|
|
19
|
-
.claude/
|
|
20
|
-
|
|
21
|
-
site/
|
|
22
|
-
docs/_build/
|
|
23
|
-
|
|
24
|
-
# Tutorial / example export artefacts — produced on demand by
|
|
25
|
-
# scripts/render_tutorial.py, not part of the source tree.
|
|
26
|
-
*.docx
|
|
27
|
-
*.pptx
|
|
28
|
-
*.xlsx
|
|
29
|
-
examples/tutorial_*.docx
|
|
30
|
-
examples/tutorial_*.pptx
|
|
31
|
-
examples/tutorial_*.xlsx
|
|
32
|
-
examples/tutorial_*.png
|
|
33
|
-
!tests/fixtures/**/*.docx
|
|
34
|
-
!tests/fixtures/**/*.pptx
|
|
35
|
-
!tests/fixtures/**/*.xlsx
|
|
36
|
-
|
|
37
|
-
# Hypothesis example database (auto-managed)
|
|
38
|
-
.hypothesis/
|
|
39
|
-
|
|
40
|
-
# uv writes a lockfile when ``uv add`` / ``uv pip install`` is used
|
|
41
|
-
# locally; the project pins its runtime versions explicitly in
|
|
42
|
-
# pyproject.toml + paper/replication/requirements.txt, so the uv
|
|
43
|
-
# lockfile is a local convenience that should not ship.
|
|
44
|
-
uv.lock
|
|
45
|
-
|
|
46
|
-
# JSS paper bundle — paper/ contains the manuscript, bibliography,
|
|
47
|
-
# embedded figures, and replication archive. The .tex / .bib / .py /
|
|
48
|
-
# .R sources are ALWAYS tracked. Replication outputs are tracked in
|
|
49
|
-
# the text formats reviewers diff (json / tex / md / html / png /
|
|
50
|
-
# pdf / svg) so the archive ships with a working baseline; only the
|
|
51
|
-
# heavy regenerable Office binaries (docx / pptx / xlsx) and the
|
|
52
|
-
# fontconfig / mpl cache directories are ignored.
|
|
53
|
-
paper/replication/trial.csv
|
|
54
|
-
paper/replication/table*.docx
|
|
55
|
-
paper/replication/table*.pptx
|
|
56
|
-
paper/replication/table*.xlsx
|
|
57
|
-
paper/replication/.mplconfig/
|
|
58
|
-
|
|
59
|
-
# Submission-required text outputs and figures: explicit re-include.
|
|
60
|
-
# The blanket repo-wide ``*.docx`` rule at the top of this file would
|
|
61
|
-
# otherwise eat ``paper/replication/table*.html`` etc. — these
|
|
62
|
-
# negations keep them tracked.
|
|
63
|
-
!paper/replication/paper_outputs*.json
|
|
64
|
-
!paper/replication/table*.html
|
|
65
|
-
!paper/replication/table*.tex
|
|
66
|
-
!paper/replication/table*.md
|
|
67
|
-
!paper/replication/figures/
|
|
68
|
-
!paper/replication/figures/*
|
|
69
|
-
|
|
70
|
-
# The `paper/figures/` directory IS tracked because paper.tex embeds
|
|
71
|
-
# its PDFs/PNGs via \includegraphics. To regenerate, run
|
|
72
|
-
# paper/replication/example_trial.py then copy the desired files in.
|
|
73
|
-
!paper/figures/
|
|
74
|
-
!paper/figures/*
|
|
75
|
-
|
|
76
|
-
# LaTeX build artefacts (in case anyone compiles paper.tex locally).
|
|
77
|
-
paper/*.aux
|
|
78
|
-
paper/*.log
|
|
79
|
-
paper/*.out
|
|
80
|
-
paper/*.toc
|
|
81
|
-
paper/*.bbl
|
|
82
|
-
paper/*.blg
|
|
83
|
-
paper/*.synctex.gz
|
|
84
|
-
paper/paper.pdf
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
# paper/ is excluded from the public repo until JSS submission lands.
|
|
88
|
-
# It stays in the local working tree for editing; the replication
|
|
89
|
-
# archive moves into the repo at submission time.
|
|
90
|
-
paper/
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|