specmod 0.2.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 (211) hide show
  1. specmod-0.2.0/.git-blame-ignore-revs +6 -0
  2. specmod-0.2.0/.github/workflows/build.yml +40 -0
  3. specmod-0.2.0/.github/workflows/docs.yml +51 -0
  4. specmod-0.2.0/.github/workflows/release.yml +76 -0
  5. specmod-0.2.0/.github/workflows/test.yml +130 -0
  6. specmod-0.2.0/.gitignore +48 -0
  7. specmod-0.2.0/.pre-commit-config.yaml +62 -0
  8. specmod-0.2.0/.readthedocs.yaml +47 -0
  9. specmod-0.2.0/.release-please-manifest.json +3 -0
  10. specmod-0.2.0/AGENTS.md +92 -0
  11. specmod-0.2.0/CHANGELOG.md +175 -0
  12. specmod-0.2.0/CITATION.cff +59 -0
  13. specmod-0.2.0/CLAUDE.md +5 -0
  14. specmod-0.2.0/CONTRIBUTING.md +90 -0
  15. specmod-0.2.0/LICENSE +21 -0
  16. specmod-0.2.0/PKG-INFO +294 -0
  17. specmod-0.2.0/README.md +238 -0
  18. specmod-0.2.0/datasets/magna_2020.toml +45 -0
  19. specmod-0.2.0/datasets/pnr_2019.toml +37 -0
  20. specmod-0.2.0/docs/REFACTOR_PLAN.md +3704 -0
  21. specmod-0.2.0/docs/api.md +121 -0
  22. specmod-0.2.0/docs/choosing-a-transform.md +608 -0
  23. specmod-0.2.0/docs/conf.py +110 -0
  24. specmod-0.2.0/docs/development.md +427 -0
  25. specmod-0.2.0/docs/documentation.md +233 -0
  26. specmod-0.2.0/docs/index.md +109 -0
  27. specmod-0.2.0/docs/notebooks/_build_notebook.py +683 -0
  28. specmod-0.2.0/docs/notebooks/choosing-a-transform.ipynb +887 -0
  29. specmod-0.2.0/docs/notes/api-audit.md +201 -0
  30. specmod-0.2.0/docs/notes/window-position.md +85 -0
  31. specmod-0.2.0/docs/pick-formats.md +215 -0
  32. specmod-0.2.0/docs/processing.md +403 -0
  33. specmod-0.2.0/docs/releasing-data.md +206 -0
  34. specmod-0.2.0/docs/releasing.md +167 -0
  35. specmod-0.2.0/docs/roadmap.md +109 -0
  36. specmod-0.2.0/pyproject.toml +260 -0
  37. specmod-0.2.0/release-please-config.json +26 -0
  38. specmod-0.2.0/requirements.txt +52 -0
  39. specmod-0.2.0/src/specmod/__init__.py +17 -0
  40. specmod-0.2.0/src/specmod/_vendor/__init__.py +21 -0
  41. specmod-0.2.0/src/specmod/_vendor/qiinv.py +243 -0
  42. specmod-0.2.0/src/specmod/acquire.py +358 -0
  43. specmod-0.2.0/src/specmod/api.py +480 -0
  44. specmod-0.2.0/src/specmod/cli.py +139 -0
  45. specmod-0.2.0/src/specmod/config/__init__.py +44 -0
  46. specmod-0.2.0/src/specmod/config/layers.py +168 -0
  47. specmod-0.2.0/src/specmod/config/provenance.py +77 -0
  48. specmod-0.2.0/src/specmod/config/sections.py +385 -0
  49. specmod-0.2.0/src/specmod/config/serialize.py +58 -0
  50. specmod-0.2.0/src/specmod/core/__init__.py +41 -0
  51. specmod-0.2.0/src/specmod/core/bandwidth.py +187 -0
  52. specmod-0.2.0/src/specmod/core/collection.py +549 -0
  53. specmod-0.2.0/src/specmod/core/noise.py +478 -0
  54. specmod-0.2.0/src/specmod/core/scalogram.py +234 -0
  55. specmod-0.2.0/src/specmod/core/spectrum.py +326 -0
  56. specmod-0.2.0/src/specmod/core/units.py +116 -0
  57. specmod-0.2.0/src/specmod/datasets.py +316 -0
  58. specmod-0.2.0/src/specmod/distance.py +190 -0
  59. specmod-0.2.0/src/specmod/exceptions.py +58 -0
  60. specmod-0.2.0/src/specmod/fitting/__init__.py +58 -0
  61. specmod-0.2.0/src/specmod/fitting/base.py +50 -0
  62. specmod-0.2.0/src/specmod/fitting/event.py +284 -0
  63. specmod-0.2.0/src/specmod/fitting/guess.py +170 -0
  64. specmod-0.2.0/src/specmod/fitting/spectrum.py +330 -0
  65. specmod-0.2.0/src/specmod/io.py +241 -0
  66. specmod-0.2.0/src/specmod/magnitude.py +312 -0
  67. specmod-0.2.0/src/specmod/picks/__init__.py +182 -0
  68. specmod-0.2.0/src/specmod/picks/base.py +250 -0
  69. specmod-0.2.0/src/specmod/picks/delimited.py +224 -0
  70. specmod-0.2.0/src/specmod/picks/events.py +157 -0
  71. specmod-0.2.0/src/specmod/picks/resolution.py +149 -0
  72. specmod-0.2.0/src/specmod/picks/snuffler.py +92 -0
  73. specmod-0.2.0/src/specmod/pipeline.py +280 -0
  74. specmod-0.2.0/src/specmod/plotting.py +203 -0
  75. specmod-0.2.0/src/specmod/preprocess.py +554 -0
  76. specmod-0.2.0/src/specmod/smoothing/__init__.py +50 -0
  77. specmod-0.2.0/src/specmod/smoothing/base.py +56 -0
  78. specmod-0.2.0/src/specmod/smoothing/konno_ohmachi.py +83 -0
  79. specmod-0.2.0/src/specmod/smoothing/log_bins.py +171 -0
  80. specmod-0.2.0/src/specmod/sources/__init__.py +65 -0
  81. specmod-0.2.0/src/specmod/sources/attenuation.py +110 -0
  82. specmod-0.2.0/src/specmod/sources/composite.py +135 -0
  83. specmod-0.2.0/src/specmod/sources/motion.py +40 -0
  84. specmod-0.2.0/src/specmod/sources/source.py +147 -0
  85. specmod-0.2.0/src/specmod/spreading.py +209 -0
  86. specmod-0.2.0/src/specmod/staged.py +523 -0
  87. specmod-0.2.0/src/specmod/tables.py +110 -0
  88. specmod-0.2.0/src/specmod/transforms/__init__.py +50 -0
  89. specmod-0.2.0/src/specmod/transforms/base.py +242 -0
  90. specmod-0.2.0/src/specmod/transforms/cwt.py +219 -0
  91. specmod-0.2.0/src/specmod/transforms/fft.py +157 -0
  92. specmod-0.2.0/src/specmod/transforms/multitaper.py +357 -0
  93. specmod-0.2.0/src/specmod/transforms/prieto.py +272 -0
  94. specmod-0.2.0/src/specmod/transforms/quadratic.py +221 -0
  95. specmod-0.2.0/src/specmod/utils.py +305 -0
  96. specmod-0.2.0/stubs/README.md +75 -0
  97. specmod-0.2.0/stubs/lmfit/__init__.pyi +2 -0
  98. specmod-0.2.0/stubs/lmfit/model.pyi +65 -0
  99. specmod-0.2.0/stubs/lmfit/parameter.pyi +48 -0
  100. specmod-0.2.0/stubs/obspy/__init__.pyi +36 -0
  101. specmod-0.2.0/stubs/obspy/clients/__init__.pyi +1 -0
  102. specmod-0.2.0/stubs/obspy/clients/fdsn/__init__.pyi +29 -0
  103. specmod-0.2.0/stubs/obspy/core/__init__.pyi +3 -0
  104. specmod-0.2.0/stubs/obspy/core/event.pyi +56 -0
  105. specmod-0.2.0/stubs/obspy/core/inventory.pyi +13 -0
  106. specmod-0.2.0/stubs/obspy/core/stream.pyi +64 -0
  107. specmod-0.2.0/stubs/obspy/core/trace.pyi +78 -0
  108. specmod-0.2.0/stubs/obspy/core/utcdatetime.pyi +43 -0
  109. specmod-0.2.0/stubs/obspy/core/util/__init__.pyi +0 -0
  110. specmod-0.2.0/stubs/obspy/core/util/base.pyi +12 -0
  111. specmod-0.2.0/stubs/obspy/geodetics/__init__.pyi +14 -0
  112. specmod-0.2.0/stubs/obspy/signal/__init__.pyi +0 -0
  113. specmod-0.2.0/stubs/obspy/signal/konnoohmachismoothing.pyi +14 -0
  114. specmod-0.2.0/studies/magna_2020_paper.toml +136 -0
  115. specmod-0.2.0/tests/__init__.py +0 -0
  116. specmod-0.2.0/tests/conftest.py +191 -0
  117. specmod-0.2.0/tests/golden/motion_reference.json +3763 -0
  118. specmod-0.2.0/tests/golden/pipeline_reference.json +29839 -0
  119. specmod-0.2.0/tests/golden/window_reference.json +963 -0
  120. specmod-0.2.0/tests/test_acquire.py +375 -0
  121. specmod-0.2.0/tests/test_ambient_state.py +221 -0
  122. specmod-0.2.0/tests/test_api_surface.py +318 -0
  123. specmod-0.2.0/tests/test_collection.py +644 -0
  124. specmod-0.2.0/tests/test_config.py +273 -0
  125. specmod-0.2.0/tests/test_cwt.py +250 -0
  126. specmod-0.2.0/tests/test_datasets.py +178 -0
  127. specmod-0.2.0/tests/test_docs_are_current.py +80 -0
  128. specmod-0.2.0/tests/test_end_to_end.py +333 -0
  129. specmod-0.2.0/tests/test_fitting_defaults.py +372 -0
  130. specmod-0.2.0/tests/test_golden_reference.py +461 -0
  131. specmod-0.2.0/tests/test_import.py +100 -0
  132. specmod-0.2.0/tests/test_io_and_plotting.py +586 -0
  133. specmod-0.2.0/tests/test_legacy_fixes.py +192 -0
  134. specmod-0.2.0/tests/test_magnitude.py +278 -0
  135. specmod-0.2.0/tests/test_make_golden.py +127 -0
  136. specmod-0.2.0/tests/test_pick_plugins.py +413 -0
  137. specmod-0.2.0/tests/test_pick_readers.py +266 -0
  138. specmod-0.2.0/tests/test_picks.py +338 -0
  139. specmod-0.2.0/tests/test_pipeline.py +452 -0
  140. specmod-0.2.0/tests/test_pipeline_smoke.py +241 -0
  141. specmod-0.2.0/tests/test_preprocess.py +638 -0
  142. specmod-0.2.0/tests/test_prieto.py +210 -0
  143. specmod-0.2.0/tests/test_quadratic.py +359 -0
  144. specmod-0.2.0/tests/test_release_config.py +172 -0
  145. specmod-0.2.0/tests/test_smoothing.py +226 -0
  146. specmod-0.2.0/tests/test_sources.py +355 -0
  147. specmod-0.2.0/tests/test_spectral_wiring.py +270 -0
  148. specmod-0.2.0/tests/test_staged.py +407 -0
  149. specmod-0.2.0/tests/test_stubs.py +303 -0
  150. specmod-0.2.0/tests/test_transforms.py +872 -0
  151. specmod-0.2.0/tests/test_tutorial.py +154 -0
  152. specmod-0.2.0/tests/test_typing_backlog.py +129 -0
  153. specmod-0.2.0/tests/test_utils.py +345 -0
  154. specmod-0.2.0/tests/test_versioning.py +61 -0
  155. specmod-0.2.0/tools/check_built_version.py +84 -0
  156. specmod-0.2.0/tools/check_floors.py +139 -0
  157. specmod-0.2.0/tools/make_golden.py +295 -0
  158. specmod-0.2.0/tools/measure_docs.py +719 -0
  159. specmod-0.2.0/tutorial/SpecModTutorial.ipynb +2000 -0
  160. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/picks/2019-08-26T07:30:47.000000Z.picks +31 -0
  161. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/picks/2019-08-26T07:30:47.000000Z.xml +217 -0
  162. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/spectra/2019-08-26T07:30:47.000000Z.h5 +0 -0
  163. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/spectra/flatfiles/2019-08-26T07:30:47.000000Z.csv +29 -0
  164. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/spectra/flatfiles/2019-08-26T07:30:47.000000Z.parquet +0 -0
  165. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/stations/inventory.xml +7379 -0
  166. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L001..HHE_2019-08-26T07:30:47.000000Z +0 -0
  167. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L001..HHN_2019-08-26T07:30:47.000000Z +0 -0
  168. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L001..HHZ_2019-08-26T07:30:47.000000Z +0 -0
  169. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L002..HHE_2019-08-26T07:30:47.000000Z +0 -0
  170. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L002..HHN_2019-08-26T07:30:47.000000Z +0 -0
  171. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L002..HHZ_2019-08-26T07:30:47.000000Z +0 -0
  172. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L006..HHE_2019-08-26T07:30:47.000000Z +0 -0
  173. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L006..HHN_2019-08-26T07:30:47.000000Z +0 -0
  174. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L006..HHZ_2019-08-26T07:30:47.000000Z +0 -0
  175. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L007..HHE_2019-08-26T07:30:47.000000Z +0 -0
  176. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L007..HHN_2019-08-26T07:30:47.000000Z +0 -0
  177. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L007..HHZ_2019-08-26T07:30:47.000000Z +0 -0
  178. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L008..HHE_2019-08-26T07:30:47.000000Z +0 -0
  179. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L008..HHN_2019-08-26T07:30:47.000000Z +0 -0
  180. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L008..HHZ_2019-08-26T07:30:47.000000Z +0 -0
  181. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L009..HHE_2019-08-26T07:30:47.000000Z +0 -0
  182. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L009..HHN_2019-08-26T07:30:47.000000Z +0 -0
  183. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L009..HHZ_2019-08-26T07:30:47.000000Z +0 -0
  184. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.LD06..HH1_2019-08-26T07:30:47.000000Z +0 -0
  185. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.LD06..HH2_2019-08-26T07:30:47.000000Z +0 -0
  186. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.LD06..HH3_2019-08-26T07:30:47.000000Z +0 -0
  187. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ01.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
  188. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ01.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
  189. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ01.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
  190. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ02.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
  191. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ03.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
  192. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ03.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
  193. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ03.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
  194. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ04.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
  195. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ04.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
  196. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ04.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
  197. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ05.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
  198. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ05.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
  199. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ05.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
  200. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ06.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
  201. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ06.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
  202. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ06.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
  203. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ07.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
  204. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ07.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
  205. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ07.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
  206. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ09.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
  207. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ09.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
  208. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ09.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
  209. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ10.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
  210. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ10.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
  211. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ10.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
