py-lucidum 0.4.59__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 (225) hide show
  1. py_lucidum-0.4.59/.githooks/pre-commit +14 -0
  2. py_lucidum-0.4.59/.githooks/pre-push +14 -0
  3. py_lucidum-0.4.59/.github/dependabot.yml +8 -0
  4. py_lucidum-0.4.59/.github/workflows/ci.yml +45 -0
  5. py_lucidum-0.4.59/.github/workflows/release.yml +327 -0
  6. py_lucidum-0.4.59/.gitignore +27 -0
  7. py_lucidum-0.4.59/AGENTS.md +115 -0
  8. py_lucidum-0.4.59/DEVELOPMENT.md +612 -0
  9. py_lucidum-0.4.59/LICENSE +21 -0
  10. py_lucidum-0.4.59/PKG-INFO +519 -0
  11. py_lucidum-0.4.59/README.md +490 -0
  12. py_lucidum-0.4.59/REFACTOR.md +118 -0
  13. py_lucidum-0.4.59/datasets/monthly/2016-09.parquet +0 -0
  14. py_lucidum-0.4.59/datasets/monthly/2016-10.parquet +0 -0
  15. py_lucidum-0.4.59/datasets/monthly/2016-11.parquet +0 -0
  16. py_lucidum-0.4.59/datasets/monthly/2016-12.parquet +0 -0
  17. py_lucidum-0.4.59/datasets/monthly/2017-01.parquet +0 -0
  18. py_lucidum-0.4.59/datasets/monthly/2017-02.parquet +0 -0
  19. py_lucidum-0.4.59/datasets/monthly/2017-03.parquet +0 -0
  20. py_lucidum-0.4.59/datasets/motor_premiums.parquet +0 -0
  21. py_lucidum-0.4.59/docs/assets/line_and_bar.png +0 -0
  22. py_lucidum-0.4.59/docs/assets/postcode_area.png +0 -0
  23. py_lucidum-0.4.59/docs/assets/postcode_sector.png +0 -0
  24. py_lucidum-0.4.59/docs/assets/postcode_unit.png +0 -0
  25. py_lucidum-0.4.59/docs/specs/gbm-tool.md +140 -0
  26. py_lucidum-0.4.59/docs/specs/gbm-tool_plan.md +115 -0
  27. py_lucidum-0.4.59/docs/specs/glm-tabulations.md +106 -0
  28. py_lucidum-0.4.59/docs/telemetry-monitor.md +80 -0
  29. py_lucidum-0.4.59/favicon.ico +0 -0
  30. py_lucidum-0.4.59/pyproject.toml +53 -0
  31. py_lucidum-0.4.59/scripts/README.md +116 -0
  32. py_lucidum-0.4.59/scripts/build_uk_sector_adjacency.py +98 -0
  33. py_lucidum-0.4.59/scripts/bump_version.py +108 -0
  34. py_lucidum-0.4.59/scripts/release_artifacts.py +167 -0
  35. py_lucidum-0.4.59/scripts/run_browser_smoke.py +148 -0
  36. py_lucidum-0.4.59/scripts/run_tests.py +416 -0
  37. py_lucidum-0.4.59/scripts/uk_map/convert_gpkg_to_geojson.py +352 -0
  38. py_lucidum-0.4.59/specs/feature_spec.csv +20 -0
  39. py_lucidum-0.4.59/specs/filter_spec.csv +25 -0
  40. py_lucidum-0.4.59/specs/home_filter_spec.csv +93 -0
  41. py_lucidum-0.4.59/specs/kpi_spec.csv +10 -0
  42. py_lucidum-0.4.59/src/py_lucidum/__init__.py +15 -0
  43. py_lucidum-0.4.59/src/py_lucidum/__main__.py +4 -0
  44. py_lucidum-0.4.59/src/py_lucidum/_version.py +31 -0
  45. py_lucidum-0.4.59/src/py_lucidum/app/__init__.py +7 -0
  46. py_lucidum-0.4.59/src/py_lucidum/app/assets.py +30 -0
  47. py_lucidum-0.4.59/src/py_lucidum/app/context.py +14 -0
  48. py_lucidum-0.4.59/src/py_lucidum/app/factory.py +539 -0
  49. py_lucidum-0.4.59/src/py_lucidum/app/servers.py +265 -0
  50. py_lucidum-0.4.59/src/py_lucidum/app/telemetry.py +1227 -0
  51. py_lucidum-0.4.59/src/py_lucidum/cli.py +551 -0
  52. py_lucidum-0.4.59/src/py_lucidum/core/__init__.py +84 -0
  53. py_lucidum-0.4.59/src/py_lucidum/core/dataset.py +711 -0
  54. py_lucidum-0.4.59/src/py_lucidum/core/features.py +81 -0
  55. py_lucidum-0.4.59/src/py_lucidum/core/filters.py +37 -0
  56. py_lucidum-0.4.59/src/py_lucidum/core/kpis.py +78 -0
  57. py_lucidum-0.4.59/src/py_lucidum/core/metric_sources.py +220 -0
  58. py_lucidum-0.4.59/src/py_lucidum/core/metrics.py +285 -0
  59. py_lucidum-0.4.59/src/py_lucidum/core/schema.py +91 -0
  60. py_lucidum-0.4.59/src/py_lucidum/core/sql.py +9 -0
  61. py_lucidum-0.4.59/src/py_lucidum/core/workspace.py +79 -0
  62. py_lucidum-0.4.59/src/py_lucidum/demo.py +79 -0
  63. py_lucidum-0.4.59/src/py_lucidum/query.py +38 -0
  64. py_lucidum-0.4.59/src/py_lucidum/static/app/column-profile-tool.js +1152 -0
  65. py_lucidum-0.4.59/src/py_lucidum/static/app/dataset-viewer-tool.js +2277 -0
  66. py_lucidum-0.4.59/src/py_lucidum/static/app/gbm-evaluation-chart.js +502 -0
  67. py_lucidum-0.4.59/src/py_lucidum/static/app/gbm-feature-parameter-controls.js +471 -0
  68. py_lucidum-0.4.59/src/py_lucidum/static/app/gbm-model-navigator.js +188 -0
  69. py_lucidum-0.4.59/src/py_lucidum/static/app/gbm-shap-chart.js +772 -0
  70. py_lucidum-0.4.59/src/py_lucidum/static/app/gbm-shap-tool.js +1322 -0
  71. py_lucidum-0.4.59/src/py_lucidum/static/app/gbm-stacked-shap-chart.js +247 -0
  72. py_lucidum-0.4.59/src/py_lucidum/static/app/gbm-stacked-shap-tool.js +819 -0
  73. py_lucidum-0.4.59/src/py_lucidum/static/app/gbm-tab-orchestration.js +40 -0
  74. py_lucidum-0.4.59/src/py_lucidum/static/app/gbm-tool.js +3912 -0
  75. py_lucidum-0.4.59/src/py_lucidum/static/app/gbm-tree-viewer.js +1090 -0
  76. py_lucidum-0.4.59/src/py_lucidum/static/app/glm-formula-assist.js +278 -0
  77. py_lucidum-0.4.59/src/py_lucidum/static/app/glm-formula-builder.js +1239 -0
  78. py_lucidum-0.4.59/src/py_lucidum/static/app/glm-model-navigator.js +148 -0
  79. py_lucidum-0.4.59/src/py_lucidum/static/app/glm-tabulations.js +293 -0
  80. py_lucidum-0.4.59/src/py_lucidum/static/app/glm-tool.js +3230 -0
  81. py_lucidum-0.4.59/src/py_lucidum/static/app/histogram-tool.js +1090 -0
  82. py_lucidum-0.4.59/src/py_lucidum/static/app/line-bar-tool.js +5056 -0
  83. py_lucidum-0.4.59/src/py_lucidum/static/app/line-bar-two-feature-chart.js +1074 -0
  84. py_lucidum-0.4.59/src/py_lucidum/static/app/main.js +5171 -0
  85. py_lucidum-0.4.59/src/py_lucidum/static/app/model-tool-shell.js +120 -0
  86. py_lucidum-0.4.59/src/py_lucidum/static/app/shared/api.js +56 -0
  87. py_lucidum-0.4.59/src/py_lucidum/static/app/shared/echarts-gl.js +28 -0
  88. py_lucidum-0.4.59/src/py_lucidum/static/app/shared/filter-expression.js +170 -0
  89. py_lucidum-0.4.59/src/py_lucidum/static/app/shared/format.js +132 -0
  90. py_lucidum-0.4.59/src/py_lucidum/static/app/shared/list-navigation.js +107 -0
  91. py_lucidum-0.4.59/src/py_lucidum/static/app/shared/model-ui.js +232 -0
  92. py_lucidum-0.4.59/src/py_lucidum/static/app/shared/schema.js +42 -0
  93. py_lucidum-0.4.59/src/py_lucidum/static/app/shared/settings-strip.js +80 -0
  94. py_lucidum-0.4.59/src/py_lucidum/static/app/shared/tabulator.js +41 -0
  95. py_lucidum-0.4.59/src/py_lucidum/static/app/shared/timing.js +222 -0
  96. py_lucidum-0.4.59/src/py_lucidum/static/app/shared/tool-screen-nav.js +94 -0
  97. py_lucidum-0.4.59/src/py_lucidum/static/app/specifications-tool.js +1422 -0
  98. py_lucidum-0.4.59/src/py_lucidum/static/app/uk-map-tool.js +2807 -0
  99. py_lucidum-0.4.59/src/py_lucidum/static/app.css +11 -0
  100. py_lucidum-0.4.59/src/py_lucidum/static/app.js +3 -0
  101. py_lucidum-0.4.59/src/py_lucidum/static/index.html +679 -0
  102. py_lucidum-0.4.59/src/py_lucidum/static/monitor.css +829 -0
  103. py_lucidum-0.4.59/src/py_lucidum/static/monitor.html +270 -0
  104. py_lucidum-0.4.59/src/py_lucidum/static/monitor.js +765 -0
  105. py_lucidum-0.4.59/src/py_lucidum/static/styles/column-profile.css +890 -0
  106. py_lucidum-0.4.59/src/py_lucidum/static/styles/controls.css +1846 -0
  107. py_lucidum-0.4.59/src/py_lucidum/static/styles/dataset-viewer.css +660 -0
  108. py_lucidum-0.4.59/src/py_lucidum/static/styles/foundations.css +163 -0
  109. py_lucidum-0.4.59/src/py_lucidum/static/styles/gbm.css +2852 -0
  110. py_lucidum-0.4.59/src/py_lucidum/static/styles/glm.css +1811 -0
  111. py_lucidum-0.4.59/src/py_lucidum/static/styles/histogram.css +350 -0
  112. py_lucidum-0.4.59/src/py_lucidum/static/styles/line-bar.css +1068 -0
  113. py_lucidum-0.4.59/src/py_lucidum/static/styles/model-shell.css +79 -0
  114. py_lucidum-0.4.59/src/py_lucidum/static/styles/shell.css +634 -0
  115. py_lucidum-0.4.59/src/py_lucidum/static/styles/specifications.css +314 -0
  116. py_lucidum-0.4.59/src/py_lucidum/static/styles/uk-map.css +1105 -0
  117. py_lucidum-0.4.59/src/py_lucidum/static/vendor/ace/LICENSE +24 -0
  118. py_lucidum-0.4.59/src/py_lucidum/static/vendor/ace/ace.js +22 -0
  119. py_lucidum-0.4.59/src/py_lucidum/static/vendor/ace/mode-r.js +7 -0
  120. py_lucidum-0.4.59/src/py_lucidum/static/vendor/ace/theme-monokai.js +7 -0
  121. py_lucidum-0.4.59/src/py_lucidum/static/vendor/ace/theme-textmate.js +7 -0
  122. py_lucidum-0.4.59/src/py_lucidum/static/vendor/d3/LICENSE +13 -0
  123. py_lucidum-0.4.59/src/py_lucidum/static/vendor/d3/d3.min.js +2 -0
  124. py_lucidum-0.4.59/src/py_lucidum/static/vendor/echarts/LICENSE +222 -0
  125. py_lucidum-0.4.59/src/py_lucidum/static/vendor/echarts/echarts.min.js +45 -0
  126. py_lucidum-0.4.59/src/py_lucidum/static/vendor/echarts-gl/LICENSE +29 -0
  127. py_lucidum-0.4.59/src/py_lucidum/static/vendor/echarts-gl/echarts-gl.min.js +1 -0
  128. py_lucidum-0.4.59/src/py_lucidum/static/vendor/leaflet/LICENSE +26 -0
  129. py_lucidum-0.4.59/src/py_lucidum/static/vendor/leaflet/images/layers-2x.png +0 -0
  130. py_lucidum-0.4.59/src/py_lucidum/static/vendor/leaflet/images/layers.png +0 -0
  131. py_lucidum-0.4.59/src/py_lucidum/static/vendor/leaflet/images/marker-icon-2x.png +0 -0
  132. py_lucidum-0.4.59/src/py_lucidum/static/vendor/leaflet/images/marker-icon.png +0 -0
  133. py_lucidum-0.4.59/src/py_lucidum/static/vendor/leaflet/images/marker-shadow.png +0 -0
  134. py_lucidum-0.4.59/src/py_lucidum/static/vendor/leaflet/leaflet.css +661 -0
  135. py_lucidum-0.4.59/src/py_lucidum/static/vendor/leaflet/leaflet.js +6 -0
  136. py_lucidum-0.4.59/src/py_lucidum/static/vendor/tabulator/LICENSE +21 -0
  137. py_lucidum-0.4.59/src/py_lucidum/static/vendor/tabulator/tabulator.min.css +2 -0
  138. py_lucidum-0.4.59/src/py_lucidum/static/vendor/tabulator/tabulator.min.js +3 -0
  139. py_lucidum-0.4.59/src/py_lucidum/tools/__init__.py +3 -0
  140. py_lucidum-0.4.59/src/py_lucidum/tools/column_profile/__init__.py +25 -0
  141. py_lucidum-0.4.59/src/py_lucidum/tools/column_profile/query.py +652 -0
  142. py_lucidum-0.4.59/src/py_lucidum/tools/column_profile/routes.py +44 -0
  143. py_lucidum-0.4.59/src/py_lucidum/tools/dataset_viewer/__init__.py +24 -0
  144. py_lucidum-0.4.59/src/py_lucidum/tools/dataset_viewer/query.py +109 -0
  145. py_lucidum-0.4.59/src/py_lucidum/tools/dataset_viewer/routes.py +32 -0
  146. py_lucidum-0.4.59/src/py_lucidum/tools/gbm/__init__.py +24 -0
  147. py_lucidum-0.4.59/src/py_lucidum/tools/gbm/config.py +480 -0
  148. py_lucidum-0.4.59/src/py_lucidum/tools/gbm/grid.py +364 -0
  149. py_lucidum-0.4.59/src/py_lucidum/tools/gbm/interaction_group_model.py +641 -0
  150. py_lucidum-0.4.59/src/py_lucidum/tools/gbm/jobs.py +329 -0
  151. py_lucidum-0.4.59/src/py_lucidum/tools/gbm/routes.py +225 -0
  152. py_lucidum-0.4.59/src/py_lucidum/tools/gbm/sample.py +189 -0
  153. py_lucidum-0.4.59/src/py_lucidum/tools/gbm/shap.py +1197 -0
  154. py_lucidum-0.4.59/src/py_lucidum/tools/gbm/sources.py +6 -0
  155. py_lucidum-0.4.59/src/py_lucidum/tools/gbm/store.py +1115 -0
  156. py_lucidum-0.4.59/src/py_lucidum/tools/gbm/tabulation.py +675 -0
  157. py_lucidum-0.4.59/src/py_lucidum/tools/gbm/training.py +1832 -0
  158. py_lucidum-0.4.59/src/py_lucidum/tools/gbm/trees.py +603 -0
  159. py_lucidum-0.4.59/src/py_lucidum/tools/gbm/validation.py +1142 -0
  160. py_lucidum-0.4.59/src/py_lucidum/tools/glm/__init__.py +15 -0
  161. py_lucidum-0.4.59/src/py_lucidum/tools/glm/formula_assist.py +92 -0
  162. py_lucidum-0.4.59/src/py_lucidum/tools/glm/jobs.py +182 -0
  163. py_lucidum-0.4.59/src/py_lucidum/tools/glm/overlay.py +1758 -0
  164. py_lucidum-0.4.59/src/py_lucidum/tools/glm/overlay_worker.py +120 -0
  165. py_lucidum-0.4.59/src/py_lucidum/tools/glm/routes.py +257 -0
  166. py_lucidum-0.4.59/src/py_lucidum/tools/glm/store.py +612 -0
  167. py_lucidum-0.4.59/src/py_lucidum/tools/glm/tabulation.py +2416 -0
  168. py_lucidum-0.4.59/src/py_lucidum/tools/glm/tabulation_worker.py +70 -0
  169. py_lucidum-0.4.59/src/py_lucidum/tools/glm/terms.py +49 -0
  170. py_lucidum-0.4.59/src/py_lucidum/tools/glm/training.py +1730 -0
  171. py_lucidum-0.4.59/src/py_lucidum/tools/glm/validation.py +821 -0
  172. py_lucidum-0.4.59/src/py_lucidum/tools/glm/worker.py +124 -0
  173. py_lucidum-0.4.59/src/py_lucidum/tools/histogram/__init__.py +24 -0
  174. py_lucidum-0.4.59/src/py_lucidum/tools/histogram/query.py +937 -0
  175. py_lucidum-0.4.59/src/py_lucidum/tools/histogram/routes.py +28 -0
  176. py_lucidum-0.4.59/src/py_lucidum/tools/line_bar/__init__.py +25 -0
  177. py_lucidum-0.4.59/src/py_lucidum/tools/line_bar/favourites.py +550 -0
  178. py_lucidum-0.4.59/src/py_lucidum/tools/line_bar/importance.py +217 -0
  179. py_lucidum-0.4.59/src/py_lucidum/tools/line_bar/model_ratio.py +194 -0
  180. py_lucidum-0.4.59/src/py_lucidum/tools/line_bar/query.py +3296 -0
  181. py_lucidum-0.4.59/src/py_lucidum/tools/line_bar/routes.py +161 -0
  182. py_lucidum-0.4.59/src/py_lucidum/tools/line_bar/two_feature.py +1016 -0
  183. py_lucidum-0.4.59/src/py_lucidum/tools/registry.py +144 -0
  184. py_lucidum-0.4.59/src/py_lucidum/tools/specifications/__init__.py +15 -0
  185. py_lucidum-0.4.59/src/py_lucidum/tools/specifications/routes.py +49 -0
  186. py_lucidum-0.4.59/src/py_lucidum/tools/specifications/service.py +487 -0
  187. py_lucidum-0.4.59/src/py_lucidum/tools/uk_map/__init__.py +24 -0
  188. py_lucidum-0.4.59/src/py_lucidum/tools/uk_map/query.py +661 -0
  189. py_lucidum-0.4.59/src/py_lucidum/tools/uk_map/routes.py +82 -0
  190. py_lucidum-0.4.59/src/py_lucidum/tools/uk_map/smoothing.py +371 -0
  191. py_lucidum-0.4.59/src/py_lucidum/tools/uk_map/static/.gitkeep +1 -0
  192. py_lucidum-0.4.59/src/py_lucidum/tools/uk_map/static/geodata/areas_MappaR.geojson +1 -0
  193. py_lucidum-0.4.59/src/py_lucidum/tools/uk_map/static/geodata/sector_adjacency.json +1 -0
  194. py_lucidum-0.4.59/src/py_lucidum/tools/uk_map/static/geodata/sector_smoothing_pools.parquet +0 -0
  195. py_lucidum-0.4.59/src/py_lucidum/tools/uk_map/static/geodata/sectors_MappaR.geojson +1 -0
  196. py_lucidum-0.4.59/src/py_lucidum/tools/uk_map/static/icons/London.png +0 -0
  197. py_lucidum-0.4.59/src/py_lucidum/tools/uk_map/static/icons/UK.png +0 -0
  198. py_lucidum-0.4.59/src/py_lucidum/tools/uk_map/static/icons/map-controls/base-aerial.png +0 -0
  199. py_lucidum-0.4.59/src/py_lucidum/tools/uk_map/static/icons/map-controls/base-blank.png +0 -0
  200. py_lucidum-0.4.59/src/py_lucidum/tools/uk_map/static/icons/map-controls/base-dark.png +0 -0
  201. py_lucidum-0.4.59/src/py_lucidum/tools/uk_map/static/icons/map-controls/base-esri.png +0 -0
  202. py_lucidum-0.4.59/src/py_lucidum/tools/uk_map/static/icons/map-controls/base-light.png +0 -0
  203. py_lucidum-0.4.59/src/py_lucidum/tools/uk_map/static/icons/map-controls/base-osm.png +0 -0
  204. py_lucidum-0.4.59/src/py_lucidum/tools/uk_map/static/icons/map-controls/level-area.png +0 -0
  205. py_lucidum-0.4.59/src/py_lucidum/tools/uk_map/static/icons/map-controls/level-sector.png +0 -0
  206. py_lucidum-0.4.59/src/py_lucidum/tools/uk_map/static/icons/map-controls/level-unit.png +0 -0
  207. py_lucidum-0.4.59/tests/test_browser_smoke.py +33793 -0
  208. py_lucidum-0.4.59/tests/test_cli.py +960 -0
  209. py_lucidum-0.4.59/tests/test_column_profile.py +496 -0
  210. py_lucidum-0.4.59/tests/test_dataset_viewer.py +234 -0
  211. py_lucidum-0.4.59/tests/test_demo_dataset.py +151 -0
  212. py_lucidum-0.4.59/tests/test_features.py +176 -0
  213. py_lucidum-0.4.59/tests/test_gbm.py +5292 -0
  214. py_lucidum-0.4.59/tests/test_glm.py +3863 -0
  215. py_lucidum-0.4.59/tests/test_histogram.py +406 -0
  216. py_lucidum-0.4.59/tests/test_line_bar.py +5096 -0
  217. py_lucidum-0.4.59/tests/test_model_denominators.py +446 -0
  218. py_lucidum-0.4.59/tests/test_parquet_folder.py +337 -0
  219. py_lucidum-0.4.59/tests/test_pipx_install.py +169 -0
  220. py_lucidum-0.4.59/tests/test_release_artifacts.py +111 -0
  221. py_lucidum-0.4.59/tests/test_specifications.py +504 -0
  222. py_lucidum-0.4.59/tests/test_static_assets.py +2814 -0
  223. py_lucidum-0.4.59/tests/test_telemetry.py +591 -0
  224. py_lucidum-0.4.59/tests/test_test_runner.py +292 -0
  225. py_lucidum-0.4.59/tests/test_uk_map.py +563 -0
