openretailscience 0.48.0__tar.gz → 0.48.1__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 (322) hide show
  1. {openretailscience-0.48.0 → openretailscience-0.48.1}/CONTRIBUTING.md +4 -3
  2. {openretailscience-0.48.0 → openretailscience-0.48.1}/PKG-INFO +1 -1
  3. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/getting_started/agent_skills.md +17 -14
  4. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/skills.py +74 -82
  5. {openretailscience-0.48.0 → openretailscience-0.48.1}/pyproject.toml +1 -1
  6. openretailscience-0.48.1/tests/test_skills.py +778 -0
  7. {openretailscience-0.48.0 → openretailscience-0.48.1}/uv.lock +1 -1
  8. openretailscience-0.48.0/tests/test_skills.py +0 -685
  9. {openretailscience-0.48.0 → openretailscience-0.48.1}/.env_sample +0 -0
  10. {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  11. {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  12. {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/dependabot.yml +0 -0
  13. {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/bigquery-integration.yml +0 -0
  14. {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/claude-code-review.yml +0 -0
  15. {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/claude.yml +0 -0
  16. {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/cloudflare-docs-preview.yml +0 -0
  17. {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/gh-page.yml +0 -0
  18. {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/oracle-integration.yml +0 -0
  19. {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/pyspark-integration.yml +0 -0
  20. {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/release.yml +0 -0
  21. {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/security-audit.yml +0 -0
  22. {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/slack-notifications.yml +0 -0
  23. {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/snowflake-integration.yml +0 -0
  24. {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/sqlserver-integration.yml +0 -0
  25. {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/test.yml +0 -0
  26. {openretailscience-0.48.0 → openretailscience-0.48.1}/.gitignore +0 -0
  27. {openretailscience-0.48.0 → openretailscience-0.48.1}/.markdownlint.json +0 -0
  28. {openretailscience-0.48.0 → openretailscience-0.48.1}/.pre-commit-config.yaml +0 -0
  29. {openretailscience-0.48.0 → openretailscience-0.48.1}/CLAUDE.md +0 -0
  30. {openretailscience-0.48.0 → openretailscience-0.48.1}/CNAME +0 -0
  31. {openretailscience-0.48.0 → openretailscience-0.48.1}/CODE_OF_CONDUCT.md +0 -0
  32. {openretailscience-0.48.0 → openretailscience-0.48.1}/LICENSE +0 -0
  33. {openretailscience-0.48.0 → openretailscience-0.48.1}/README.md +0 -0
  34. {openretailscience-0.48.0 → openretailscience-0.48.1}/data/transactions.parquet +0 -0
  35. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/analysis_modules.md +0 -0
  36. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/analysis/cohort.md +0 -0
  37. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/analysis/composite_rank.md +0 -0
  38. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/analysis/cross_shop.md +0 -0
  39. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/analysis/customer.md +0 -0
  40. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/analysis/customer_decision_hierarchy.md +0 -0
  41. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/analysis/gain_loss.md +0 -0
  42. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/analysis/haversine.md +0 -0
  43. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/analysis/product_association.md +0 -0
  44. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/analysis/revenue_tree.md +0 -0
  45. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/experimental/clv.md +0 -0
  46. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/experimental/metrics/distribution.md +0 -0
  47. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/options.md +0 -0
  48. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/area.md +0 -0
  49. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/bar.md +0 -0
  50. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/broken_timeline.md +0 -0
  51. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/cohort.md +0 -0
  52. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/heatmap.md +0 -0
  53. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/histogram.md +0 -0
  54. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/index.md +0 -0
  55. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/line.md +0 -0
  56. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/period_on_period.md +0 -0
  57. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/price.md +0 -0
  58. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/scatter.md +0 -0
  59. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/styles/graph_utils.md +0 -0
  60. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/styles/styling_helpers.md +0 -0
  61. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/time.md +0 -0
  62. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/venn.md +0 -0
  63. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/waterfall.md +0 -0
  64. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/segmentation/hml.md +0 -0
  65. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/segmentation/nlr.md +0 -0
  66. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/segmentation/rfm.md +0 -0
  67. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/segmentation/segstats.md +0 -0
  68. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/segmentation/threshold.md +0 -0
  69. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/utils/date.md +0 -0
  70. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/utils/filter_and_label.md +0 -0
  71. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/utils/label.md +0 -0
  72. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/analysis_modules/cohort.svg +0 -0
  73. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/analysis_modules/cross_shop.svg +0 -0
  74. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/analysis_modules/customer_decision_hierarchy.svg +0 -0
  75. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/analysis_modules/days_between_purchases.svg +0 -0
  76. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/analysis_modules/gain_loss.svg +0 -0
  77. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/analysis_modules/hml_segmentation.svg +0 -0
  78. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/analysis_modules/purchases_per_customer.svg +0 -0
  79. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/analysis_modules/revenue_tree.svg +0 -0
  80. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/analysis_modules/threshold_segmentation.svg +0 -0
  81. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/analysis_modules/transaction_churn.svg +0 -0
  82. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/features/charts.png +0 -0
  83. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/features/cross_shop.png +0 -0
  84. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/area_thumbnail.png +0 -0
  85. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/bar_thumbnail.png +0 -0
  86. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/broken_timeline_thumbnail.png +0 -0
  87. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/cohort_thumbnail.png +0 -0
  88. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/customer_decision_hierarchy_thumbnail.png +0 -0
  89. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/heatmap_thumbnail.png +0 -0
  90. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/histogram_thumbnail.png +0 -0
  91. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/index_thumbnail.png +0 -0
  92. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/line_thumbnail.png +0 -0
  93. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/period_on_period_thumbnail.png +0 -0
  94. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/price_thumbnail.png +0 -0
  95. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/revenue_tree_thumbnail.png +0 -0
  96. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/scatter_thumbnail.png +0 -0
  97. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/time_thumbnail.png +0 -0
  98. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/venn_thumbnail.png +0 -0
  99. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/waterfall_thumbnail.png +0 -0
  100. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/logo.svg +0 -0
  101. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/examples/cross_shop.ipynb +0 -0
  102. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/examples/gain_loss.ipynb +0 -0
  103. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/examples/product_association.ipynb +0 -0
  104. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/examples/retention.ipynb +0 -0
  105. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/examples/revenue_tree.ipynb +0 -0
  106. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/examples/segmentation.ipynb +0 -0
  107. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/experimental/clv.md +0 -0
  108. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/experimental/metrics.md +0 -0
  109. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/index.md +0 -0
  110. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/area.ipynb +0 -0
  111. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/bar.ipynb +0 -0
  112. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/broken_timeline.ipynb +0 -0
  113. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/cohort.ipynb +0 -0
  114. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/customer_decision_hierarchy.ipynb +0 -0
  115. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/heatmap.ipynb +0 -0
  116. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/histogram.ipynb +0 -0
  117. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/index_plot.ipynb +0 -0
  118. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/line.ipynb +0 -0
  119. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/period_on_period.ipynb +0 -0
  120. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/price.ipynb +0 -0
  121. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/revenue_tree.ipynb +0 -0
  122. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/scatter.ipynb +0 -0
  123. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/time.ipynb +0 -0
  124. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/venn.ipynb +0 -0
  125. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/waterfall.ipynb +0 -0
  126. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/getting_started/connecting_to_data.md +0 -0
  127. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/getting_started/data_structures.md +0 -0
  128. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/getting_started/installation.md +0 -0
  129. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/getting_started/options_guide.md +0 -0
  130. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/getting_started/styling_guide.ipynb +0 -0
  131. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/index.md +0 -0
  132. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/javascripts/mathjax.js +0 -0
  133. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/overrides/home.html +0 -0
  134. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/overrides/main.html +0 -0
  135. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/robots.txt +0 -0
  136. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/scripts/README.md +0 -0
  137. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/scripts/regenerate_analysis_modules_svgs.py +0 -0
  138. {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/stylesheets/extra.css +0 -0
  139. {openretailscience-0.48.0 → openretailscience-0.48.1}/mkdocs.yml +0 -0
  140. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/SKILL.md +0 -0
  141. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/references/analysis.md +0 -0
  142. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/references/clv.md +0 -0
  143. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/references/configuration.md +0 -0
  144. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/references/plotting.md +0 -0
  145. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_advanced_customization.py +0 -0
  146. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_area.py +0 -0
  147. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_bar.py +0 -0
  148. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_broken_timeline.py +0 -0
  149. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_clv_stats.py +0 -0
  150. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_cohort.py +0 -0
  151. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_cohort_analysis.py +0 -0
  152. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_composite_rank.py +0 -0
  153. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_connecting_to_data.py +0 -0
  154. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_cross_shop.py +0 -0
  155. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_customer_decision_hierarchy.py +0 -0
  156. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_filter_and_label.py +0 -0
  157. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_filter_by_periods.py +0 -0
  158. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_gain_loss.py +0 -0
  159. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_haversine_distance.py +0 -0
  160. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_heatmap.py +0 -0
  161. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_histogram.py +0 -0
  162. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_hml_segmentation.py +0 -0
  163. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_human_format.py +0 -0
  164. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_index.py +0 -0
  165. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_label_by_condition.py +0 -0
  166. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_line.py +0 -0
  167. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_nlr_segmentation.py +0 -0
  168. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_options.py +0 -0
  169. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_overlapping_periods.py +0 -0
  170. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_period_on_period.py +0 -0
  171. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_price.py +0 -0
  172. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_product_association.py +0 -0
  173. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_purchases_per_customer.py +0 -0
  174. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_revenue_tree.py +0 -0
  175. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_rfm_segmentation.py +0 -0
  176. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_scatter.py +0 -0
  177. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_segment_transaction_stats.py +0 -0
  178. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_set_axis_percent.py +0 -0
  179. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_threshold_segmentation.py +0 -0
  180. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_time.py +0 -0
  181. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_tree_diagram.py +0 -0
  182. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_trend_line.py +0 -0
  183. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_venn.py +0 -0
  184. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_waterfall.py +0 -0
  185. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/__init__.py +0 -0
  186. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/analysis/__init__.py +0 -0
  187. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/analysis/cohort.py +0 -0
  188. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/analysis/composite_rank.py +0 -0
  189. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/analysis/cross_shop.py +0 -0
  190. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/analysis/customer.py +0 -0
  191. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/analysis/customer_decision_hierarchy.py +0 -0
  192. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/analysis/gain_loss.py +0 -0
  193. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/analysis/haversine.py +0 -0
  194. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/analysis/product_association.py +0 -0
  195. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/analysis/revenue_tree.py +0 -0
  196. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/assets/fonts/Poppins-Bold.ttf +0 -0
  197. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/assets/fonts/Poppins-LightItalic.ttf +0 -0
  198. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/assets/fonts/Poppins-Medium.ttf +0 -0
  199. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/assets/fonts/Poppins-Regular.ttf +0 -0
  200. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/assets/fonts/Poppins-SemiBold.ttf +0 -0
  201. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/constants.py +0 -0
  202. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/core/__init__.py +0 -0
  203. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/core/validation.py +0 -0
  204. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/experimental/__init__.py +0 -0
  205. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/experimental/clv.py +0 -0
  206. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/experimental/metrics/__init__.py +0 -0
  207. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/experimental/metrics/base.py +0 -0
  208. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/experimental/metrics/distribution/__init__.py +0 -0
  209. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/experimental/metrics/distribution/acv.py +0 -0
  210. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/experimental/metrics/distribution/pct_of_stores.py +0 -0
  211. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/options.py +0 -0
  212. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/__init__.py +0 -0
  213. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/area.py +0 -0
  214. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/bar.py +0 -0
  215. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/broken_timeline.py +0 -0
  216. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/cohort.py +0 -0
  217. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/heatmap.py +0 -0
  218. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/histogram.py +0 -0
  219. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/index.py +0 -0
  220. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/line.py +0 -0
  221. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/period_on_period.py +0 -0
  222. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/price.py +0 -0
  223. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/scatter.py +0 -0
  224. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/styles/__init__.py +0 -0
  225. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/styles/colors.py +0 -0
  226. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/styles/font_utils.py +0 -0
  227. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/styles/graph_utils.py +0 -0
  228. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/styles/styling_helpers.py +0 -0
  229. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/styles/trend.py +0 -0
  230. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/time.py +0 -0
  231. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/tree_diagram.py +0 -0
  232. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/venn.py +0 -0
  233. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/waterfall.py +0 -0
  234. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/py.typed +0 -0
  235. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/segmentation/__init__.py +0 -0
  236. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/segmentation/hml.py +0 -0
  237. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/segmentation/nlr.py +0 -0
  238. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/segmentation/rfm.py +0 -0
  239. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/segmentation/segstats.py +0 -0
  240. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/segmentation/threshold.py +0 -0
  241. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/utils/__init__.py +0 -0
  242. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/utils/date.py +0 -0
  243. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/utils/filter_and_label.py +0 -0
  244. {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/utils/label.py +0 -0
  245. {openretailscience-0.48.0 → openretailscience-0.48.1}/options_template.toml +0 -0
  246. {openretailscience-0.48.0 → openretailscience-0.48.1}/osv-scanner.toml +0 -0
  247. {openretailscience-0.48.0 → openretailscience-0.48.1}/readme_assets/cross_shop_chart.png +0 -0
  248. {openretailscience-0.48.0 → openretailscience-0.48.1}/readme_assets/days_until_next_purchase.png +0 -0
  249. {openretailscience-0.48.0 → openretailscience-0.48.1}/readme_assets/gain_loss_chart.png +0 -0
  250. {openretailscience-0.48.0 → openretailscience-0.48.1}/readme_assets/logo.png +0 -0
  251. {openretailscience-0.48.0 → openretailscience-0.48.1}/readme_assets/logo.svg +0 -0
  252. {openretailscience-0.48.0 → openretailscience-0.48.1}/readme_assets/new_store_cannibalization.png +0 -0
  253. {openretailscience-0.48.0 → openretailscience-0.48.1}/taplo.toml +0 -0
  254. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/analysis/test_cohort.py +0 -0
  255. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/analysis/test_composite_rank.py +0 -0
  256. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/analysis/test_cross_shop.py +0 -0
  257. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/analysis/test_customer.py +0 -0
  258. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/analysis/test_customer_decision_hierarchy.py +0 -0
  259. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/analysis/test_gain_loss.py +0 -0
  260. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/analysis/test_haversine.py +0 -0
  261. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/analysis/test_product_association.py +0 -0
  262. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/analysis/test_revenue_tree.py +0 -0
  263. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/core/__init__.py +0 -0
  264. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/core/test_validation.py +0 -0
  265. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/experimental/metrics/distribution/test_acv.py +0 -0
  266. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/experimental/metrics/distribution/test_pct_of_stores.py +0 -0
  267. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/experimental/metrics/test_base.py +0 -0
  268. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/experimental/test_clv.py +0 -0
  269. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/conftest.py +0 -0
  270. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/docker/README.md +0 -0
  271. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/docker/docker-compose.oracle.yml +0 -0
  272. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/docker/docker-compose.sqlserver.yml +0 -0
  273. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_clv_stats.py +0 -0
  274. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_cohort_analysis.py +0 -0
  275. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_composite_rank.py +0 -0
  276. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_cross_shop.py +0 -0
  277. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_customer_decision_hierarchy.py +0 -0
  278. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_date.py +0 -0
  279. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_filter_and_label.py +0 -0
  280. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_haversine.py +0 -0
  281. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_hml_segmentation.py +0 -0
  282. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_product_association.py +0 -0
  283. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_revenue_tree.py +0 -0
  284. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_rfm_segmentation.py +0 -0
  285. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_segstats_segmentation.py +0 -0
  286. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_threshold_segmentation.py +0 -0
  287. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/styles/test_chart_chrome.py +0 -0
  288. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/styles/test_color.py +0 -0
  289. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/styles/test_font_utils.py +0 -0
  290. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/styles/test_graph_utils.py +0 -0
  291. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/styles/test_styling_helper.py +0 -0
  292. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/styles/test_trend.py +0 -0
  293. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_area.py +0 -0
  294. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_bar.py +0 -0
  295. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_broken_timeline.py +0 -0
  296. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_cohort.py +0 -0
  297. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_heatmap.py +0 -0
  298. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_histogram.py +0 -0
  299. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_index.py +0 -0
  300. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_line.py +0 -0
  301. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_period_on_period.py +0 -0
  302. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_price.py +0 -0
  303. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_scatter.py +0 -0
  304. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_styling_options_integration.py +0 -0
  305. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_time.py +0 -0
  306. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_tree_diagram.py +0 -0
  307. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_venn.py +0 -0
  308. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_waterfall.py +0 -0
  309. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/segmentation/test_hml.py +0 -0
  310. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/segmentation/test_nlr.py +0 -0
  311. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/segmentation/test_rfm.py +0 -0
  312. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/segmentation/test_segstats.py +0 -0
  313. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/segmentation/test_threshold.py +0 -0
  314. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/test_options.py +0 -0
  315. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/toml_files/corrupt.toml +0 -0
  316. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/toml_files/invalid_option.toml +0 -0
  317. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/toml_files/test_options.toml +0 -0
  318. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/toml_files/valid.toml +0 -0
  319. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/utils/test_date.py +0 -0
  320. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/utils/test_filter_and_label.py +0 -0
  321. {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/utils/test_label.py +0 -0
  322. {openretailscience-0.48.0 → openretailscience-0.48.1}/tox.ini +0 -0
@@ -45,9 +45,10 @@ describe the public API — analyses, plots, options, and how to connect to data
45
45
 
46
46
  If you add, rename, or remove a public class, function, plot, or option, update
47
47
  the skill in the same PR. `tests/test_skills.py` guards this: it re-executes
48
- every import example in the skill and fails if one no longer resolves, and it
49
- checks that every referenced file exists. Run `uv run pytest tests/test_skills.py`
50
- before pushing.
48
+ every import example and every bundled example script, and it requires the
49
+ reference files under `references/` and the links pointing at them to match
50
+ exactly, so a new reference file must also be linked from the skill. Run
51
+ `uv run pytest tests/test_skills.py` before pushing.
51
52
 
52
53
  ### Getting Help
53
54
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openretailscience
3
- Version: 0.48.0
3
+ Version: 0.48.1
4
4
  Summary: Retail Data Science Tools
5
5
  Author-email: Murray Vanwyk <2493311+mvanwyk@users.noreply.github.com>
6
6
  License-Expression: Elastic-2.0
@@ -25,17 +25,18 @@ the installed package never drift apart:
25
25
  `.claude/skills/using-openretailscience/` when Claude Code is detected (a
26
26
  `~/.claude` directory exists).
27
27
  - **Global mode** links into the equivalent directories under your home folder.
28
+ Not available on Databricks (see below).
28
29
 
29
30
  Each skill installs as a subfolder named after itself, so the bundled skill
30
31
  lands at `using-openretailscience/` inside whichever skills directory applies.
31
32
 
32
- Because the entries are symlinks back into the installed package, a
33
- `pip install -U openretailscience` updates the skill in place, so there is nothing
33
+ Outside Databricks the entries are symlinks back into the installed package, so a
34
+ `pip install -U openretailscience` updates the skill in place and there is nothing
34
35
  to reinstall.
35
36
 
36
37
  ```python
37
38
  install_skills() # project install
38
- install_skills(global_mode=True) # install for all your projects
39
+ install_skills(global_mode=True) # install for all your projects (not on Databricks)
39
40
  ```
40
41
 
41
42
  The operation is **idempotent**: re-running it re-uses existing links and never
@@ -52,19 +53,22 @@ one left by an earlier install.
52
53
 
53
54
  ## Databricks
54
55
 
55
- Databricks works differently, and `install_skills()` adapts
56
+ Databricks works differently, and the default `install_skills()` adapts
56
57
  automatically. A pip-installed package lives on the cluster's ephemeral storage,
57
58
  which is wiped on restart, and Databricks Genie reads skills from a persistent
58
59
  workspace location that cannot symlink back into that storage. So on Databricks
59
60
  the installer **copies** the skill into the Genie skills directory instead of
60
- linking it:
61
+ linking it.
61
62
 
62
- - Project mode `/Workspace/.assistant/skills/`
63
- - Global mode `/Workspace/Users/<you>/.assistant/skills/` (falling back to the
64
- shared workspace directory when the user cannot be determined)
63
+ Skills go to `/Workspace/Users/<you>/.assistant/skills/`, your own workspace home.
64
+ `global_mode=True` raises `NotImplementedError` there: the workspace-wide
65
+ `/Workspace/.assistant/skills/` needs admin rights, and a single shared copy
66
+ cannot match the package version each user has installed.
65
67
 
66
- Run it from a notebook, or from a cluster init script so it re-applies on every
67
- start:
68
+ Run it from a notebook, as yourself. Your workspace home comes from the Spark
69
+ session's `current_user()`, so cases like these raise rather than install: a
70
+ context with no session (a cluster init script, a bare Python job), and an identity
71
+ with no workspace home of its own, such as a job running as a service principal.
68
72
 
69
73
  ```python
70
74
  from openretailscience.skills import install_skills
@@ -73,11 +77,10 @@ install_skills()
73
77
  ```
74
78
 
75
79
  Because this is a copy rather than a link, it does **not** update itself when you
76
- upgrade the package. Re-run `install_skills()` after upgrading OpenRetailScience
77
- (a cluster init script is the easiest way to keep it current).
80
+ upgrade the package: re-run `install_skills()` after upgrading OpenRetailScience.
78
81
 
79
82
  !!! warning "Managed directory"
80
- The workspace `.assistant/skills/` directory is treated as installer-managed:
83
+ Your workspace `.assistant/skills/` directory is treated as installer-managed:
81
84
  a folder there whose name matches a bundled skill is refreshed on every run.
82
85
  If you hand-author your own skill under that directory, give it a different
83
86
  name so `install_skills()` does not overwrite it.
@@ -88,7 +91,7 @@ upgrade the package. Re-run `install_skills()` after upgrading OpenRetailScience
88
91
  | --- | --- | --- |
89
92
  | Local / project | symlink | Yes, automatically |
90
93
  | Global (home) | symlink | Yes, automatically |
91
- | Databricks | copy | No, re-run `install_skills()` |
94
+ | Databricks (no global mode) | copy | No, re-run `install_skills()` |
92
95
 
93
96
  The skill's content is maintained alongside the codebase and validated in CI, so
94
97
  each release ships guidance that matches that version's public API.
@@ -20,8 +20,8 @@ Linking behaviour:
20
20
  * On Databricks the package lives on ephemeral compute that is wiped on cluster
21
21
  restart, and Genie reads skills from a persistent ``/Workspace`` location that
22
22
  cannot symlink into site-packages. There we always **copy** skills into the
23
- Genie skills directory; re-run :func:`install_skills` after upgrading the
24
- package to refresh the copy.
23
+ user's own ``/Workspace/Users/<user>/.assistant/skills`` directory; re-run
24
+ :func:`install_skills` after upgrading the package to refresh the copy.
25
25
  """
26
26
 
27
27
  from __future__ import annotations
@@ -39,14 +39,13 @@ SKILL_MARKER_FILENAME = "SKILL.md"
39
39
  AGENTS_DIR_NAME = ".agents"
40
40
  CLAUDE_DIR_NAME = ".claude"
41
41
  SKILLS_SUBDIR = "skills"
42
+ # pip byte-compiles the package it installs, leaving these inside the bundled skill.
43
+ # The Databricks workspace mount rejects them with OSError 95, part-way through a copy.
44
+ BYTECODE_CACHE_DIR = "__pycache__"
42
45
 
43
46
  # Databricks Genie / Assistant reads skills from ``<root>/.assistant/skills``.
44
47
  DATABRICKS_ASSISTANT_DIR = ".assistant"
45
- DATABRICKS_USERS_DIR = "Users"
46
48
  _DATABRICKS_RUNTIME_ENV = "DATABRICKS_RUNTIME_VERSION"
47
- _DATABRICKS_USER_ENV = "DATABRICKS_USER"
48
- # Persistent workspace root on Databricks. Kept as a module constant so tests
49
- # can redirect it away from the real ``/Workspace`` mount.
50
49
  _DATABRICKS_WORKSPACE_ROOT = Path("/Workspace")
51
50
 
52
51
 
@@ -109,7 +108,7 @@ def _find_project_root(start: Path | None = None) -> Path:
109
108
  Returns:
110
109
  Path: The resolved project root.
111
110
  """
112
- start_dir = (start or Path.cwd()).resolve()
111
+ start_dir = (start if start is not None else Path.cwd()).resolve()
113
112
  home = Path.home().resolve()
114
113
 
115
114
  git_root: Path | None = None
@@ -137,47 +136,37 @@ def _skills_dir(base: Path, harness_dir: str) -> Path:
137
136
  return base / harness_dir / SKILLS_SUBDIR
138
137
 
139
138
 
140
- def _get_target_dirs(base: Path) -> list[Path]:
141
- """Return the ``.agents/skills`` (and ``.claude/skills``) targets under ``base``.
142
-
143
- Always targets ``<base>/.agents/skills``; adds ``<base>/.claude/skills`` when
144
- Claude Code is detected (a ``~/.claude`` directory exists).
145
-
146
- Args:
147
- base (Path): The resolved project root (project mode) or home directory
148
- (global mode).
139
+ def _get_databricks_target_dir() -> Path:
140
+ """Return the Genie skills directory in the caller's Databricks workspace home.
149
141
 
150
142
  Returns:
151
- list[Path]: Target skill directories.
152
- """
153
- targets = [_skills_dir(base, AGENTS_DIR_NAME)]
154
- if (Path.home() / CLAUDE_DIR_NAME).is_dir():
155
- targets.append(_skills_dir(base, CLAUDE_DIR_NAME))
156
- return targets
157
-
143
+ Path: The Genie skills directory to copy into.
158
144
 
159
- def _get_databricks_target_dirs(*, global_mode: bool) -> list[Path]:
160
- """Return the persistent Databricks Genie skills directory.
145
+ Raises:
146
+ ImportError: When pyspark is unavailable, so the caller cannot be named.
147
+ RuntimeError: When no Spark session is running to name the caller, when
148
+ ``current_user()`` does not name a workspace user, or when that user's
149
+ workspace home does not exist.
150
+ """
151
+ from pyspark.sql import SparkSession # noqa: PLC0415 - pyspark is not a dependency; Databricks provides it
161
152
 
162
- Project mode installs to the shared workspace skills directory; global mode
163
- installs to the per-user directory when the user is known, otherwise falls
164
- back to the shared directory with a note.
153
+ spark = SparkSession.getActiveSession()
154
+ if spark is None:
155
+ msg = "Installing to your Databricks workspace home needs an active Spark session; run this from a notebook."
156
+ raise RuntimeError(msg)
165
157
 
166
- Args:
167
- global_mode (bool): Whether to install per-user rather than shared.
158
+ # A SELECT of a scalar function always yields exactly one row.
159
+ user = str(spark.sql("SELECT current_user()").collect()[0][0])
160
+ # An empty, "..", or otherwise path-like name would join back out to the admin-only workspace root.
161
+ if user in {"", ".."} or user != Path(user).name:
162
+ msg = f"Databricks current_user() did not name a workspace user: {user!r}"
163
+ raise RuntimeError(msg)
168
164
 
169
- Returns:
170
- list[Path]: The single Genie skills directory to copy into.
171
- """
172
- if global_mode:
173
- user = os.environ.get(_DATABRICKS_USER_ENV) or None # blank env var counts as unknown
174
- if user is not None:
175
- base = _DATABRICKS_WORKSPACE_ROOT / DATABRICKS_USERS_DIR / user
176
- return [_skills_dir(base, DATABRICKS_ASSISTANT_DIR)]
177
- print( # noqa: T201 - user-facing installer output
178
- "Could not determine the Databricks user; installing to the shared workspace skills directory instead."
179
- )
180
- return [_skills_dir(_DATABRICKS_WORKSPACE_ROOT, DATABRICKS_ASSISTANT_DIR)]
165
+ home = _DATABRICKS_WORKSPACE_ROOT / "Users" / user
166
+ if not home.is_dir():
167
+ msg = f"Databricks workspace home not found: {home}. current_user() returned {user!r}."
168
+ raise RuntimeError(msg)
169
+ return _skills_dir(home, DATABRICKS_ASSISTANT_DIR)
181
170
 
182
171
 
183
172
  def _relative_symlink_target(source_path: Path, target_path: Path) -> str:
@@ -219,6 +208,18 @@ def _try_symlink(source_path: Path, target_path: Path) -> bool:
219
208
  return True
220
209
 
221
210
 
211
+ def _copied_paths(root: Path) -> list[Path]:
212
+ """List the paths under ``root`` that an install copies.
213
+
214
+ Args:
215
+ root (Path): A skill directory, bundled or installed.
216
+
217
+ Returns:
218
+ list[Path]: Sorted paths relative to ``root``, excluding bytecode caches.
219
+ """
220
+ return sorted(p.relative_to(root) for p in root.rglob("*") if BYTECODE_CACHE_DIR not in p.parts)
221
+
222
+
222
223
  def _skill_copy_matches(source_path: Path, target_path: Path) -> bool:
223
224
  """Return whether a copied skill directory matches the source byte-for-byte.
224
225
 
@@ -231,8 +232,8 @@ def _skill_copy_matches(source_path: Path, target_path: Path) -> bool:
231
232
  """
232
233
  if not target_path.is_dir():
233
234
  return False
234
- source_files = sorted(p.relative_to(source_path) for p in source_path.rglob("*"))
235
- if source_files != sorted(p.relative_to(target_path) for p in target_path.rglob("*")):
235
+ source_files = _copied_paths(source_path)
236
+ if source_files != _copied_paths(target_path):
236
237
  return False
237
238
  for rel in source_files:
238
239
  source_file = source_path / rel
@@ -249,11 +250,8 @@ def _is_owned_target(target_path: Path, bundled_names: set[str]) -> bool:
249
250
 
250
251
  A target is owned when it is a symlink whose name matches a bundled skill, or
251
252
  a real directory named after a bundled skill that itself contains a
252
- ``SKILL.md`` (a prior copy install). Anything else is user content.
253
-
254
- Ownership only permits replacement; whether an owned *real directory* is
255
- actually replaced depends on the install method (see :func:`_prepare_target`):
256
- symlink mode always leaves real directories in place, copy mode refreshes them.
253
+ ``SKILL.md`` (a prior copy install). Anything else is user content. Ownership
254
+ only permits replacement; :func:`_prepare_target` decides whether it happens.
257
255
 
258
256
  Args:
259
257
  target_path (Path): The candidate target.
@@ -287,7 +285,7 @@ def _prepare_target(
287
285
  use_copy (bool): Whether the install method is copy (vs. symlink).
288
286
 
289
287
  Returns:
290
- str: One of ``"install"``, ``"up_to_date"``, or ``"skip"``.
288
+ Literal["install", "up_to_date", "skip"]: The disposition for this target.
291
289
  """
292
290
  if not target_path.exists() and not target_path.is_symlink():
293
291
  return "install"
@@ -303,11 +301,6 @@ def _prepare_target(
303
301
  target_path.unlink() # os.unlink semantics: remove the link, not its target
304
302
  return "install"
305
303
 
306
- # Owned real directory. A byte-identical tree is a current install (a prior
307
- # copy-fallback run, or a Databricks copy), so report it up to date. Otherwise
308
- # copy mode refreshes it, while symlink mode skips it: a non-matching real
309
- # directory may be user-authored content that merely shares a bundled skill's
310
- # name, so it must not be deleted.
311
304
  if _skill_copy_matches(source_path, target_path):
312
305
  return "up_to_date"
313
306
  if not use_copy:
@@ -354,37 +347,22 @@ def _install_one(
354
347
  # so a skipped skill never leaves an empty directory behind.
355
348
  target_dir.mkdir(parents=True, exist_ok=True)
356
349
  if use_copy or not _try_symlink(source_path, target_path):
357
- shutil.copytree(source_path, target_path)
350
+ shutil.copytree(source_path, target_path, ignore=shutil.ignore_patterns(BYTECODE_CACHE_DIR))
358
351
  result.installed.append(label)
359
352
 
360
353
 
361
- def _print_result(result: SkillInstallResult) -> None:
362
- """Print a summary of the installation outcome.
363
-
364
- Args:
365
- result (SkillInstallResult): The completed install result.
366
- """
367
- for label, paths in (
368
- ("Installed", result.installed),
369
- ("Already up to date", result.up_to_date),
370
- ("Skipped (conflicting file)", result.skipped),
371
- ):
372
- for path in paths:
373
- print(f"{label}: {path}") # noqa: T201
374
-
375
-
376
354
  def install_skills(global_mode: bool = False) -> SkillInstallResult:
377
355
  """Install the package's bundled agent skills.
378
356
 
379
357
  In project mode (default) skills are linked into the current project's
380
358
  ``.agents/skills/`` (and ``.claude/skills/`` when Claude Code is detected). In
381
359
  global mode they are linked into the equivalent home directories. On
382
- Databricks, skills are copied into the persistent Genie workspace skills
383
- directory instead of linked. The operation is idempotent.
360
+ Databricks skills are copied into the caller's persistent workspace Genie
361
+ skills directory instead. The operation is idempotent.
384
362
 
385
363
  Args:
386
- global_mode (bool): Install to user-level home directories instead of the
387
- current project. Defaults to False.
364
+ global_mode (bool): Install into the user-level home directories rather
365
+ than the current project. Defaults to False. Unsupported on Databricks.
388
366
 
389
367
  Returns:
390
368
  SkillInstallResult: The skills that were installed, already up to date,
@@ -393,7 +371,9 @@ def install_skills(global_mode: bool = False) -> SkillInstallResult:
393
371
  Raises:
394
372
  FileNotFoundError: When the bundled skills directory is missing.
395
373
  RuntimeError: When the bundled skills directory contains no installable
396
- skills.
374
+ skills, or when the Databricks workspace home cannot be resolved.
375
+ NotImplementedError: When ``global_mode`` is requested on Databricks.
376
+ ImportError: When pyspark is unavailable on Databricks.
397
377
  """
398
378
  source_dir = _get_source_skills_dir()
399
379
  if not source_dir.is_dir():
@@ -406,13 +386,19 @@ def install_skills(global_mode: bool = False) -> SkillInstallResult:
406
386
  raise RuntimeError(msg)
407
387
 
408
388
  if _DATABRICKS_RUNTIME_ENV in os.environ:
409
- target_dirs = _get_databricks_target_dirs(global_mode=global_mode)
389
+ if global_mode:
390
+ msg = (
391
+ "global_mode is not supported on Databricks: the workspace-wide skills directory needs admin "
392
+ "rights, and one shared copy cannot match the package version each user has installed."
393
+ )
394
+ raise NotImplementedError(msg)
395
+ target_dirs = [_get_databricks_target_dir()]
410
396
  use_copy = True
411
- elif global_mode:
412
- target_dirs = _get_target_dirs(Path.home())
413
- use_copy = False
414
397
  else:
415
- target_dirs = _get_target_dirs(_find_project_root())
398
+ base = Path.home() if global_mode else _find_project_root()
399
+ target_dirs = [_skills_dir(base, AGENTS_DIR_NAME)]
400
+ if (Path.home() / CLAUDE_DIR_NAME).is_dir(): # Claude Code is installed
401
+ target_dirs.append(_skills_dir(base, CLAUDE_DIR_NAME))
416
402
  use_copy = False
417
403
 
418
404
  result = SkillInstallResult()
@@ -421,5 +407,11 @@ def install_skills(global_mode: bool = False) -> SkillInstallResult:
421
407
  for target_dir in target_dirs:
422
408
  _install_one(skill_name, source_dir, target_dir, result, bundled_names, use_copy=use_copy)
423
409
 
424
- _print_result(result)
410
+ for label, paths in (
411
+ ("Installed", result.installed),
412
+ ("Already up to date", result.up_to_date),
413
+ ("Skipped (conflicting file)", result.skipped),
414
+ ):
415
+ for path in paths:
416
+ print(f"{label}: {path}") # noqa: T201
425
417
  return result
@@ -27,7 +27,7 @@ license = "Elastic-2.0"
27
27
  name = "openretailscience"
28
28
  readme = "README.md"
29
29
  requires-python = ">=3.10,<3.14"
30
- version = "0.48.0"
30
+ version = "0.48.1"
31
31
  [[project.authors]]
32
32
  email = "2493311+mvanwyk@users.noreply.github.com"
33
33
  name = "Murray Vanwyk"