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.
- {openretailscience-0.48.0 → openretailscience-0.48.1}/CONTRIBUTING.md +4 -3
- {openretailscience-0.48.0 → openretailscience-0.48.1}/PKG-INFO +1 -1
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/getting_started/agent_skills.md +17 -14
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/skills.py +74 -82
- {openretailscience-0.48.0 → openretailscience-0.48.1}/pyproject.toml +1 -1
- openretailscience-0.48.1/tests/test_skills.py +778 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/uv.lock +1 -1
- openretailscience-0.48.0/tests/test_skills.py +0 -685
- {openretailscience-0.48.0 → openretailscience-0.48.1}/.env_sample +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/dependabot.yml +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/bigquery-integration.yml +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/claude-code-review.yml +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/claude.yml +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/cloudflare-docs-preview.yml +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/gh-page.yml +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/oracle-integration.yml +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/pyspark-integration.yml +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/release.yml +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/security-audit.yml +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/slack-notifications.yml +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/snowflake-integration.yml +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/sqlserver-integration.yml +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/.github/workflows/test.yml +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/.gitignore +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/.markdownlint.json +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/.pre-commit-config.yaml +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/CLAUDE.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/CNAME +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/CODE_OF_CONDUCT.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/LICENSE +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/README.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/data/transactions.parquet +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/analysis_modules.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/analysis/cohort.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/analysis/composite_rank.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/analysis/cross_shop.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/analysis/customer.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/analysis/customer_decision_hierarchy.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/analysis/gain_loss.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/analysis/haversine.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/analysis/product_association.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/analysis/revenue_tree.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/experimental/clv.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/experimental/metrics/distribution.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/options.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/area.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/bar.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/broken_timeline.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/cohort.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/heatmap.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/histogram.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/index.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/line.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/period_on_period.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/price.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/scatter.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/styles/graph_utils.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/styles/styling_helpers.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/time.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/venn.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/plots/waterfall.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/segmentation/hml.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/segmentation/nlr.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/segmentation/rfm.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/segmentation/segstats.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/segmentation/threshold.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/utils/date.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/utils/filter_and_label.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/api/utils/label.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/analysis_modules/cohort.svg +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/analysis_modules/cross_shop.svg +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/analysis_modules/customer_decision_hierarchy.svg +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/analysis_modules/days_between_purchases.svg +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/analysis_modules/gain_loss.svg +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/analysis_modules/hml_segmentation.svg +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/analysis_modules/purchases_per_customer.svg +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/analysis_modules/revenue_tree.svg +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/analysis_modules/threshold_segmentation.svg +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/analysis_modules/transaction_churn.svg +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/features/charts.png +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/features/cross_shop.png +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/area_thumbnail.png +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/bar_thumbnail.png +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/broken_timeline_thumbnail.png +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/cohort_thumbnail.png +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/customer_decision_hierarchy_thumbnail.png +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/heatmap_thumbnail.png +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/histogram_thumbnail.png +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/index_thumbnail.png +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/line_thumbnail.png +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/period_on_period_thumbnail.png +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/price_thumbnail.png +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/revenue_tree_thumbnail.png +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/scatter_thumbnail.png +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/time_thumbnail.png +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/venn_thumbnail.png +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/gallery/waterfall_thumbnail.png +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/assets/images/logo.svg +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/examples/cross_shop.ipynb +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/examples/gain_loss.ipynb +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/examples/product_association.ipynb +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/examples/retention.ipynb +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/examples/revenue_tree.ipynb +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/examples/segmentation.ipynb +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/experimental/clv.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/experimental/metrics.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/index.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/area.ipynb +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/bar.ipynb +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/broken_timeline.ipynb +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/cohort.ipynb +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/customer_decision_hierarchy.ipynb +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/heatmap.ipynb +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/histogram.ipynb +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/index_plot.ipynb +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/line.ipynb +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/period_on_period.ipynb +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/price.ipynb +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/revenue_tree.ipynb +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/scatter.ipynb +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/time.ipynb +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/venn.ipynb +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/gallery/plots/waterfall.ipynb +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/getting_started/connecting_to_data.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/getting_started/data_structures.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/getting_started/installation.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/getting_started/options_guide.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/getting_started/styling_guide.ipynb +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/index.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/javascripts/mathjax.js +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/overrides/home.html +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/overrides/main.html +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/robots.txt +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/scripts/README.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/scripts/regenerate_analysis_modules_svgs.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/docs/stylesheets/extra.css +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/mkdocs.yml +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/SKILL.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/references/analysis.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/references/clv.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/references/configuration.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/references/plotting.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_advanced_customization.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_area.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_bar.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_broken_timeline.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_clv_stats.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_cohort.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_cohort_analysis.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_composite_rank.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_connecting_to_data.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_cross_shop.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_customer_decision_hierarchy.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_filter_and_label.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_filter_by_periods.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_gain_loss.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_haversine_distance.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_heatmap.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_histogram.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_hml_segmentation.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_human_format.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_index.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_label_by_condition.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_line.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_nlr_segmentation.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_options.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_overlapping_periods.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_period_on_period.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_price.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_product_association.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_purchases_per_customer.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_revenue_tree.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_rfm_segmentation.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_scatter.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_segment_transaction_stats.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_set_axis_percent.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_threshold_segmentation.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_time.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_tree_diagram.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_trend_line.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_venn.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/.agents/skills/using-openretailscience/scripts/example_waterfall.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/__init__.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/analysis/__init__.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/analysis/cohort.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/analysis/composite_rank.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/analysis/cross_shop.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/analysis/customer.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/analysis/customer_decision_hierarchy.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/analysis/gain_loss.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/analysis/haversine.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/analysis/product_association.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/analysis/revenue_tree.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/assets/fonts/Poppins-Bold.ttf +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/assets/fonts/Poppins-LightItalic.ttf +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/assets/fonts/Poppins-Medium.ttf +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/assets/fonts/Poppins-Regular.ttf +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/assets/fonts/Poppins-SemiBold.ttf +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/constants.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/core/__init__.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/core/validation.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/experimental/__init__.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/experimental/clv.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/experimental/metrics/__init__.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/experimental/metrics/base.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/experimental/metrics/distribution/__init__.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/experimental/metrics/distribution/acv.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/experimental/metrics/distribution/pct_of_stores.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/options.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/__init__.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/area.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/bar.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/broken_timeline.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/cohort.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/heatmap.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/histogram.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/index.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/line.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/period_on_period.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/price.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/scatter.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/styles/__init__.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/styles/colors.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/styles/font_utils.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/styles/graph_utils.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/styles/styling_helpers.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/styles/trend.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/time.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/tree_diagram.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/venn.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/plots/waterfall.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/py.typed +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/segmentation/__init__.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/segmentation/hml.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/segmentation/nlr.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/segmentation/rfm.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/segmentation/segstats.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/segmentation/threshold.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/utils/__init__.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/utils/date.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/utils/filter_and_label.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/openretailscience/utils/label.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/options_template.toml +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/osv-scanner.toml +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/readme_assets/cross_shop_chart.png +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/readme_assets/days_until_next_purchase.png +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/readme_assets/gain_loss_chart.png +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/readme_assets/logo.png +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/readme_assets/logo.svg +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/readme_assets/new_store_cannibalization.png +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/taplo.toml +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/analysis/test_cohort.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/analysis/test_composite_rank.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/analysis/test_cross_shop.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/analysis/test_customer.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/analysis/test_customer_decision_hierarchy.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/analysis/test_gain_loss.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/analysis/test_haversine.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/analysis/test_product_association.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/analysis/test_revenue_tree.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/core/__init__.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/core/test_validation.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/experimental/metrics/distribution/test_acv.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/experimental/metrics/distribution/test_pct_of_stores.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/experimental/metrics/test_base.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/experimental/test_clv.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/conftest.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/docker/README.md +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/docker/docker-compose.oracle.yml +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/docker/docker-compose.sqlserver.yml +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_clv_stats.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_cohort_analysis.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_composite_rank.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_cross_shop.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_customer_decision_hierarchy.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_date.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_filter_and_label.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_haversine.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_hml_segmentation.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_product_association.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_revenue_tree.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_rfm_segmentation.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_segstats_segmentation.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/integration/test_threshold_segmentation.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/styles/test_chart_chrome.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/styles/test_color.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/styles/test_font_utils.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/styles/test_graph_utils.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/styles/test_styling_helper.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/styles/test_trend.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_area.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_bar.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_broken_timeline.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_cohort.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_heatmap.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_histogram.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_index.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_line.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_period_on_period.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_price.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_scatter.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_styling_options_integration.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_time.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_tree_diagram.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_venn.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/plots/test_waterfall.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/segmentation/test_hml.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/segmentation/test_nlr.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/segmentation/test_rfm.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/segmentation/test_segstats.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/segmentation/test_threshold.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/test_options.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/toml_files/corrupt.toml +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/toml_files/invalid_option.toml +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/toml_files/test_options.toml +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/toml_files/valid.toml +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/utils/test_date.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/utils/test_filter_and_label.py +0 -0
- {openretailscience-0.48.0 → openretailscience-0.48.1}/tests/utils/test_label.py +0 -0
- {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
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
|
@@ -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
|
-
|
|
33
|
-
`pip install -U openretailscience` updates the skill in place
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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,
|
|
67
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
141
|
-
"""Return the
|
|
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
|
-
|
|
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
|
-
|
|
160
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
167
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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 =
|
|
235
|
-
if source_files !=
|
|
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
|
-
|
|
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
|
|
383
|
-
directory instead
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
30
|
+
version = "0.48.1"
|
|
31
31
|
[[project.authors]]
|
|
32
32
|
email = "2493311+mvanwyk@users.noreply.github.com"
|
|
33
33
|
name = "Murray Vanwyk"
|