@@ -0,0 +1,14 @@
1
+ #!/bin/sh
2
+
3
+ set -eu
4
+
5
+ repo_root=$(git rev-parse --show-toplevel)
6
+ python=${PY_LUCIDUM_TEST_PYTHON:-"$repo_root/.venv/bin/python"}
7
+
8
+ if ! command -v "$python" >/dev/null 2>&1; then
9
+ echo "Lucidum pre-commit: Python is not executable: $python" >&2
10
+ echo "Set PY_LUCIDUM_TEST_PYTHON to the test-environment Python path." >&2
11
+ exit 1
12
+ fi
13
+
14
+ exec "$python" "$repo_root/scripts/run_tests.py" precommit
@@ -0,0 +1,14 @@
1
+ #!/bin/sh
2
+
3
+ set -eu
4
+
5
+ repo_root=$(git rev-parse --show-toplevel)
6
+ python=${PY_LUCIDUM_TEST_PYTHON:-"$repo_root/.venv/bin/python"}
7
+
8
+ if ! command -v "$python" >/dev/null 2>&1; then
9
+ echo "Lucidum pre-push: Python is not executable: $python" >&2
10
+ echo "Set PY_LUCIDUM_TEST_PYTHON to the test-environment Python path." >&2
11
+ exit 1
12
+ fi
13
+
14
+ exec "$python" "$repo_root/scripts/run_tests.py" prepush
@@ -0,0 +1,8 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: github-actions
4
+ directory: "/"
5
+ schedule:
6
+ interval: weekly
7
+ labels:
8
+ - dependencies
@@ -0,0 +1,45 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+ branches:
9
+ - main
10
+ workflow_dispatch:
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ concurrency:
16
+ group: ci-${{ github.workflow }}-${{ github.ref }}
17
+ cancel-in-progress: true
18
+
19
+ jobs:
20
+ full-test-suite:
21
+ name: Full test suite
22
+ runs-on: ubuntu-latest
23
+ timeout-minutes: 30
24
+
25
+ steps:
26
+ - name: Check out repository
27
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
28
+ with:
29
+ persist-credentials: false
30
+
31
+ - name: Set up Python
32
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
33
+ with:
34
+ python-version: "3.13"
35
+ cache: pip
36
+ cache-dependency-path: pyproject.toml
37
+
38
+ - name: Install test dependencies
39
+ run: |
40
+ python -m pip install --upgrade pip
41
+ python -m pip install -e ".[glm,gbm]" pytest playwright
42
+ python -m playwright install --with-deps chromium webkit
43
+
44
+ - name: Run complete test gate
45
+ run: python scripts/run_tests.py prepush
@@ -0,0 +1,327 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+
8
+ permissions: {}
9
+
10
+ concurrency:
11
+ group: release
12
+ cancel-in-progress: false
13
+
14
+ jobs:
15
+ validate:
16
+ name: Validate release tag
17
+ runs-on: ubuntu-latest
18
+ permissions:
19
+ actions: read
20
+ contents: read
21
+ outputs:
22
+ release_commit: ${{ steps.release.outputs.release_commit }}
23
+ version: ${{ steps.release.outputs.version }}
24
+
25
+ steps:
26
+ - name: Check out tagged source
27
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
28
+ with:
29
+ fetch-depth: 0
30
+ persist-credentials: false
31
+
32
+ - name: Validate tag, version, branch, and CI
33
+ id: release
34
+ env:
35
+ GH_TOKEN: ${{ github.token }}
36
+ run: |
37
+ if [[ ! "$GITHUB_REF_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
38
+ echo "Release tag must use vMAJOR.MINOR.PATCH: $GITHUB_REF_NAME" >&2
39
+ exit 1
40
+ fi
41
+ if [[ "$(git cat-file -t "$GITHUB_REF_NAME")" != "tag" ]]; then
42
+ echo "Release tag must be annotated: $GITHUB_REF_NAME" >&2
43
+ exit 1
44
+ fi
45
+
46
+ version="$(python - <<'PY'
47
+ import tomllib
48
+ from pathlib import Path
49
+ print(tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8"))["project"]["version"])
50
+ PY
51
+ )"
52
+ if [[ "$GITHUB_REF_NAME" != "v$version" ]]; then
53
+ echo "Tag $GITHUB_REF_NAME does not match pyproject.toml version $version" >&2
54
+ exit 1
55
+ fi
56
+
57
+ release_commit="$(git rev-list -n 1 "$GITHUB_REF_NAME")"
58
+ git fetch --no-tags origin main
59
+ if ! git merge-base --is-ancestor "$release_commit" origin/main; then
60
+ echo "Tagged commit $release_commit is not on origin/main" >&2
61
+ exit 1
62
+ fi
63
+
64
+ conclusion="$(gh api -X GET \
65
+ "repos/$GITHUB_REPOSITORY/actions/workflows/ci.yml/runs" \
66
+ -f branch=main \
67
+ -f event=push \
68
+ -f head_sha="$release_commit" \
69
+ -f status=completed \
70
+ --jq '.workflow_runs[0].conclusion // ""')"
71
+ if [[ "$conclusion" != "success" ]]; then
72
+ echo "Tagged commit $release_commit does not have a successful main CI run" >&2
73
+ exit 1
74
+ fi
75
+
76
+ echo "release_commit=$release_commit" >> "$GITHUB_OUTPUT"
77
+ echo "version=$version" >> "$GITHUB_OUTPUT"
78
+
79
+ build:
80
+ name: Build and smoke-test distributions
81
+ needs: validate
82
+ runs-on: ubuntu-latest
83
+ timeout-minutes: 20
84
+ permissions:
85
+ contents: read
86
+
87
+ steps:
88
+ - name: Check out tagged source
89
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
90
+ with:
91
+ ref: ${{ needs.validate.outputs.release_commit }}
92
+ persist-credentials: false
93
+
94
+ - name: Set up Python
95
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
96
+ with:
97
+ python-version: "3.13"
98
+ cache: pip
99
+ cache-dependency-path: pyproject.toml
100
+
101
+ - name: Build and inspect distributions
102
+ env:
103
+ RELEASE_VERSION: ${{ needs.validate.outputs.version }}
104
+ run: |
105
+ python -m pip install --upgrade pip
106
+ python -m pip install build pipx twine
107
+ python -m build
108
+ python -m twine check dist/*
109
+ python scripts/release_artifacts.py inspect --version "$RELEASE_VERSION"
110
+
111
+ - name: Smoke-test the built wheel through pipx
112
+ env:
113
+ PY_LUCIDUM_EXPECTED_VERSION: ${{ needs.validate.outputs.version }}
114
+ PY_LUCIDUM_PIPX_SPEC: ${{ github.workspace }}/dist/py_lucidum-${{ needs.validate.outputs.version }}-py3-none-any.whl
115
+ run: python scripts/run_tests.py pipx
116
+
117
+ - name: Store distributions
118
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
119
+ with:
120
+ name: python-package-distributions
121
+ path: |
122
+ dist/*.whl
123
+ dist/*.tar.gz
124
+ if-no-files-found: error
125
+ retention-days: 14
126
+
127
+ - name: Store checksums
128
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
129
+ with:
130
+ name: python-package-checksums
131
+ path: dist/SHA256SUMS
132
+ if-no-files-found: error
133
+ retention-days: 14
134
+
135
+ draft-release:
136
+ name: Create draft GitHub release
137
+ needs:
138
+ - validate
139
+ - build
140
+ runs-on: ubuntu-latest
141
+ permissions:
142
+ contents: write
143
+
144
+ steps:
145
+ - name: Download distributions
146
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
147
+ with:
148
+ name: python-package-distributions
149
+ path: dist/
150
+
151
+ - name: Download checksums
152
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
153
+ with:
154
+ name: python-package-checksums
155
+ path: dist/
156
+
157
+ - name: Create draft and attach artifacts
158
+ env:
159
+ GH_TOKEN: ${{ github.token }}
160
+ RELEASE_TAG: ${{ github.ref_name }}
161
+ run: |
162
+ if gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
163
+ is_draft="$(gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --json isDraft --jq .isDraft)"
164
+ if [[ "$is_draft" != "true" ]]; then
165
+ echo "Release $RELEASE_TAG is already published" >&2
166
+ exit 1
167
+ fi
168
+ else
169
+ gh release create "$RELEASE_TAG" \
170
+ --repo "$GITHUB_REPOSITORY" \
171
+ --verify-tag \
172
+ --draft \
173
+ --generate-notes \
174
+ --title "py-lucidum $RELEASE_TAG"
175
+ fi
176
+ gh release upload "$RELEASE_TAG" dist/* \
177
+ --repo "$GITHUB_REPOSITORY" \
178
+ --clobber
179
+
180
+ publish-testpypi:
181
+ name: Publish to TestPyPI
182
+ needs:
183
+ - validate
184
+ - build
185
+ - draft-release
186
+ runs-on: ubuntu-latest
187
+ environment:
188
+ name: testpypi
189
+ url: https://test.pypi.org/p/py-lucidum
190
+ permissions:
191
+ id-token: write
192
+
193
+ steps:
194
+ - name: Download distributions
195
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
196
+ with:
197
+ name: python-package-distributions
198
+ path: dist/
199
+
200
+ - name: Publish distributions
201
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
202
+ with:
203
+ repository-url: https://test.pypi.org/legacy/
204
+
205
+ verify-testpypi:
206
+ name: Verify TestPyPI artifacts
207
+ needs:
208
+ - validate
209
+ - build
210
+ - publish-testpypi
211
+ runs-on: ubuntu-latest
212
+ permissions:
213
+ contents: read
214
+
215
+ steps:
216
+ - name: Check out release verifier
217
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
218
+ with:
219
+ ref: ${{ needs.validate.outputs.release_commit }}
220
+ persist-credentials: false
221
+
222
+ - name: Set up Python
223
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
224
+ with:
225
+ python-version: "3.13"
226
+
227
+ - name: Download distributions
228
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
229
+ with:
230
+ name: python-package-distributions
231
+ path: dist/
232
+
233
+ - name: Compare TestPyPI hashes
234
+ run: >-
235
+ python scripts/release_artifacts.py verify-index
236
+ --version "${{ needs.validate.outputs.version }}"
237
+ --repository-url https://test.pypi.org/pypi
238
+
239
+ publish-pypi:
240
+ name: Publish to PyPI
241
+ needs:
242
+ - validate
243
+ - build
244
+ - verify-testpypi
245
+ runs-on: ubuntu-latest
246
+ environment:
247
+ name: pypi
248
+ url: https://pypi.org/p/py-lucidum
249
+ permissions:
250
+ id-token: write
251
+
252
+ steps:
253
+ - name: Download distributions
254
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
255
+ with:
256
+ name: python-package-distributions
257
+ path: dist/
258
+
259
+ - name: Publish distributions
260
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
261
+
262
+ verify-pypi:
263
+ name: Verify PyPI installation
264
+ needs:
265
+ - validate
266
+ - build
267
+ - publish-pypi
268
+ runs-on: ubuntu-latest
269
+ timeout-minutes: 15
270
+ permissions:
271
+ contents: read
272
+
273
+ steps:
274
+ - name: Check out release verifier
275
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
276
+ with:
277
+ ref: ${{ needs.validate.outputs.release_commit }}
278
+ persist-credentials: false
279
+
280
+ - name: Set up Python
281
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
282
+ with:
283
+ python-version: "3.13"
284
+
285
+ - name: Download distributions
286
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
287
+ with:
288
+ name: python-package-distributions
289
+ path: dist/
290
+
291
+ - name: Compare PyPI hashes
292
+ run: >-
293
+ python scripts/release_artifacts.py verify-index
294
+ --version "${{ needs.validate.outputs.version }}"
295
+ --repository-url https://pypi.org/pypi
296
+
297
+ - name: Smoke-test installation from PyPI
298
+ env:
299
+ PY_LUCIDUM_EXPECTED_VERSION: ${{ needs.validate.outputs.version }}
300
+ PY_LUCIDUM_PIPX_SPEC: py-lucidum==${{ needs.validate.outputs.version }}
301
+ run: |
302
+ python -m pip install pipx
303
+ python scripts/run_tests.py pipx
304
+
305
+ publish-github-release:
306
+ name: Publish immutable GitHub release
307
+ needs:
308
+ - validate
309
+ - draft-release
310
+ - verify-pypi
311
+ runs-on: ubuntu-latest
312
+ permissions:
313
+ contents: write
314
+
315
+ steps:
316
+ - name: Publish the validated draft
317
+ env:
318
+ GH_TOKEN: ${{ github.token }}
319
+ RELEASE_TAG: ${{ github.ref_name }}
320
+ run: |
321
+ gh release edit "$RELEASE_TAG" \
322
+ --repo "$GITHUB_REPOSITORY" \
323
+ --draft=false \
324
+ --latest
325
+ gh release view "$RELEASE_TAG" \
326
+ --repo "$GITHUB_REPOSITORY" \
327
+ --json isDraft,tagName,url
@@ -0,0 +1,27 @@
1
+ # Local development datasets and artifacts
2
+ datasets/*.csv
3
+ datasets/*.parquet
4
+ !datasets/motor_premiums.parquet
5
+ .lucidum/
6
+ local/
7
+
8
+ # Python/cache/build artifacts
9
+ __pycache__/
10
+ *.py[cod]
11
+ .pytest_cache/
12
+ .mypy_cache/
13
+ .ruff_cache/
14
+ .venv
15
+ .venv/
16
+ .venv-linux
17
+ .venv-linux/
18
+ dist/
19
+ build/
20
+ *.egg-info/
21
+
22
+ # OS/editor metadata
23
+ .DS_Store
24
+
25
+ # Generated README previews
26
+ README.html
27
+ README_files/
@@ -0,0 +1,115 @@
1
+ # Repository Guidance
2
+
3
+ This file gives future coding agents the shortest reliable path into `py_lucidum`.
4
+
5
+ ## Start Here
6
+
7
+ - Read `README.md` for user-facing installation, launch commands, and current tool behavior.
8
+ - Read `DEVELOPMENT.md` before non-trivial changes. It is the durable maintainer context for architecture, behavior contracts, test commands, and commit rules.
9
+ - Read the `Releasing` section in `DEVELOPMENT.md` before packaging, tagging, or publishing a release.
10
+ - Current GBM product notes live in `docs/specs/gbm-tool.md`; `docs/specs/gbm-tool_plan.md` is historical implementation context only.
11
+
12
+ ## Environment Notes
13
+
14
+ - This repo is normally driven from Positron, not the Codex app. Do not try to use the Codex in-app Browser plugin here; it will not be available. For browser verification, use the repo's Playwright smoke tooling, local `curl` checks, or a manually launched local browser session.
15
+ - To launch the committed multi-Parquet fixture from a source checkout, run `.venv/bin/lucidum datasets/monthly --port 8000`. Folder inputs are for non-modelling tools only; do not combine this fixture with `glm`, `gbm`, or `--tools all`.
16
+
17
+ ## Shape Of The App
18
+
19
+ - `py_lucidum` is a local-first FastAPI + DuckDB app with a static ES-module frontend.
20
+ - Backend app setup lives under `src/py_lucidum/app/`; core dataset/query helpers live in `src/py_lucidum/core/`.
21
+ - Tool backends live under `src/py_lucidum/tools/`.
22
+ - Frontend shell and tool modules live under `src/py_lucidum/static/app/`.
23
+ - `src/py_lucidum/static/app/main.js` is the frontend coordinator only: schema/defaults, KPI controls, saved filters, sidebar/footer layout, tool selection, cross-tool invalidation, reload orchestration, and boot flow.
24
+ - Frontend tool ownership is split by file: `dataset-viewer-tool.js`, `column-profile-tool.js`, `line-bar-tool.js`, `histogram-tool.js`, `uk-map-tool.js`, `glm-tool.js`, `glm-formula-builder.js`, `glm-model-navigator.js`, `glm-tabulations.js`, `gbm-tool.js`, `gbm-feature-parameter-controls.js`, `gbm-evaluation-chart.js`, `gbm-model-navigator.js`, `gbm-tab-orchestration.js`, `gbm-shap-tool.js`, `gbm-shap-chart.js`, `gbm-stacked-shap-tool.js`, `gbm-stacked-shap-chart.js`, `gbm-tree-viewer.js`, and `model-tool-shell.js`.
25
+ - Import-safe shared frontend helpers live under `src/py_lucidum/static/app/shared/`: API calls, formatters, schema/source helpers, Tabulator lazy loading, and action timing.
26
+ - `src/py_lucidum/static/app.css` is only the stable linked CSS manifest. Actual app styles live under `src/py_lucidum/static/styles/` by shell/tool boundary.
27
+ - Vendored browser libraries live under `src/py_lucidum/static/vendor/`. ECharts and Leaflet load from `index.html` because default tools need them at startup; Ace, Tabulator, D3, and ECharts GL are lazy-loaded by the tools/views that need them.
28
+
29
+ ## Read The Smallest Useful Surface
30
+
31
+ Use this map before opening large files:
32
+
33
+ - App boot, tool switching, shared sidebar/filter/KPI behavior: read `static/app/main.js`, `static/styles/shell.css`, and `static/styles/controls.css`.
34
+ - Shared API, formatting, schema/source helpers, or timing behavior: read the matching file in `static/app/shared/` first; only read tool modules if call-site behavior matters.
35
+ - Dataset Viewer frontend: read `static/app/dataset-viewer-tool.js` and `static/styles/dataset-viewer.css`; backend behavior is under `tools/dataset_viewer/`.
36
+ - Column Profile frontend: read `static/app/column-profile-tool.js` and `static/styles/column-profile.css`; backend behavior is under `tools/column_profile/`.
37
+ - Line/Bar frontend: read `static/app/line-bar-tool.js` and `static/styles/line-bar.css`; backend behavior is under `tools/line_bar/`.
38
+ - Histogram frontend: read `static/app/histogram-tool.js` and `static/styles/histogram.css`; backend behavior is under `tools/histogram/`.
39
+ - UK Mapping frontend: read `static/app/uk-map-tool.js` and `static/styles/uk-map.css`; backend behavior and map static assets are under `tools/uk_map/`.
40
+ - GLM frontend: read `static/app/glm-tool.js`, `static/app/glm-formula-builder.js`, `static/app/glm-model-navigator.js`, `static/app/glm-tabulations.js`, and `static/styles/glm.css`; backend behavior is under `tools/glm/`.
41
+ - GBM frontend: read `static/app/gbm-tool.js`, `static/app/gbm-feature-parameter-controls.js`, `static/app/gbm-evaluation-chart.js`, `static/app/gbm-model-navigator.js`, `static/app/gbm-tab-orchestration.js`, and `static/styles/gbm.css`; SHAP, Stacked SHAP, and tree UI have their own `gbm-shap-*`, `gbm-stacked-shap-*`, and `gbm-tree-viewer.js` files. Backend behavior is under `tools/gbm/`.
42
+ - Generic modelling shell: read `static/app/model-tool-shell.js` and `static/styles/model-shell.css`.
43
+ - CSS changes: read `static/app.css` only to confirm import order. Put shared primitives in `styles/foundations.css` or `styles/controls.css`; put tool-specific selectors in the tool-owned stylesheet.
44
+ - Static asset contract checks and frontend string contracts live in `tests/test_static_assets.py`; browser smoke coverage lives in `tests/test_browser_smoke.py`.
45
+
46
+ ## GBM Notes
47
+
48
+ - GBM is opt-in via `--tools gbm` and optional Python dependencies from `pip install -e ".[gbm]"`.
49
+ - LightGBM with its Arrow bridge, Polars/PyArrow, pandas, and numpy must be imported lazily in GBM training paths, not at base app import time. Large GBM matrices flow from DuckDB to Polars to numeric Arrow; pandas remains for compact artifacts, tabulations, and exports.
50
+ - On macOS, LightGBM may need Homebrew `libomp`; missing native runtime errors should stay actionable rather than surfacing as server 500s.
51
+ - GBM and GLM artifacts are sidecars under `.lucidum/datasets/<dataset-slug>/<dataset-signature>/models/` beside the dataset folder and must not be committed.
52
+ - The GBM SHAP tab reads saved SHAP sidecars only. Keep SHAP aggregation in backend GBM modules, keep SHAP UI/chart code separate, return dense grids for 3D surfaces, and lazy-load vendored ECharts GL only for those surface plots.
53
+
54
+ ## Before Committing
55
+
56
+ Use the tiered runner documented in `DEVELOPMENT.md`. The normal development
57
+ loop selects focused tests from staged, unstaged, and untracked changes, with a
58
+ safe fallback to the broad lane for shared or unknown files:
59
+
60
+ ```bash
61
+ .venv/bin/python scripts/run_tests.py changed
62
+ ```
63
+
64
+ The broad development lane runs syntax checks, non-browser modules, fast GLM
65
+ contracts, and excludes slow modelling process integration cases:
66
+
67
+ ```bash
68
+ .venv/bin/python scripts/run_tests.py dev
69
+ ```
70
+
71
+ Use `focus` or `browser` with a specific target when you need to override the
72
+ automatic selection. The fast commit gate runs staged and unstaged whitespace
73
+ checks, dynamic syntax checks, and the change-aware unit lane:
74
+
75
+ ```bash
76
+ .venv/bin/python scripts/run_tests.py precommit
77
+ ```
78
+
79
+ The complete push gate runs full unittest discovery and all browser smoke tests
80
+ after the same whitespace and syntax checks:
81
+
82
+ ```bash
83
+ .venv/bin/python scripts/run_tests.py prepush
84
+ ```
85
+
86
+ The versioned `.githooks/pre-commit` and `.githooks/pre-push` hooks run those
87
+ commands automatically after one-time setup with
88
+ `git config core.hooksPath .githooks`. Do not manually run `prepush`
89
+ immediately before a normal `git push`, and do not weaken or bypass either hook
90
+ merely to save time.
91
+
92
+ - Before every commit, unless the user explicitly says not to, run `.venv/bin/python scripts/bump_version.py patch`. Include `pyproject.toml` in the same commit and report the final version number.
93
+
94
+ Keep local datasets, `.lucidum/`, virtualenvs, caches, build artifacts, and generated README previews out of git.
95
+
96
+ ## Before Releasing
97
+
98
+ - Release only a clean `main` commit whose hosted `CI` workflow passed. Run the
99
+ release-only pipx/build checks before tagging; the tag workflow repeats the
100
+ artifact and installed-wheel checks.
101
+ - Make all release-preparation changes in one commit when a specific next
102
+ version is required. Apply the normal version bump once before that commit,
103
+ then create an annotated `vMAJOR.MINOR.PATCH` tag whose value exactly matches
104
+ `project.version` in `pyproject.toml`.
105
+ - Do not create or push a release tag, publish or approve a production
106
+ deployment, move a released tag, or yank a release without explicit user
107
+ authorization. Repository changes alone do not imply permission for those
108
+ external actions.
109
+ - PyPI filenames and versions cannot be reused, and published immutable GitHub
110
+ release tags/assets cannot be edited. Fix a bad published release by yanking
111
+ it when appropriate and publishing a higher patch version; never rebuild or
112
+ repoint the old version.
113
+ - `py-lucidum==X.Y.Z` pins Lucidum only. It does not lock transitive dependency
114
+ versions; consuming projects should use their own lock or constraints file
115
+ when they need a reproducible complete environment.