@@ -0,0 +1,6 @@
1
+ # Revisions to skip in `git blame` — formatting-only, no behaviour change.
2
+ # Enable locally with:
3
+ # git config blame.ignoreRevsFile .git-blame-ignore-revs
4
+
5
+ # style: apply ruff format and autofixes across the tree
6
+ b74ae4d632dffaafad9a440a624fd50f5c73274c
@@ -0,0 +1,40 @@
1
+ name: build
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ with:
15
+ fetch-depth: 0 # hatch-vcs derives the version from tags
16
+ - uses: astral-sh/setup-uv@v5
17
+ - run: uv python install 3.11
18
+
19
+ - name: Build sdist and wheel
20
+ run: uv build
21
+
22
+ - name: Check metadata
23
+ run: uvx twine check dist/*
24
+
25
+ - name: Install from the wheel in a clean env
26
+ # Catches missing package data and bad entry points, which an editable
27
+ # install will happily hide.
28
+ run: |
29
+ uv venv /tmp/smoke
30
+ uv pip install --python /tmp/smoke/bin/python dist/*.whl
31
+ /tmp/smoke/bin/python -c "
32
+ import specmod
33
+ from specmod import core, fitting, pipeline, preprocess, transforms, utils
34
+ print('specmod', specmod.__version__, '- wheel imports cleanly')
35
+ "
36
+
37
+ - uses: actions/upload-artifact@v4
38
+ with:
39
+ name: dist
40
+ path: dist/
@@ -0,0 +1,51 @@
1
+ name: docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ # Read the Docs publishes the site; this job only checks that it builds. It is
10
+ # kept alongside Read the Docs' own pull request build because it is the fast,
11
+ # in-repository check that does not depend on a third-party service being up —
12
+ # and because it is an import check, which is worth having in CI regardless.
13
+ # See docs/documentation.md.
14
+
15
+ concurrency:
16
+ group: docs-${{ github.ref }}
17
+ cancel-in-progress: true
18
+
19
+ jobs:
20
+ # The job name is what branch protection matches, not the workflow name.
21
+ # This was `build`, which collided with build.yml's job of the same name and
22
+ # left two unrelated checks sharing one name — and no check called `docs` for
23
+ # a required-checks list to name.
24
+ docs:
25
+ runs-on: ubuntu-latest
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+ with:
29
+ fetch-depth: 0 # hatch-vcs derives the version from tags
30
+ - uses: astral-sh/setup-uv@v5
31
+ - run: uv python install 3.11
32
+ - run: uv venv # create a venv to install into
33
+
34
+ # The package itself, not just the docs extra: autodoc imports every
35
+ # module it documents, so a docs build is also an import check.
36
+ - run: uv pip install -e ".[docs,io]"
37
+
38
+ - name: Build
39
+ # No -W. Intersphinx resolves seven inventories over the network and
40
+ # warns when one is briefly unreachable, which would turn a third
41
+ # party's downtime into a red build. A genuinely broken build exits
42
+ # non-zero on its own. `.readthedocs.yaml` sets fail_on_warning: false
43
+ # for the same reason — keep the two in step.
44
+ run: uv run sphinx-build -b html docs docs/_build/html
45
+
46
+ # Not a Pages artefact: this is a plain zip anyone can download from the
47
+ # run and open locally, and it stays useful if Read the Docs is down.
48
+ - uses: actions/upload-artifact@v4
49
+ with:
50
+ name: docs-html
51
+ path: docs/_build/html
@@ -0,0 +1,76 @@
1
+ name: release
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ workflow_dispatch:
7
+
8
+ # release-please pushes the release branch, opens the PR, and on merge creates
9
+ # the tag and the GitHub Release. The publish job below needs neither: PyPI
10
+ # Trusted Publishing authenticates with an OIDC token, so nothing long-lived
11
+ # lives in secrets.
12
+ permissions:
13
+ contents: write
14
+ pull-requests: write
15
+
16
+ # Two pushes to main should not race to update the same release PR.
17
+ concurrency:
18
+ group: release
19
+ cancel-in-progress: false
20
+
21
+ jobs:
22
+ release-please:
23
+ runs-on: ubuntu-latest
24
+ outputs:
25
+ release_created: ${{ steps.release.outputs.release_created }}
26
+ tag_name: ${{ steps.release.outputs.tag_name }}
27
+ steps:
28
+ - uses: googleapis/release-please-action@v4
29
+ id: release
30
+ with:
31
+ config-file: release-please-config.json
32
+ manifest-file: .release-please-manifest.json
33
+
34
+ # Deliberately in this workflow rather than a separate publish.yml keyed on
35
+ # `release: published`, which is what §6.5 of the plan used to describe.
36
+ # release-please creates the release with the default GITHUB_TOKEN, and
37
+ # per GitHub's docs "events triggered by the GITHUB_TOKEN will not create a
38
+ # new workflow run" — so that publish workflow would never fire. The
39
+ # alternative is a personal access token in secrets, which is the one thing
40
+ # Trusted Publishing exists to avoid. Gating on the action's own output
41
+ # keeps the token count at zero.
42
+ #
43
+ # Zenodo is unaffected: it listens to the release *webhook*, which is not
44
+ # subject to that restriction, so the DOI is still minted from the release.
45
+ publish:
46
+ needs: release-please
47
+ if: needs.release-please.outputs.release_created == 'true'
48
+ runs-on: ubuntu-latest
49
+ environment:
50
+ name: pypi
51
+ url: https://pypi.org/p/specmod
52
+ permissions:
53
+ id-token: write # mandatory for Trusted Publishing
54
+ contents: read
55
+ steps:
56
+ - uses: actions/checkout@v4
57
+ with:
58
+ # The tag, not the branch. main has already moved on by one commit
59
+ # (the release PR merge), and hatch-vcs would derive a .postN.devN
60
+ # version from it.
61
+ ref: ${{ needs.release-please.outputs.tag_name }}
62
+ fetch-depth: 0 # hatch-vcs derives the version by describing tags
63
+
64
+ - uses: astral-sh/setup-uv@v5
65
+ - run: uv python install 3.11
66
+ - run: uv build
67
+
68
+ # The tag is the version, and pyproject's tag_regex decides whether
69
+ # hatch-vcs can read it. If those two ever disagree the wheel is built
70
+ # as 0.1.1.postN.devN and would be uploaded under that name — PyPI does
71
+ # not let it be taken back. tests/test_release_config.py checks the
72
+ # formats agree; this checks the artefact that is about to be published.
73
+ - name: The built version is the tag
74
+ run: python tools/check_built_version.py "${{ needs.release-please.outputs.tag_name }}" dist
75
+
76
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,130 @@
1
+ name: test
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ concurrency:
10
+ group: test-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ lint:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: astral-sh/setup-uv@v5
19
+ with:
20
+ enable-cache: true
21
+ - run: uv python install 3.11
22
+ - run: uv venv
23
+ - name: ruff check
24
+ run: uvx ruff@0.16.1 check --output-format=github src/ tests/ tools/
25
+ - name: ruff format
26
+ run: uvx ruff@0.16.1 format --check src/ tests/ tools/
27
+
28
+ typecheck:
29
+ runs-on: ubuntu-latest
30
+ steps:
31
+ - uses: actions/checkout@v4
32
+ with:
33
+ fetch-depth: 0 # hatch-vcs needs history to derive the version
34
+ - uses: astral-sh/setup-uv@v5
35
+ with:
36
+ enable-cache: true
37
+ - run: uv python install 3.11
38
+ - run: uv venv
39
+ - run: uv pip install -e ".[dev]"
40
+ - run: uv pip install mypy
41
+ - run: uv run mypy
42
+
43
+ test:
44
+ runs-on: ${{ matrix.os }}
45
+ strategy:
46
+ fail-fast: false
47
+ matrix:
48
+ os: [ubuntu-latest, macos-latest]
49
+ python-version: ["3.11", "3.12", "3.13"]
50
+ steps:
51
+ - uses: actions/checkout@v4
52
+ with:
53
+ fetch-depth: 0
54
+ - uses: astral-sh/setup-uv@v5
55
+ with:
56
+ enable-cache: true
57
+ - run: uv python install ${{ matrix.python-version }}
58
+ - run: uv venv
59
+ - run: uv pip install -e ".[dev]"
60
+ - run: uv pip install pytest pytest-cov
61
+ - name: pytest
62
+ # Dataset-marked tests need a network fetch; they are excluded here so
63
+ # the suite stays hermetic. See docs/REFACTOR_PLAN.md §5.2. Notebook
64
+ # execution is excluded because it costs ~40s and a Jupyter kernel, and
65
+ # is worth paying once rather than six times — see the notebook job.
66
+ run: uv run pytest -m "not dataset and not notebook" --cov=specmod --cov-report=xml --cov-report=term
67
+ - uses: codecov/codecov-action@v4
68
+ if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'
69
+ with:
70
+ files: coverage.xml
71
+ fail_ci_if_error: false
72
+
73
+ floors:
74
+ runs-on: ubuntu-latest
75
+ steps:
76
+ - uses: actions/checkout@v4
77
+ with:
78
+ fetch-depth: 0
79
+ - uses: astral-sh/setup-uv@v5
80
+ - run: uv python install 3.11
81
+ - run: uv venv
82
+ # Exercise the declared minimums. CI otherwise installs the newest of
83
+ # everything, so the floors are only ever tested by a user.
84
+ - run: uv pip install --resolution lowest-direct -e ".[dev]"
85
+ - run: uv run --no-sync python tools/check_floors.py
86
+ - run: uv run --no-sync pytest -m "not dataset and not notebook" -q
87
+
88
+ notebook:
89
+ # The tutorial is the documented entry point and has broken three times:
90
+ # twice on renamed modules, once on a renamed data directory that left
91
+ # every import resolving. Executing it is the only check that catches the
92
+ # third kind, so it runs per-PR rather than at release.
93
+ runs-on: ubuntu-latest
94
+ steps:
95
+ - uses: actions/checkout@v4
96
+ with:
97
+ fetch-depth: 0
98
+ - uses: astral-sh/setup-uv@v5
99
+ with:
100
+ enable-cache: true
101
+ - run: uv python install 3.11
102
+ - run: uv venv
103
+ - run: uv pip install -e ".[dev,tutorial]"
104
+ - run: uv run pytest -m notebook -q
105
+
106
+ # One check to require in branch protection, instead of ten — and stable when
107
+ # the matrix changes, which the six `test (os, version)` names are not.
108
+ #
109
+ # `if: always()` is load-bearing. Without it a failing dependency *skips* this
110
+ # job rather than failing it, and GitHub treats a skipped required check as
111
+ # satisfied — so branch protection would go green on a red build. Running
112
+ # always, and failing explicitly on any non-success, is what closes that.
113
+ #
114
+ # `skipped` counts as a failure here on purpose: nothing above is
115
+ # conditionally skipped, so a skip means something upstream went wrong.
116
+ ci:
117
+ if: always()
118
+ needs: [lint, typecheck, test, floors, notebook]
119
+ runs-on: ubuntu-latest
120
+ steps:
121
+ - name: Report what every job did
122
+ run: echo '${{ toJSON(needs) }}'
123
+ - name: Fail unless all of them succeeded
124
+ if: >-
125
+ contains(needs.*.result, 'failure')
126
+ || contains(needs.*.result, 'cancelled')
127
+ || contains(needs.*.result, 'skipped')
128
+ run: |
129
+ echo "::error::a required job did not succeed; see the report above"
130
+ exit 1
@@ -0,0 +1,48 @@
1
+ # Local, uncommitted configuration overrides (see docs/REFACTOR_PLAN.md §4.7).
2
+ # Promote one deliberately with `specmod config freeze > studies/<name>.toml`.
3
+ specmod.local.toml
4
+ *.local.toml
5
+
6
+ # Python
7
+ __pycache__/
8
+ *.py[cod]
9
+ *.egg-info/
10
+ build/
11
+ dist/
12
+ .eggs/
13
+
14
+ # Environments
15
+ .venv/
16
+ venv/
17
+ env/
18
+
19
+ # Tooling caches
20
+ .pytest_cache/
21
+ .mypy_cache/
22
+ .ruff_cache/
23
+ .coverage
24
+ coverage.xml
25
+ htmlcov/
26
+
27
+ # Docs
28
+ docs/_build/
29
+
30
+ # Editors / OS
31
+ .ftpconfig
32
+ .DS_Store
33
+ .idea/
34
+ .vscode/
35
+ *.swp
36
+
37
+ # Notebook checkpoints
38
+ .ipynb_checkpoints/
39
+
40
+ # Tutorial output. Regenerated by running the notebook; committing it means a
41
+ # diff every time anyone executes it, and a stale copy the moment the pipeline
42
+ # changes. The inputs (Tutorial/Data, Tutorial/MetaData) *are* committed.
43
+ Tutorial/Spectra/*.h5
44
+ Tutorial/Spectra/FlatFiles/
45
+
46
+ # Created by `uv run` without --no-sync; this project resolves fresh on
47
+ # purpose so the floors job can test the declared minimums.
48
+ uv.lock
@@ -0,0 +1,62 @@
1
+ # `pre-commit install` wires only the pre-commit stage by default, which left
2
+ # the commit-msg hook below inert in every clone that ran the documented setup
3
+ # — including the one that then committed three session links. Naming the
4
+ # stages here means one `pre-commit install` installs both.
5
+ default_install_hook_types: [pre-commit, commit-msg]
6
+
7
+ repos:
8
+ # CI runs `uvx ruff` unpinned, so it always gets the newest release. Keep
9
+ # this rev at that newest release or CI will fail on rules this does not have.
10
+ # It has bitten twice: 0.9.6 against latest, then 0.15.8 against 0.16.1, where
11
+ # PLR0917 graduated out of preview and started firing only in CI.
12
+ - repo: https://github.com/astral-sh/ruff-pre-commit
13
+ rev: v0.16.1
14
+ hooks:
15
+ # Scoped to match CI, which runs `ruff ... src/ tests/ tools/`. Without
16
+ # this the hook lints everything staged and CI lints three directories,
17
+ # so a commit touching `docs/notebooks/` or `stubs/` is blocked by rules
18
+ # nothing else enforces — and the notebook builder legitimately contains
19
+ # long markdown lines and mathematical unicode that `RUF001` reads as
20
+ # ambiguous. Widen both together or neither.
21
+ - id: ruff-check
22
+ args: [--fix]
23
+ files: ^(src|tests|tools)/
24
+ - id: ruff-format
25
+ files: ^(src|tests|tools)/
26
+
27
+ - repo: https://github.com/pre-commit/pre-commit-hooks
28
+ rev: v5.0.0
29
+ hooks:
30
+ - id: check-added-large-files
31
+ # The repo carried 25 MB of data before this refactor; keep it out.
32
+ args: [--maxkb=1024]
33
+ - id: check-toml
34
+ - id: check-yaml
35
+ # These three rewrite files as text. `.spec` artifacts are pickles, and
36
+ # running the hooks over one silently stripped six bytes from the middle
37
+ # of it — a corrupted pickle that still looks like a plausible diff.
38
+ # Anything binary has to be excluded by hand; the hooks do not detect it.
39
+ - id: end-of-file-fixer
40
+ exclude: '\.spec$'
41
+ - id: trailing-whitespace
42
+ exclude: '\.spec$'
43
+ - id: mixed-line-ending
44
+ exclude: '\.spec$'
45
+
46
+ - repo: https://github.com/kynan/nbstripout
47
+ rev: 0.8.1
48
+ hooks:
49
+ - id: nbstripout
50
+ # The tutorial notebook was 4.4 MB of embedded PNGs. Under myst-nb the
51
+ # outputs are regenerated at docs build time.
52
+
53
+ - repo: local
54
+ hooks:
55
+ - id: no-session-links
56
+ name: reject Claude session URLs in commit messages
57
+ # This repository is public; session links are private state.
58
+ entry: >-
59
+ bash -c 'if grep -qiE "^Claude-Session:|claude\.ai/code/session_" "$1";
60
+ then echo "commit-msg: message contains a Claude session link"; exit 1; fi'
61
+ language: system
62
+ stages: [commit-msg]
@@ -0,0 +1,47 @@
1
+ # Read the Docs build configuration.
2
+ #
3
+ # Read the Docs publishes the documentation; GitHub Actions only checks that it
4
+ # builds. The reason for the split is versions: this package is alpha and users
5
+ # are told to pin an exact version, so the documentation for a release has to
6
+ # stay readable after the trunk has moved on. Read the Docs keeps one build per
7
+ # tag, a `stable` pointing at the newest, and a `latest` from `main`, with a
8
+ # version switcher between them — and it builds pull requests to their own
9
+ # throwaway URL.
10
+ #
11
+ # Schema: https://docs.readthedocs.com/platform/stable/config-file/v2.html
12
+
13
+ version: 2
14
+
15
+ build:
16
+ os: ubuntu-24.04
17
+ tools:
18
+ python: "3.11" # the version CI's lint, typecheck and docs jobs use
19
+ jobs:
20
+ post_checkout:
21
+ # Read the Docs clones shallow and without tags, to save time. hatch-vcs
22
+ # derives the version from `git describe`, and `docs/conf.py` reads it
23
+ # back through importlib.metadata — so without these the sidebar reads
24
+ # the fallback 0.0.0 on every build, including tagged ones.
25
+ # `|| true` because a repository that is already complete makes
26
+ # --unshallow exit non-zero, which would fail the build.
27
+ - git fetch --unshallow || true
28
+ - git fetch --tags || true
29
+
30
+ sphinx:
31
+ configuration: docs/conf.py
32
+ # Deliberately off, for the same reason the CI job has no `-W`: intersphinx
33
+ # resolves seven inventories over the network and warns whenever one of them
34
+ # is briefly unreachable. That would make a third party's downtime a failed
35
+ # documentation build. A genuinely broken build still exits non-zero.
36
+ fail_on_warning: false
37
+
38
+ python:
39
+ install:
40
+ - method: pip
41
+ path: .
42
+ extra_requirements:
43
+ # `io` alongside `docs` because autodoc imports every module it
44
+ # documents, and `specmod.io` imports h5py and pyarrow. Without it the
45
+ # API reference loses those pages to import errors.
46
+ - docs
47
+ - io
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.2.0"
3
+ }
@@ -0,0 +1,92 @@
1
+ # Working on SpecMod with a coding agent
2
+
3
+ Rules for Claude Code, Codex, and any other agent committing to this
4
+ repository. `CLAUDE.md` points here so there is one copy.
5
+
6
+ The long version of everything below is
7
+ [`docs/development.md`](docs/development.md). This file is the part an agent
8
+ must not get wrong, and every entry is here because it has actually gone wrong.
9
+
10
+ ## Before the first commit
11
+
12
+ ```sh
13
+ uv venv && uv pip install -e ".[dev]"
14
+ pre-commit install # installs BOTH the pre-commit and commit-msg hooks
15
+ ```
16
+
17
+ `pre-commit install` is not optional. In a fresh container it is easy to skip,
18
+ and the `commit-msg` hook is the only thing enforcing the rule below.
19
+
20
+ ## Never publish session links
21
+
22
+ **No `Claude-Session:` trailers, session URLs, or agent-console links** in
23
+ commit messages, PR titles, PR bodies, code comments, or anything else that
24
+ lands in the repository. It is public; those links are private state.
25
+
26
+ `Co-Authored-By:` is fine. If your harness appends a session trailer by
27
+ default, strip it — the repository's rule wins over the harness default. The
28
+ `commit-msg` hook rejects it, which is why installing the hooks comes first.
29
+
30
+ ## Commit messages are load-bearing
31
+
32
+ [Conventional Commits](https://www.conventionalcommits.org). They are not a
33
+ style preference: `release-please` reads them to compute the version bump and
34
+ to generate `CHANGELOG.md`. See
35
+ [`docs/releasing.md`](docs/releasing.md).
36
+
37
+ - `feat:` minor, `fix:` patch, `refactor:` / `docs:` / `build:` appear in the
38
+ changelog, `test:` / `ci:` / `chore:` are hidden.
39
+ - `!` or a `BREAKING CHANGE:` footer bumps the minor while the project is
40
+ `0.x`, not the major.
41
+ - Say *why*, with the measurement if there was one. The history is the record
42
+ of what was checked; a message that only restates the diff wastes it.
43
+
44
+ ## Workflow files need a permission you may not have
45
+
46
+ `.github/workflows/` is editable directly, but only when the session's GitHub
47
+ App token carries the `workflows` permission. Without it the push is rejected
48
+ outright:
49
+
50
+ ```
51
+ refusing to allow a GitHub App to create or update workflow
52
+ `.github/workflows/test.yml` without `workflows` permission
53
+ ```
54
+
55
+ That is a loud failure, not a silent one. If you meet it, say so and ask for
56
+ the permission — do not reintroduce a parallel copy of the workflows to work
57
+ around it. There used to be one, in `ci/`, and keeping two versions of every
58
+ workflow in step cost more than the problem it solved.
59
+
60
+ ## Verify before reporting
61
+
62
+ Run these, and report what they actually printed:
63
+
64
+ ```sh
65
+ pytest -m "not dataset and not notebook" # the suite CI runs
66
+ pytest --without-optional-extras # what a default install sees
67
+ ruff check src/ tests/ tools/ && ruff format --check src/ tests/ tools/
68
+ mypy
69
+ sphinx-build -b html docs docs/_build/html # if docs/ changed
70
+ ```
71
+
72
+ `--without-optional-extras` matters: a development environment with
73
+ `specmod[multitaper]` installed passes tests that CI fails.
74
+
75
+ ## Things that look like noise and are not
76
+
77
+ - **Golden references.** `tests/golden/*.json` is a record of numbers this code
78
+ used to produce. Do not regenerate it to make a test pass. If a change moves
79
+ a number, that is the finding — say which number, by how much, and why, and
80
+ regenerate deliberately with `python tools/make_golden.py`.
81
+ - **Measured tables in the docs.** Numbers in `docs/*.md` are generated between
82
+ markers by `python tools/measure_docs.py`. Edit the tool, not the table.
83
+ - **Tolerances.** Several carry a comment explaining what was measured to
84
+ choose them. Widening one to get to green, without measuring, is the specific
85
+ failure `docs/REFACTOR_PLAN.md` §6.6 exists to catch.
86
+
87
+ ## Say what you did not check
88
+
89
+ The plan's §6.6 is an audit of claims in this repository that turned out to
90
+ describe mechanisms nobody had built. Do not add to it. If something is
91
+ untested, unreproducible, or assumed, write that down next to the claim — a
92
+ bound with a number behind it beats a confident sentence.