sorethumb-ml 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sorethumb_ml-0.1.0/.gitignore +51 -0
- sorethumb_ml-0.1.0/CHANGELOG.md +378 -0
- sorethumb_ml-0.1.0/CODE_OF_CONDUCT.md +40 -0
- sorethumb_ml-0.1.0/CONTRIBUTING.md +175 -0
- sorethumb_ml-0.1.0/LICENSE +202 -0
- sorethumb_ml-0.1.0/PKG-INFO +303 -0
- sorethumb_ml-0.1.0/README.md +245 -0
- sorethumb_ml-0.1.0/SECURITY.md +184 -0
- sorethumb_ml-0.1.0/docs/adapting-to-your-data.md +222 -0
- sorethumb_ml-0.1.0/docs/approximations.md +423 -0
- sorethumb_ml-0.1.0/docs/banner.svg +47 -0
- sorethumb_ml-0.1.0/docs/benchmark-results/backtests/composite-agreement-auto-v1.json +7560 -0
- sorethumb_ml-0.1.0/docs/benchmark-results/backtests/composite-agreement-auto-v2.json +7528 -0
- sorethumb_ml-0.1.0/docs/benchmark-results/backtests/composite-agreement-v1.json +6956 -0
- sorethumb_ml-0.1.0/docs/benchmark-results/backtests/composite-agreement-v2.json +7403 -0
- sorethumb_ml-0.1.0/docs/benchmark-results/backtests/discrete-budget-v1.json +7374 -0
- sorethumb_ml-0.1.0/docs/benchmark-results/backtests/discrete-budget-v2.json +7364 -0
- sorethumb_ml-0.1.0/docs/benchmark-results/backtests/discrete-composite-v1.json +6893 -0
- sorethumb_ml-0.1.0/docs/benchmark-results/policy-matrix.json +2186 -0
- sorethumb_ml-0.1.0/docs/benchmark-results/real-backtests/composite-agreement-real-auto-v1.json +1495 -0
- sorethumb_ml-0.1.0/docs/benchmark-results/real-backtests/composite-agreement-real-v1.json +1482 -0
- sorethumb_ml-0.1.0/docs/benchmark-results/scale.json +474 -0
- sorethumb_ml-0.1.0/docs/benchmarks.md +498 -0
- sorethumb_ml-0.1.0/docs/check_readme_snippets.py +245 -0
- sorethumb_ml-0.1.0/docs/cli_reference.md +968 -0
- sorethumb_ml-0.1.0/docs/configuration-examples.md +722 -0
- sorethumb_ml-0.1.0/docs/configuration.md +229 -0
- sorethumb_ml-0.1.0/docs/ensemble-policy.md +613 -0
- sorethumb_ml-0.1.0/docs/example-runs.md +377 -0
- sorethumb_ml-0.1.0/docs/explanations.md +376 -0
- sorethumb_ml-0.1.0/docs/generate_config_docs.py +383 -0
- sorethumb_ml-0.1.0/docs/getting-started.md +222 -0
- sorethumb_ml-0.1.0/docs/index.md +72 -0
- sorethumb_ml-0.1.0/docs/limitations.md +91 -0
- sorethumb_ml-0.1.0/docs/logo.svg +8 -0
- sorethumb_ml-0.1.0/docs/models.md +432 -0
- sorethumb_ml-0.1.0/docs/platforms.md +29 -0
- sorethumb_ml-0.1.0/docs/releasing.md +80 -0
- sorethumb_ml-0.1.0/docs/scale.md +43 -0
- sorethumb_ml-0.1.0/docs/stability.md +50 -0
- sorethumb_ml-0.1.0/docs/trends.md +163 -0
- sorethumb_ml-0.1.0/pyproject.toml +285 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/__init__.py +95 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/_atomic.py +180 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/_determinism.py +24 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/_pipeline.py +3183 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/analysis/__init__.py +1 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/analysis/contrast.py +138 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/cli.py +3594 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/config.py +744 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/detectors/__init__.py +80 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/detectors/_hyperparams.py +142 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/detectors/_protocol.py +205 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/detectors/ecod.py +110 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/detectors/hbos.py +153 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/detectors/isolation_forest.py +89 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/detectors/kmeans_distance.py +315 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/detectors/lof.py +96 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/detectors/one_class_svm.py +127 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/errors.py +378 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/evaluate/__init__.py +1 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/evaluate/backtest.py +517 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/evaluate/benchmark.py +667 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/evaluate/budgets.py +155 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/evaluate/candidates.py +121 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/evaluate/metrics.py +281 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/evaluate/pipeline_benchmark.py +1222 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/evaluate/real_backtest.py +269 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/evaluate/scale_benchmark.py +617 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/evaluate/scenarios.py +504 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/evaluate/user_backtest.py +867 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/explain/__init__.py +30 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/explain/blend.py +155 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/explain/centroid.py +84 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/explain/gradient.py +370 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/explain/native.py +73 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/explain/project.py +244 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/explain/record.py +327 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/explain/reference.py +273 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/explain/shap_tree.py +178 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/features/__init__.py +1 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/features/build.py +522 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/features/correlate.py +159 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/features/encode.py +259 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/features/reduce.py +120 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/features/scale.py +156 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/features/space.py +35 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/history/__init__.py +1 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/history/anchor.py +227 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/history/ledger.py +215 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/history/period_stats.py +267 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/history/periods.py +161 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/history/trend.py +212 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/history/windows.py +596 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/io/__init__.py +1 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/io/fingerprint.py +98 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/io/nested.py +133 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/io/readers.py +302 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/io/source.py +771 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/io/toml_write.py +103 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/io/uri.py +176 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/logging.py +18 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/profiling/__init__.py +1 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/profiling/classify.py +369 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/profiling/missing.py +31 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/profiling/plan.py +559 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/profiling/profile.py +149 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/profiling/temporal.py +348 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/py.typed +0 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/report/__init__.py +1 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/report/csv.py +79 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/report/html.py +811 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/scoring/__init__.py +6 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/scoring/_finite.py +22 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/scoring/calibrate.py +246 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/scoring/combine.py +706 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/scoring/policies.py +144 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/store/__init__.py +6 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/store/db.py +1224 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/store/identifiers.py +30 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/store/migrations/001_initial.sql +95 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/store/migrations/002_totals_config_hash.sql +33 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/store/migrations/003_artifact_run_id.sql +19 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/store/migrations/004_run_source.sql +11 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/store/migrations/005_dataset_identity.sql +44 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/store/migrations/006_period_execution.sql +33 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/store/migrations/007_run_group_warnings.sql +11 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/store/migrations/008_query_indexes.sql +40 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/store/migrations/009_source_digest.sql +21 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/store/migrations/010_run_ensemble_policy.sql +9 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/store/migrations/011_policy_evidence.sql +38 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/store/migrations/012_explain_status.sql +15 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/store/migrations/013_period_stats.sql +20 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/store/migrations/__init__.py +1 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/store/models.py +497 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/store/results.py +305 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/store/storage.py +131 -0
- sorethumb_ml-0.1.0/src/sorethumb_ml/store/workspace.py +335 -0
- sorethumb_ml-0.1.0/tests/README.md +65 -0
- sorethumb_ml-0.1.0/tests/__init__.py +0 -0
- sorethumb_ml-0.1.0/tests/benchmark/__init__.py +1 -0
- sorethumb_ml-0.1.0/tests/benchmark/budgets-archive/v2.toml +170 -0
- sorethumb_ml-0.1.0/tests/benchmark/budgets-archive/v3.toml +191 -0
- sorethumb_ml-0.1.0/tests/benchmark/budgets.toml +204 -0
- sorethumb_ml-0.1.0/tests/benchmark/preregistrations/composite-agreement-auto-v1.toml +27 -0
- sorethumb_ml-0.1.0/tests/benchmark/preregistrations/composite-agreement-auto-v2.toml +33 -0
- sorethumb_ml-0.1.0/tests/benchmark/preregistrations/composite-agreement-v1.toml +26 -0
- sorethumb_ml-0.1.0/tests/benchmark/preregistrations/composite-agreement-v2.toml +27 -0
- sorethumb_ml-0.1.0/tests/benchmark/preregistrations/discrete-budget-v1.toml +24 -0
- sorethumb_ml-0.1.0/tests/benchmark/preregistrations/discrete-budget-v2.toml +30 -0
- sorethumb_ml-0.1.0/tests/benchmark/preregistrations/discrete-composite-v1.toml +24 -0
- sorethumb_ml-0.1.0/tests/benchmark/preregistrations/real/composite-agreement-real-auto-v1.toml +23 -0
- sorethumb_ml-0.1.0/tests/benchmark/preregistrations/real/composite-agreement-real-v1.toml +24 -0
- sorethumb_ml-0.1.0/tests/benchmark/test_accuracy_floors.py +97 -0
- sorethumb_ml-0.1.0/tests/benchmark/test_benchmark_harness.py +225 -0
- sorethumb_ml-0.1.0/tests/benchmark/test_memory_budget.py +230 -0
- sorethumb_ml-0.1.0/tests/benchmark/test_pipeline_accuracy_floors.py +271 -0
- sorethumb_ml-0.1.0/tests/benchmark/test_pipeline_benchmark_harness.py +213 -0
- sorethumb_ml-0.1.0/tests/benchmark/test_policy_budgets.py +62 -0
- sorethumb_ml-0.1.0/tests/benchmark/test_real_dataset_smoke.py +90 -0
- sorethumb_ml-0.1.0/tests/benchmark/test_scale_benchmark_smoke.py +63 -0
- sorethumb_ml-0.1.0/tests/conftest.py +88 -0
- sorethumb_ml-0.1.0/tests/contract/__init__.py +0 -0
- sorethumb_ml-0.1.0/tests/contract/test_cli.py +1126 -0
- sorethumb_ml-0.1.0/tests/contract/test_cli_exit_codes.py +364 -0
- sorethumb_ml-0.1.0/tests/contract/test_cli_tracebacks.py +104 -0
- sorethumb_ml-0.1.0/tests/contract/test_config_hash_consistency.py +115 -0
- sorethumb_ml-0.1.0/tests/contract/test_database_schema.py +784 -0
- sorethumb_ml-0.1.0/tests/contract/test_detector_plugins.py +729 -0
- sorethumb_ml-0.1.0/tests/contract/test_feature_plan_compat.py +131 -0
- sorethumb_ml-0.1.0/tests/contract/test_model_manifests.py +327 -0
- sorethumb_ml-0.1.0/tests/contract/test_packaging.py +71 -0
- sorethumb_ml-0.1.0/tests/contract/test_public_api.py +298 -0
- sorethumb_ml-0.1.0/tests/factories/__init__.py +8 -0
- sorethumb_ml-0.1.0/tests/factories/benchmark_rows.py +34 -0
- sorethumb_ml-0.1.0/tests/factories/configs.py +74 -0
- sorethumb_ml-0.1.0/tests/factories/detectors.py +106 -0
- sorethumb_ml-0.1.0/tests/factories/frames.py +154 -0
- sorethumb_ml-0.1.0/tests/factories/golden.py +51 -0
- sorethumb_ml-0.1.0/tests/factories/hypothesis_profiles.py +36 -0
- sorethumb_ml-0.1.0/tests/factories/pipeline_benchmark_rows.py +35 -0
- sorethumb_ml-0.1.0/tests/factories/policy_evidence.py +78 -0
- sorethumb_ml-0.1.0/tests/factories/results.py +25 -0
- sorethumb_ml-0.1.0/tests/factories/runs.py +54 -0
- sorethumb_ml-0.1.0/tests/factories/source_uris.py +79 -0
- sorethumb_ml-0.1.0/tests/factories/workspaces.py +35 -0
- sorethumb_ml-0.1.0/tests/golden/cli_config_schema.json +742 -0
- sorethumb_ml-0.1.0/tests/golden/cli_detectors.json +32 -0
- sorethumb_ml-0.1.0/tests/golden/ensemble_policies.json +196 -0
- sorethumb_ml-0.1.0/tests/golden/report_index.html +60 -0
- sorethumb_ml-0.1.0/tests/golden/report_reason_detail.html +1 -0
- sorethumb_ml-0.1.0/tests/integration/__init__.py +0 -0
- sorethumb_ml-0.1.0/tests/integration/test_anchor_decomposition.py +284 -0
- sorethumb_ml-0.1.0/tests/integration/test_attribution_failure.py +161 -0
- sorethumb_ml-0.1.0/tests/integration/test_cli.py +1068 -0
- sorethumb_ml-0.1.0/tests/integration/test_cli_file_handles.py +179 -0
- sorethumb_ml-0.1.0/tests/integration/test_cli_home_expansion.py +54 -0
- sorethumb_ml-0.1.0/tests/integration/test_cli_markup.py +123 -0
- sorethumb_ml-0.1.0/tests/integration/test_cli_output_encoding.py +98 -0
- sorethumb_ml-0.1.0/tests/integration/test_cli_stdin.py +44 -0
- sorethumb_ml-0.1.0/tests/integration/test_cli_storage_warning.py +44 -0
- sorethumb_ml-0.1.0/tests/integration/test_cli_zero_config.py +122 -0
- sorethumb_ml-0.1.0/tests/integration/test_config_wiring.py +503 -0
- sorethumb_ml-0.1.0/tests/integration/test_ensemble_policy_provenance.py +110 -0
- sorethumb_ml-0.1.0/tests/integration/test_explain_isolation.py +307 -0
- sorethumb_ml-0.1.0/tests/integration/test_explain_record.py +245 -0
- sorethumb_ml-0.1.0/tests/integration/test_explain_reference.py +173 -0
- sorethumb_ml-0.1.0/tests/integration/test_explain_without_shap.py +93 -0
- sorethumb_ml-0.1.0/tests/integration/test_feature_matrix_lifetime.py +63 -0
- sorethumb_ml-0.1.0/tests/integration/test_files_in_use.py +183 -0
- sorethumb_ml-0.1.0/tests/integration/test_first_run_guidance.py +222 -0
- sorethumb_ml-0.1.0/tests/integration/test_group_selection.py +95 -0
- sorethumb_ml-0.1.0/tests/integration/test_history.py +980 -0
- sorethumb_ml-0.1.0/tests/integration/test_id_column_identity.py +239 -0
- sorethumb_ml-0.1.0/tests/integration/test_include_only.py +38 -0
- sorethumb_ml-0.1.0/tests/integration/test_non_utf8_source.py +53 -0
- sorethumb_ml-0.1.0/tests/integration/test_optional_packages.py +133 -0
- sorethumb_ml-0.1.0/tests/integration/test_period_stats_pipeline.py +146 -0
- sorethumb_ml-0.1.0/tests/integration/test_pipeline_detection.py +203 -0
- sorethumb_ml-0.1.0/tests/integration/test_pipeline_explain.py +368 -0
- sorethumb_ml-0.1.0/tests/integration/test_pipeline_resume.py +182 -0
- sorethumb_ml-0.1.0/tests/integration/test_policy_evidence_gate.py +582 -0
- sorethumb_ml-0.1.0/tests/integration/test_rank_ties.py +150 -0
- sorethumb_ml-0.1.0/tests/integration/test_report.py +581 -0
- sorethumb_ml-0.1.0/tests/integration/test_report_reason_detail.py +168 -0
- sorethumb_ml-0.1.0/tests/integration/test_row_identity.py +224 -0
- sorethumb_ml-0.1.0/tests/integration/test_scale_benchmark_inprocess.py +86 -0
- sorethumb_ml-0.1.0/tests/integration/test_score_forward.py +521 -0
- sorethumb_ml-0.1.0/tests/integration/test_source_provenance.py +570 -0
- sorethumb_ml-0.1.0/tests/integration/test_store_workflows.py +510 -0
- sorethumb_ml-0.1.0/tests/integration/test_temporal_columns.py +231 -0
- sorethumb_ml-0.1.0/tests/integration/test_trend_report.py +251 -0
- sorethumb_ml-0.1.0/tests/integration/test_trend_windows.py +315 -0
- sorethumb_ml-0.1.0/tests/integration/test_uninformative_boundary_pipeline.py +134 -0
- sorethumb_ml-0.1.0/tests/integration/test_workspace_reset_portability.py +119 -0
- sorethumb_ml-0.1.0/tests/property/__init__.py +1 -0
- sorethumb_ml-0.1.0/tests/property/test_atomic_write_properties.py +46 -0
- sorethumb_ml-0.1.0/tests/property/test_calibration_properties.py +71 -0
- sorethumb_ml-0.1.0/tests/property/test_detector_properties.py +74 -0
- sorethumb_ml-0.1.0/tests/property/test_feature_properties.py +85 -0
- sorethumb_ml-0.1.0/tests/property/test_grouping_properties.py +49 -0
- sorethumb_ml-0.1.0/tests/property/test_profiling_properties.py +203 -0
- sorethumb_ml-0.1.0/tests/repo_check/__init__.py +0 -0
- sorethumb_ml-0.1.0/tests/repo_check/test_changelog_commands.py +88 -0
- sorethumb_ml-0.1.0/tests/repo_check/test_ci_platforms.py +39 -0
- sorethumb_ml-0.1.0/tests/repo_check/test_dependency_floors.py +92 -0
- sorethumb_ml-0.1.0/tests/repo_check/test_dist_artifacts.py +477 -0
- sorethumb_ml-0.1.0/tests/repo_check/test_docs_checks.py +126 -0
- sorethumb_ml-0.1.0/tests/repo_check/test_license.py +78 -0
- sorethumb_ml-0.1.0/tests/repo_check/test_line_endings.py +46 -0
- sorethumb_ml-0.1.0/tests/repo_check/test_newest_dependencies.py +75 -0
- sorethumb_ml-0.1.0/tests/repo_check/test_no_private_references.py +119 -0
- sorethumb_ml-0.1.0/tests/repo_check/test_no_review_documents.py +82 -0
- sorethumb_ml-0.1.0/tests/repo_check/test_platform_claims.py +52 -0
- sorethumb_ml-0.1.0/tests/repo_check/test_policy_contacts.py +48 -0
- sorethumb_ml-0.1.0/tests/repo_check/test_python_versions.py +73 -0
- sorethumb_ml-0.1.0/tests/repo_check/test_readme_structure.py +132 -0
- sorethumb_ml-0.1.0/tests/repo_check/test_release_gate.py +153 -0
- sorethumb_ml-0.1.0/tests/repo_check/test_shell_neutral_commands.py +38 -0
- sorethumb_ml-0.1.0/tests/repo_check/test_usage_guidance.py +68 -0
- sorethumb_ml-0.1.0/tests/repo_check/test_workspace_docs.py +147 -0
- sorethumb_ml-0.1.0/tests/scripts/__init__.py +0 -0
- sorethumb_ml-0.1.0/tests/scripts/test_check_installed_dist.py +192 -0
- sorethumb_ml-0.1.0/tests/scripts/test_check_release_state.py +306 -0
- sorethumb_ml-0.1.0/tests/scripts/test_rehearsal_version.py +54 -0
- sorethumb_ml-0.1.0/tests/scripts/test_run_benchmark.py +136 -0
- sorethumb_ml-0.1.0/tests/scripts/test_summarise_junit.py +99 -0
- sorethumb_ml-0.1.0/tests/scripts/validation/__init__.py +0 -0
- sorethumb_ml-0.1.0/tests/scripts/validation/test_data.py +81 -0
- sorethumb_ml-0.1.0/tests/scripts/validation/test_identity.py +40 -0
- sorethumb_ml-0.1.0/tests/scripts/validation/test_planner.py +213 -0
- sorethumb_ml-0.1.0/tests/scripts/validation/test_runner_integration.py +175 -0
- sorethumb_ml-0.1.0/tests/scripts/validation/test_schema.py +78 -0
- sorethumb_ml-0.1.0/tests/synth/__init__.py +115 -0
- sorethumb_ml-0.1.0/tests/unit/__init__.py +0 -0
- sorethumb_ml-0.1.0/tests/unit/analysis/__init__.py +0 -0
- sorethumb_ml-0.1.0/tests/unit/analysis/test_contrast_scale.py +109 -0
- sorethumb_ml-0.1.0/tests/unit/config/__init__.py +0 -0
- sorethumb_ml-0.1.0/tests/unit/config/test_config.py +79 -0
- sorethumb_ml-0.1.0/tests/unit/detectors/__init__.py +0 -0
- sorethumb_ml-0.1.0/tests/unit/detectors/test_detectors.py +800 -0
- sorethumb_ml-0.1.0/tests/unit/detectors/test_kmeans_determinism.py +51 -0
- sorethumb_ml-0.1.0/tests/unit/evaluation/__init__.py +0 -0
- sorethumb_ml-0.1.0/tests/unit/evaluation/test_backtest.py +376 -0
- sorethumb_ml-0.1.0/tests/unit/evaluation/test_budgets.py +125 -0
- sorethumb_ml-0.1.0/tests/unit/evaluation/test_candidates.py +72 -0
- sorethumb_ml-0.1.0/tests/unit/evaluation/test_evaluate.py +579 -0
- sorethumb_ml-0.1.0/tests/unit/evaluation/test_peak_memory.py +38 -0
- sorethumb_ml-0.1.0/tests/unit/evaluation/test_pipeline_benchmark_cells.py +159 -0
- sorethumb_ml-0.1.0/tests/unit/evaluation/test_pipeline_benchmark_matrix.py +184 -0
- sorethumb_ml-0.1.0/tests/unit/evaluation/test_real_backtest.py +133 -0
- sorethumb_ml-0.1.0/tests/unit/evaluation/test_scale_benchmark.py +190 -0
- sorethumb_ml-0.1.0/tests/unit/evaluation/test_scenarios.py +198 -0
- sorethumb_ml-0.1.0/tests/unit/evaluation/test_user_backtest.py +124 -0
- sorethumb_ml-0.1.0/tests/unit/explain/__init__.py +0 -0
- sorethumb_ml-0.1.0/tests/unit/explain/test_attribution_reference.py +209 -0
- sorethumb_ml-0.1.0/tests/unit/explain/test_explain.py +736 -0
- sorethumb_ml-0.1.0/tests/unit/explain/test_record.py +351 -0
- sorethumb_ml-0.1.0/tests/unit/explain/test_reference.py +353 -0
- sorethumb_ml-0.1.0/tests/unit/features/__init__.py +0 -0
- sorethumb_ml-0.1.0/tests/unit/features/test_features.py +1226 -0
- sorethumb_ml-0.1.0/tests/unit/features/test_missing_and_nonfinite.py +243 -0
- sorethumb_ml-0.1.0/tests/unit/history/__init__.py +0 -0
- sorethumb_ml-0.1.0/tests/unit/history/test_anchor_rule.py +74 -0
- sorethumb_ml-0.1.0/tests/unit/history/test_period_stats.py +217 -0
- sorethumb_ml-0.1.0/tests/unit/history/test_periods.py +177 -0
- sorethumb_ml-0.1.0/tests/unit/io/__init__.py +0 -0
- sorethumb_ml-0.1.0/tests/unit/io/test_io.py +2085 -0
- sorethumb_ml-0.1.0/tests/unit/io/test_source_uri.py +162 -0
- sorethumb_ml-0.1.0/tests/unit/io/test_toml_write.py +112 -0
- sorethumb_ml-0.1.0/tests/unit/io/test_uri.py +128 -0
- sorethumb_ml-0.1.0/tests/unit/pipeline/__init__.py +0 -0
- sorethumb_ml-0.1.0/tests/unit/pipeline/test_grouping.py +146 -0
- sorethumb_ml-0.1.0/tests/unit/pipeline/test_ranking.py +160 -0
- sorethumb_ml-0.1.0/tests/unit/profiling/__init__.py +0 -0
- sorethumb_ml-0.1.0/tests/unit/profiling/test_include_only.py +67 -0
- sorethumb_ml-0.1.0/tests/unit/profiling/test_profiling.py +813 -0
- sorethumb_ml-0.1.0/tests/unit/profiling/test_temporal.py +365 -0
- sorethumb_ml-0.1.0/tests/unit/report/__init__.py +0 -0
- sorethumb_ml-0.1.0/tests/unit/report/test_csv_bom.py +41 -0
- sorethumb_ml-0.1.0/tests/unit/scoring/__init__.py +0 -0
- sorethumb_ml-0.1.0/tests/unit/scoring/test_policies.py +136 -0
- sorethumb_ml-0.1.0/tests/unit/scoring/test_scoring.py +1014 -0
- sorethumb_ml-0.1.0/tests/unit/scoring/test_uninformative_boundary.py +128 -0
- sorethumb_ml-0.1.0/tests/unit/store/__init__.py +0 -0
- sorethumb_ml-0.1.0/tests/unit/store/test_storage_detection.py +81 -0
- sorethumb_ml-0.1.0/tests/unit/store/test_store_helpers.py +81 -0
- sorethumb_ml-0.1.0/tests/unit/test_atomic_durability.py +232 -0
- sorethumb_ml-0.1.0/tests/unit/test_cli_guards.py +62 -0
- sorethumb_ml-0.1.0/tests/unit/test_cli_output_streams.py +87 -0
- sorethumb_ml-0.1.0/tests/unit/test_file_in_use.py +125 -0
- sorethumb_ml-0.1.0/tests/unit/test_log_rollover.py +97 -0
- sorethumb_ml-0.1.0/tests/unit/test_path_length.py +80 -0
- sorethumb_ml-0.1.0/tests/unit/test_smoke.py +90 -0
- sorethumb_ml-0.1.0/tests/unit/test_warning_records.py +81 -0
- sorethumb_ml-0.1.0/uv.lock +2689 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
data-samples/
|
|
2
|
+
work-dir/
|
|
3
|
+
.venv/
|
|
4
|
+
__pycache__/
|
|
5
|
+
*.py[cod]
|
|
6
|
+
*.egg-info/
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
.mypy_cache/
|
|
10
|
+
.ruff_cache/
|
|
11
|
+
.pytest_cache/
|
|
12
|
+
.hypothesis/
|
|
13
|
+
.claude/
|
|
14
|
+
.coverage
|
|
15
|
+
coverage.xml
|
|
16
|
+
htmlcov/
|
|
17
|
+
*.log
|
|
18
|
+
*.joblib
|
|
19
|
+
# Default sorethumb workspace and its artefacts. Anchored: a
|
|
20
|
+
# workspace explicitly created at "." (the previous default, or via
|
|
21
|
+
# `--workdir .`) produces these same top-level names, and unanchored
|
|
22
|
+
# patterns would also match same-named directories anywhere else in the tree.
|
|
23
|
+
/sorethumb-workspace/
|
|
24
|
+
/sorethumb.db
|
|
25
|
+
/models/
|
|
26
|
+
/results/
|
|
27
|
+
/reports/
|
|
28
|
+
/logs/
|
|
29
|
+
/cache/
|
|
30
|
+
.sorethumb_benchmark_cache/
|
|
31
|
+
benchmark_results/
|
|
32
|
+
/validation/
|
|
33
|
+
/tmp/
|
|
34
|
+
.idea/
|
|
35
|
+
*.iml
|
|
36
|
+
prompts/
|
|
37
|
+
PLAN.md
|
|
38
|
+
|
|
39
|
+
# Generated or hand-written review documents (code reviews, audits of the repository,
|
|
40
|
+
# pre-release critiques). They are working material about the project, not part of it, and
|
|
41
|
+
# were never meant to be published; the [Rr][Ee]... classes make the match case-insensitive.
|
|
42
|
+
/reviews/
|
|
43
|
+
*[Rr][Ee][Vv][Ii][Ee][Ww]*.md
|
|
44
|
+
*[Rr][Ee][Vv][Ii][Ee][Ww]*.docx
|
|
45
|
+
*[Rr][Ee][Vv][Ii][Ee][Ww]*.pdf
|
|
46
|
+
|
|
47
|
+
# Delete-pending placeholders an SMB/CIFS share leaves while a deleted file is still open
|
|
48
|
+
.smbdelete*
|
|
49
|
+
|
|
50
|
+
# scripts/run_scale_benchmark.py default scratch directory
|
|
51
|
+
scale_benchmark_scratch/
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
5
|
+
Versioning: [Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-10-20
|
|
10
|
+
|
|
11
|
+
First release.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **Names**: install the `sorethumb-ml` distribution (`pip install sorethumb-ml`;
|
|
16
|
+
`sorethumb` on PyPI is an unrelated package), import `sorethumb_ml`, and run
|
|
17
|
+
the `sorethumb` command. Third-party detectors register through the
|
|
18
|
+
`sorethumb_ml.detectors` entry-point group. Python 3.11–3.14 on Linux,
|
|
19
|
+
macOS and Windows (x64), each tested in CI; `requires-python` is `>=3.11,<3.15`,
|
|
20
|
+
so pip will not install on a CPython that has not been tested. The package ships
|
|
21
|
+
`py.typed`.
|
|
22
|
+
- **Pipeline**: profile every column, encode, impute and derive features, fit
|
|
23
|
+
an ensemble of detectors, percentile-calibrate their scores, combine them into
|
|
24
|
+
a ranking and a flag decision, and explain each flagged row in terms of the
|
|
25
|
+
original columns. It ranks a review shortlist: `scoring.contamination` is a
|
|
26
|
+
review budget, not an estimate of how many anomalies the data contains, and
|
|
27
|
+
each run shows the per-detector flag rates alongside the flagged count. How many
|
|
28
|
+
rows are flagged depends on the ensemble policy as well as on `contamination`.
|
|
29
|
+
By default (`intersection` at `"auto"`) it is whatever every detector's own
|
|
30
|
+
boundary agrees on, not a number you choose.
|
|
31
|
+
- **Input data**: a file with a single column, or with every column as text
|
|
32
|
+
(all-categorical data), is valid input. A CSV/TSV is refused as
|
|
33
|
+
mis-delimited only when another delimiter character appears in the header
|
|
34
|
+
and the same number of times in most data rows; an all-text schema is
|
|
35
|
+
re-scanned (up to 1,000,000 rows) and refused, naming
|
|
36
|
+
`source.read_options['infer_schema_length']`, if a numeric column was hidden
|
|
37
|
+
by blank leading rows. A source path that is a directory is rejected with a
|
|
38
|
+
clear message. A column is never dropped for having few distinct values
|
|
39
|
+
(only a constant one is), since a rare level of a low-cardinality column can
|
|
40
|
+
be exactly the anomaly; `profiling.near_constant_distinct` can opt in to
|
|
41
|
+
dropping them. `columns.include_only` restricts
|
|
42
|
+
the features to the listed columns plus `time_column`, `group_by`,
|
|
43
|
+
`id_column` and `reference_column`; a listed name not in the dataset is a
|
|
44
|
+
`ConfigError`.
|
|
45
|
+
- **Categorical columns**: up to `features.one_hot_max_cardinality` levels are
|
|
46
|
+
one-hot encoded, more are frequency-encoded. One-hot dummies are not scaled
|
|
47
|
+
like numeric columns: two rows that differ only in one category are
|
|
48
|
+
`features.categorical_distance` apart (default 0.5, in the interquartile-range
|
|
49
|
+
units numeric features use), so a categorical column of noise cannot dominate
|
|
50
|
+
the distance-based detectors (KMeans, One-Class SVM, LOF) while a rare level
|
|
51
|
+
still stands out. Isolation Forest is unaffected.
|
|
52
|
+
- **Dates and times stored as text**: a text column holding ISO dates or
|
|
53
|
+
datetimes, a time of day, or a day/month/year date is parsed into a real
|
|
54
|
+
date/time before profiling, so it gets derived features (`hour`,
|
|
55
|
+
`dayofweek`, ...) and a text `columns.time_column` filters period windows by
|
|
56
|
+
date rather than as a string. Every date/time column gets derived features,
|
|
57
|
+
not only `time_column`. `group_by`, `id_column` and `reference_column`
|
|
58
|
+
columns, and columns an `ignore` pattern or `include_only` excludes, are
|
|
59
|
+
left as text. An ambiguous day/month order (every day and month <= 12) is
|
|
60
|
+
never guessed: the column stays text, with a warning that `run.strict`
|
|
61
|
+
does not turn into an error. `columns.datetime_formats` forces a column's
|
|
62
|
+
format, or `"epoch_s"`/`"epoch_ms"` for a Unix-timestamp integer or float
|
|
63
|
+
column. A value that fails to parse becomes missing. `Duration` columns
|
|
64
|
+
become a numeric seconds feature. `run`, `score --from-run`, `inspect`,
|
|
65
|
+
`explain-plan` and the Python API's `build_feature_plan`/`apply_feature_plan`
|
|
66
|
+
all parse the same way, and the parse is stored with the run so
|
|
67
|
+
`score --from-run` replays it exactly.
|
|
68
|
+
- **Detectors**: `isolation_forest`, `kmeans_distance` (CBLOF-style) and
|
|
69
|
+
`one_class_svm` form the default ensemble; `ecod`, `hbos` and `lof` are also
|
|
70
|
+
available. A detector's `extra_params` forwards extra constructor keywords to
|
|
71
|
+
the underlying scikit-learn estimator. On discrete features (for example
|
|
72
|
+
all-categorical data: at most 50 distinct feature rows, and at most half
|
|
73
|
+
as many as there are rows), `kmeans_distance` gives each distinct row its
|
|
74
|
+
own cluster so a rare category is flagged; its `k_max` defaults to automatic
|
|
75
|
+
(8 on continuous data), and an explicitly set `k_max` is a hard cap.
|
|
76
|
+
- **Detector output contract**: `score_samples` must return a finite 1-D numeric
|
|
77
|
+
`ndarray` with one value per input row, and `natural_flag` a 1-D `bool`
|
|
78
|
+
`ndarray` of the same length. Every call is checked immediately, for a
|
|
79
|
+
freshly fitted detector, a `run.reuse_models` model and a `score --from-run`
|
|
80
|
+
model alike. A violation, or an exception raised inside a plugin's method,
|
|
81
|
+
fails the group with a `DetectorError` naming the detector, the failing
|
|
82
|
+
condition, the expected contract and the actual dtype/shape, and the model is
|
|
83
|
+
not saved. `ScoreEnsemble.combine()` and `Calibrator.fit()`/`transform()` also
|
|
84
|
+
reject malformed or non-finite input themselves. See
|
|
85
|
+
[Writing a detector plugin](docs/models.md#writing-a-detector-plugin).
|
|
86
|
+
- **Ensemble combination**: `intersection` (the default: every configured
|
|
87
|
+
detector must flag a row; see Known limitations), `union`, and `composite`
|
|
88
|
+
(a weighted average with a guard that drops an anti-correlated member, and
|
|
89
|
+
optional agreement-based weighting). Each is a versioned *policy*
|
|
90
|
+
(`intersection@1`, `union@1`, `composite-equal@1`, `composite-agreement@1`,
|
|
91
|
+
`composite-manual@1`) recorded on every run (`run --json`, `show`); a change
|
|
92
|
+
to what a policy flags is a new version. The default is a deliberate design
|
|
93
|
+
decision and is not changing for now; see "Where things stand" in
|
|
94
|
+
`docs/ensemble-policy.md` for the evidence and its limits.
|
|
95
|
+
- **Explanations**: up to `explain.top_n` reasons per flagged row
|
|
96
|
+
(`reason_1`, `reason_2`, ...), labelled by `attribution_kind`: `exact` for ECOD
|
|
97
|
+
and HBOS, `model_specific` for Isolation Forest (interventional TreeSHAP, with
|
|
98
|
+
the `explain` extra) and `heuristic` otherwise (centroid distance, input
|
|
99
|
+
gradient, KernelSHAP, or the reference-scaled deviation used where a score is
|
|
100
|
+
flat). See `docs/explanations.md`.
|
|
101
|
+
- **Detection first.** Each group's detection output (`anomalies.parquet`:
|
|
102
|
+
scores, flags, ranks) is written, and its SHA-256 recorded as
|
|
103
|
+
`detection_digest`, before any explanation is computed. Nothing the
|
|
104
|
+
explanation stage does, including failing, changes which rows are flagged.
|
|
105
|
+
Detection always uses `features.dtype`, whatever the explain settings.
|
|
106
|
+
- **Explained against normal rows.** Every method measures a flagged row
|
|
107
|
+
against a reference of rows no detector flags (falling back to the less
|
|
108
|
+
anomalous half of the unflagged rows, and to no explanation when neither has
|
|
109
|
+
10 rows), never against the anomalies. A fitting run saves the reference
|
|
110
|
+
summary (`models/<run>/<group>/reference.json`, plain JSON), and
|
|
111
|
+
`score --from-run` explains against it.
|
|
112
|
+
- **Per-row status.** Each flagged row has an `explain_status` (`ok`,
|
|
113
|
+
`partial`, `unavailable`) and an `explain_issue` saying what is missing; an
|
|
114
|
+
unavailable row has no `reason_<k>`. Each group records `explain_status`,
|
|
115
|
+
`n_explained` and `n_explain_unavailable` (`run --json`, `show`).
|
|
116
|
+
- **The record behind each reason** (`attributions.parquet`): original field,
|
|
117
|
+
raw value, reference value and direction, signed contribution (positive
|
|
118
|
+
toward anomaly) and the magnitude reasons are ranked by, each detector's
|
|
119
|
+
method, weight and share, the transformed features behind the field, every
|
|
120
|
+
approximation caveat (`approximation_flags`), `explainer_version`
|
|
121
|
+
(`explain@2`) and `reference_id`. Shown by `sorethumb anomalies --detail` and
|
|
122
|
+
the HTML report's "Explanation detail" section; `index.json` carries it under
|
|
123
|
+
`attributions`.
|
|
124
|
+
- **Failures are reported, not hidden.** An explanation failure is a warning
|
|
125
|
+
with a stable code (`explain.stage_failed`, `explain.backend_failed`,
|
|
126
|
+
`explain.no_signal`, `explain.reference_unavailable`, ...), never a group
|
|
127
|
+
failure. A failed explanation stage makes the run exit `partial`, as does any
|
|
128
|
+
explanation warning under `run.strict`.
|
|
129
|
+
- **Structured warnings**: every warning carries a stable `code`, its stage and,
|
|
130
|
+
where they apply, the detector and rows affected. They are stored with each
|
|
131
|
+
group, shown with their codes in the run summary, `show` and the report, and
|
|
132
|
+
listed in `run --json` (`warnings`).
|
|
133
|
+
- **CLI**: `init`, `inspect`, `run`, `score`, `report`, `backfill`, `history`,
|
|
134
|
+
`backtest`, `evidence`, `runs`, `show`, `anomalies`, `explain-plan`, `detectors`,
|
|
135
|
+
`config` (`check`, `schema`, `show`) and `workspace` (`ls`, `du`, `prune`,
|
|
136
|
+
`vacuum`, `migrate`, `reset`). `sorethumb run data.csv` works with no config file, and
|
|
137
|
+
the read-only commands after it need none either. Runs are resumable and
|
|
138
|
+
idempotent, tracked in a SQLite workspace. Exit codes are stable (`0` ok,
|
|
139
|
+
`1` runtime failure, `2` pre-flight rejection, `3` not found, `4` partial
|
|
140
|
+
success) and every `--json` command emits one document per outcome; see
|
|
141
|
+
`docs/cli_reference.md`. A group is selected by its label (`run --only-group`,
|
|
142
|
+
`show --group`, `history --group`; the last two also take its key).
|
|
143
|
+
- **Reproducible fits**: KMeans (the detector, its choice of k, and the explanation
|
|
144
|
+
reference) fits on one OpenMP thread, so the same data and seed give the same
|
|
145
|
+
centroids every run; scikit-learn's parallel partial sums otherwise varied by a
|
|
146
|
+
rounding step, which on data with many duplicate rows moved whole tied blocks across
|
|
147
|
+
the KMeans flag fence (up to 90 rows flagged more or fewer between identical KDD Cup
|
|
148
|
+
1999 runs). About 18% more KMeans fit time at its 200,000-row training cap.
|
|
149
|
+
`threadpoolctl` is now a declared dependency. The real-data backtest pins the KDD
|
|
150
|
+
SA subset's random draw (`random_state=0`), which was different on every load.
|
|
151
|
+
- **Uninformative detector boundaries**: at `scoring.contamination = "auto"`, a
|
|
152
|
+
detector whose own boundary flagged more than half the rows it was fitted on is left
|
|
153
|
+
out of the flag vote (the intersection or union, and the composite's
|
|
154
|
+
median-of-rates cut), with an `UninformativeBoundaryWarning`
|
|
155
|
+
(`scoring.boundary_uninformative`); its scores still rank. On a table of
|
|
156
|
+
low-cardinality categoricals, Isolation Forest's boundary flagged every row (so
|
|
157
|
+
Isolation Forest alone flagged every row, and `union` did too) and One-Class SVM's
|
|
158
|
+
sat on every row, where the side a row fell on was floating-point noise and differed
|
|
159
|
+
by platform. The fit-time rate is saved in each model's manifest
|
|
160
|
+
(`boundary_flag_rate`), so `score --from-run` applies the same verdict; models
|
|
161
|
+
saved before have none and keep their vote. `GroupSummary.abstaining_detectors` and
|
|
162
|
+
`groups[].abstaining_detectors` in `--json` name them. A numeric contamination is
|
|
163
|
+
unaffected.
|
|
164
|
+
- **Reading a run: the ranking first**: every `run` and `score` summary ends with
|
|
165
|
+
the top-ranked rows, flagged or not, and each group keeps its top 20 by combined
|
|
166
|
+
score in `ranked.parquet` (`GroupSummary.top_ranked`, `groups[].top_ranked` in
|
|
167
|
+
`--json`, `sorethumb anomalies --ranked`, and in the report). At the default
|
|
168
|
+
`scoring.contamination = "auto"` the detectors' own boundaries set how many rows
|
|
169
|
+
are flagged, so every run says the size was not chosen (`shortlist_size_chosen:
|
|
170
|
+
false` in `--json`), and a workspace's first run, or any run without a
|
|
171
|
+
`sorethumb.toml`, says it prominently with the count it came to and how to set a
|
|
172
|
+
review budget. The default policy is unchanged. A run, `anomalies` or a report
|
|
173
|
+
with no flagged rows says that is the policy's decision, not a finding that the
|
|
174
|
+
data holds no anomalies, and shows the ranking; a missing results file is
|
|
175
|
+
reported as missing.
|
|
176
|
+
- **Score-forward**: `sorethumb score --from-run RUN_ID` scores new data with a
|
|
177
|
+
fitted run's persisted models and calibrators, without refitting, so scores
|
|
178
|
+
stay on one scale across calls. A config whose fit-time settings differ from
|
|
179
|
+
the source run's is rejected.
|
|
180
|
+
- **History**: `sorethumb backfill` fills missing periods and `sorethumb history`
|
|
181
|
+
reports how the data is changing, scoped to the dataset and the exact
|
|
182
|
+
configuration that produced each period. A period window that selects no rows
|
|
183
|
+
fails immediately, naming the window, the time column and the data's available
|
|
184
|
+
range. See `docs/trends.md`.
|
|
185
|
+
- Four signals, each with a verdict per window (`ok`, caveats with reasons, or
|
|
186
|
+
n/a with why): **population** drift (PSI per input field, and volume; no
|
|
187
|
+
model involved), **scores** and **threshold** (with an anchor run, below),
|
|
188
|
+
and **counts** (the anomaly rate per window with Wilson and Newcombe 95%
|
|
189
|
+
intervals, coverage and matched groups).
|
|
190
|
+
- Under `composite` with a numeric contamination the rate is set by the review
|
|
191
|
+
budget, so no change is shown.
|
|
192
|
+
- **Anchor run** (`history.anchor_run`): each period run also scores its rows
|
|
193
|
+
with one fixed run's models, calibrators and flag rule, so a change in
|
|
194
|
+
anomaly count splits exactly into volume, score drift and threshold drift.
|
|
195
|
+
Every fitting run saves its rule (`anchor_rule.json`, plain JSON), so any
|
|
196
|
+
complete fitting run can be an anchor.
|
|
197
|
+
- Each period run records per-period statistics (`period_stats`): per input
|
|
198
|
+
field, its missing count and percentiles or top values; per detector, score
|
|
199
|
+
percentiles and flag count.
|
|
200
|
+
- `history --json` prints the whole report; each period run's HTML report shows
|
|
201
|
+
it per group, and `index.json` carries it under `trend`.
|
|
202
|
+
- **Reports**: self-contained HTML plus CSV and JSON, re-renderable from
|
|
203
|
+
persisted state with `sorethumb report`.
|
|
204
|
+
- **Windows** (10/11 x64): every command, the Python API and reports work as
|
|
205
|
+
on Linux and macOS, from PowerShell, Windows PowerShell 5.1 or cmd.exe; CI
|
|
206
|
+
runs the unit, contract and integration suites on `windows-latest` for
|
|
207
|
+
every change, plus installs of the built wheel and sdist. Spellings of one Windows
|
|
208
|
+
file that differ only in letter case, separators or a `file://` prefix
|
|
209
|
+
(`C:\Data\x.csv`, `c:/data/x.csv`, `file:///C:/Data/x.csv`) are one dataset
|
|
210
|
+
for history and one configuration for run ids and model reuse; a
|
|
211
|
+
drive-relative path such as `C:data.csv` is rejected with a clear message.
|
|
212
|
+
`~` in `--config`, `--workdir` and `init` paths is expanded by sorethumb
|
|
213
|
+
itself, so it works in cmd.exe too, and a command started with stdin from
|
|
214
|
+
`NUL` (a scheduled task, `subprocess.DEVNULL`) never prompts.
|
|
215
|
+
- **Files held open by another program** (a spreadsheet, a file-sync client,
|
|
216
|
+
antivirus): replacing or deleting a report, model or downloaded file is
|
|
217
|
+
retried briefly, then fails with a `FileInUseError` naming the file.
|
|
218
|
+
`sorethumb workspace prune` deletes everything else it can, keeps a locked
|
|
219
|
+
file indexed for the next prune, and exits non-zero.
|
|
220
|
+
- **Workspace checks before a run**: without Windows long-path support, `init`,
|
|
221
|
+
`run` and `score` stop before doing any work (exit code `2`,
|
|
222
|
+
`PathTooLongError`) when the workspace is too deep for the 260-character
|
|
223
|
+
path limit. A workspace on a network filesystem, a mapped drive or UNC
|
|
224
|
+
path, or in a OneDrive, Dropbox, Google Drive or iCloud folder gets a
|
|
225
|
+
one-line warning.
|
|
226
|
+
- **Text encodings**: a CSV that isn't UTF-8 (Excel's plain "CSV" on Windows
|
|
227
|
+
writes cp1252) fails with a message saying so and how to fix it.
|
|
228
|
+
`report.csv_bom` starts report CSVs with the byte-order mark Excel needs to
|
|
229
|
+
show non-ASCII text. Output redirected to a pipe or file is UTF-8 unless
|
|
230
|
+
`PYTHONIOENCODING` says otherwise, and every `--json` output is
|
|
231
|
+
ASCII-escaped, so it is lossless under any output encoding.
|
|
232
|
+
- **Extras**: `explain` (shap, numba) for TreeSHAP and KernelSHAP attributions;
|
|
233
|
+
`benchmark` (datasets, pandas, pyarrow) for the benchmark harnesses.
|
|
234
|
+
- **Docs**: the README is an entry page: install, the quickstart (the command line
|
|
235
|
+
first, then Python), what it does not do, whether it is the right tool and which
|
|
236
|
+
setup to use, and the platform table. The rest is behind `docs/index.md`:
|
|
237
|
+
`docs/getting-started.md` (the command line step by step, and file formats),
|
|
238
|
+
`docs/limitations.md`, `docs/scale.md` and `docs/platforms.md`, with the benchmark
|
|
239
|
+
headline summary in `docs/benchmarks.md`. `docs/stability.md` sets out the pre-1.0
|
|
240
|
+
API and stability policy;
|
|
241
|
+
`docs/benchmarks.md` holds the benchmark results and their accuracy floors,
|
|
242
|
+
and an ensemble policy matrix: every scenario under every policy, with
|
|
243
|
+
precision, recall, ranking, stability and false-positive figures checked
|
|
244
|
+
against versioned per-anomaly-type budgets, where every budget the default
|
|
245
|
+
misses is listed as an accepted limitation with its reason.
|
|
246
|
+
`docs/ensemble-policy.md` sets out the backtest protocol a candidate policy
|
|
247
|
+
must pass before it can be said to outperform the default (pre-registration,
|
|
248
|
+
disjoint confirmation seeds, paired bootstrap with Holm correction, no budget
|
|
249
|
+
or stability regressions, confirmation in rounds of fresh seeds) and
|
|
250
|
+
publishes each confirmation result. An end-to-end scale benchmark
|
|
251
|
+
(`scripts/run_scale_benchmark.py`) runs a zero-config `sorethumb run` and a
|
|
252
|
+
`score --from-run` at 10,000, 100,000 and 1,000,000 rows, and publishes time per
|
|
253
|
+
stage, peak memory, feature width, each detector's training sample, explanation
|
|
254
|
+
cost and output size. `docs/explanations.md` and
|
|
255
|
+
`docs/trends.md` say what each explanation and trend figure means and where it
|
|
256
|
+
stops.
|
|
257
|
+
- **`sorethumb backtest`**: compares every registered ensemble policy with the
|
|
258
|
+
default on your own data, using `columns.reference_column` labels, and records
|
|
259
|
+
each verdict in the workspace (migration 011, `policy_evidence`). A `run` or
|
|
260
|
+
`score` with a non-default policy that has no `outperforms` record for the
|
|
261
|
+
dataset and configuration warns (`UnevidencedPolicyWarning`); `run.strict`
|
|
262
|
+
refuses it (`UnevidencedPolicyError`, exit code 2). The evidence a run relied
|
|
263
|
+
on is recorded as `run.policy_evidence_id` and in `--json` output, and shown by
|
|
264
|
+
`sorethumb show` and the run summary; `sorethumb evidence` lists the recorded
|
|
265
|
+
evidence and which records apply to the current configuration.
|
|
266
|
+
`sorethumb backtest --forward` tests out of time instead: each period is fitted
|
|
267
|
+
and the next scored with those fits, as `score --from-run` does. A candidate
|
|
268
|
+
must also rank the anomalies no worse than the default (mean average precision
|
|
269
|
+
within `--ranking-tolerance`, 10%): decision rule 2, which `run` requires of the
|
|
270
|
+
evidence it relies on. With `scoring.contamination = "auto"`, `--review-budget`
|
|
271
|
+
states the budget flags are judged against.
|
|
272
|
+
- **Real-data policy evidence**: the pre-registered backtest protocol applied to
|
|
273
|
+
KDD Cup 1999 (SA), Covertype (2 vs 4) and a time-ordered credit-card fraud
|
|
274
|
+
dataset, with disjoint development and confirmation slices; results in
|
|
275
|
+
`docs/ensemble-policy.md`.
|
|
276
|
+
Composite policies must be paired with a numeric `scoring.contamination`: at
|
|
277
|
+
`"auto"`, `composite-agreement@1` flags several times the review budget and
|
|
278
|
+
does not outperform the default (pre-registered, synthetic and real).
|
|
279
|
+
`docs/ensemble-policy.md` gives a recommended setup for users with a review
|
|
280
|
+
budget, and qualifies the default's precision-first rationale: it holds at the
|
|
281
|
+
shipped `"auto"`, and the default's real weakness is unpredictable volume.
|
|
282
|
+
|
|
283
|
+
### Security
|
|
284
|
+
|
|
285
|
+
- CSV report output is neutralised against spreadsheet formula injection.
|
|
286
|
+
- `sorethumb workspace reset` refuses any target that does not open as a
|
|
287
|
+
sorethumb workspace, and always refuses a filesystem root, the home
|
|
288
|
+
directory, the current directory, a git repository root or a very shallow
|
|
289
|
+
path. It deletes only the entries sorethumb creates, so other files in the
|
|
290
|
+
directory are kept, and removes a symlink or Windows directory junction
|
|
291
|
+
among them without following it. The typed confirmation accepts the path
|
|
292
|
+
however it is spelled: quoted (as Windows Explorer's "Copy as path" gives
|
|
293
|
+
it), with a trailing separator, or on Windows in a different letter case
|
|
294
|
+
or with forward slashes.
|
|
295
|
+
- Credentials are never sent over plaintext HTTP. `source.auth` or
|
|
296
|
+
`user:password@` in an `http://` `source.uri` is rejected at config load, and
|
|
297
|
+
a credentialed request is refused before it is sent on every hop, including
|
|
298
|
+
the first request of an HTTP-to-HTTPS redirect. There is no loopback
|
|
299
|
+
exception.
|
|
300
|
+
- A config validation error never echoes a `user:password@` from
|
|
301
|
+
`source.uri`, and download error messages redact it.
|
|
302
|
+
- Authenticated HTTP(S) downloads (`source.auth`) send the `Authorization`
|
|
303
|
+
header only to the configured origin: never across a redirect to another host
|
|
304
|
+
or port. An HTTPS-to-HTTP redirect is refused, and so is a redirect to a
|
|
305
|
+
different host that resolves to a loopback, link-local, private, reserved
|
|
306
|
+
or multicast address (such as a cloud metadata endpoint). The host you
|
|
307
|
+
configure yourself is never blocked, even an internal one; see SECURITY.md.
|
|
308
|
+
- An unexpected CLI crash never prints local variables, so a traceback cannot
|
|
309
|
+
reveal the download credential.
|
|
310
|
+
- Source URI query values are never stored, logged or reported. The persisted
|
|
311
|
+
and displayed form keeps the scheme, host, non-default port, path and query
|
|
312
|
+
key names and replaces every query value with `REDACTED` (no name list, so
|
|
313
|
+
`client_secret`, `jwt`, `accessKey` and unlisted names are covered); userinfo
|
|
314
|
+
becomes `***@` and the fragment is dropped. This covers `dataset.source_uri`,
|
|
315
|
+
`run.config_json`, `RunResult.dataset_uri`, `run --json`, `show`, reports,
|
|
316
|
+
the `httpx` request log line and download errors, with no opt-out. A new
|
|
317
|
+
`source_digest` (SHA-256 of the canonical full URI) on `dataset` and `run`
|
|
318
|
+
keeps query-differing sources distinguishable. A workspace created by a
|
|
319
|
+
pre-release build is scrubbed when opened (migration 009; see SECURITY.md for
|
|
320
|
+
what it does not reach). Without
|
|
321
|
+
`source.dataset_id`, a run whose query key set differs from the dataset's
|
|
322
|
+
previous run is refused, naming the `dataset_id` that keeps its history.
|
|
323
|
+
- **Persisted-model trust boundary**: a workspace's fitted models are
|
|
324
|
+
`joblib`/pickle files, and `score --from-run`, `run.reuse_models` and
|
|
325
|
+
`history.anchor_run` unpickle them with no sandboxing. That is arbitrary code execution, not safe data
|
|
326
|
+
loading. The SHA-256 digests checked on load catch corruption or a swapped
|
|
327
|
+
file; they are **not** a security boundary against a deliberately malicious
|
|
328
|
+
one. Only use workspaces you created or fully trust. See
|
|
329
|
+
[SECURITY.md](https://github.com/tarne75/sorethumb/blob/main/SECURITY.md).
|
|
330
|
+
|
|
331
|
+
### Known limitations
|
|
332
|
+
|
|
333
|
+
- **The default `combination="intersection"`** flags a row only when every
|
|
334
|
+
configured detector flags it independently. That is deliberately
|
|
335
|
+
conservative and, on some data shapes, can flag very few true anomalies or
|
|
336
|
+
none. At the default `scoring.contamination = "auto"` you do not control how
|
|
337
|
+
many rows it flags: 0.18x to 8.4x a review budget on the real datasets tested.
|
|
338
|
+
With a fixed review budget, `composite-agreement@1` at that budget is the
|
|
339
|
+
evidenced choice. See `docs/ensemble-policy.md` and `docs/approximations.md`.
|
|
340
|
+
- **Policy evidence has limits.** It covers a maintainer-written synthetic suite
|
|
341
|
+
and three real datasets (one confirmation slice each), with the default three
|
|
342
|
+
detectors at stated review budgets. `rule:discrete-budget@2` won on synthetic
|
|
343
|
+
data only and is not a selectable setting. No policy recovers contextual
|
|
344
|
+
anomalies. Full list: "Where things stand" in `docs/ensemble-policy.md`.
|
|
345
|
+
- **Without the `explain` extra**, Isolation Forest explanations are
|
|
346
|
+
`heuristic` rather than `model_specific`. Explanations are exact only for ECOD
|
|
347
|
+
and HBOS; TreeSHAP decomposes Isolation Forest's path length, not its score.
|
|
348
|
+
- **`run.max_memory_mb` is a projection, not a limit**: it refuses a run whose
|
|
349
|
+
estimated feature-matrix size exceeds it, but cannot cap actual memory use.
|
|
350
|
+
- **How much a categorical column counts is a prior.** One-hot dummies sit at
|
|
351
|
+
a fixed scale (`features.categorical_distance`, 0.5 interquartile ranges per
|
|
352
|
+
change of category), chosen so uninformative categories cannot swamp the
|
|
353
|
+
distance-based detectors; it is not learned from your data. See
|
|
354
|
+
`docs/approximations.md`.
|
|
355
|
+
- Every detector fits on the same unlabelled data it scores; there is no
|
|
356
|
+
held-out "known normal" reference. Independent runs are not on a common score
|
|
357
|
+
scale: `sorethumb history` separates score drift from threshold drift only
|
|
358
|
+
with an anchor run, and `score --from-run` reuses a fitted run's scale. See
|
|
359
|
+
`docs/approximations.md` for this and the other approximations (PCA,
|
|
360
|
+
correlation pruning, row-capped detector training, zero-inflated scaling).
|
|
361
|
+
- **The `swamping` benchmark scenario** found no false-positive inflation from
|
|
362
|
+
unlabelled training contamination; measured on matched data, the effect runs
|
|
363
|
+
the other way (mildly reduced sensitivity).
|
|
364
|
+
- Real-dataset benchmark figures (KDDCup99, Covtype) are capped at 20,000 rows
|
|
365
|
+
for CI runtime, as `docs/benchmarks.md` states where the tables appear.
|
|
366
|
+
- **Dataset size**: measured end to end up to 1,000,000 rows (with the defaults, 6.9
|
|
367
|
+
minutes and 4.9 GB peak memory on an 8-core, 16 GB laptop); larger datasets are
|
|
368
|
+
untested. Explanations are most of the time and memory, and peak memory is set by
|
|
369
|
+
loading, profiling and explanations, not by the projected feature matrix that
|
|
370
|
+
`run.max_memory_mb` checks. See `docs/scale.md`.
|
|
371
|
+
- **Platforms**: native Windows on ARM is untested (the core installs from
|
|
372
|
+
Windows ARM64 wheels, but no CI job runs there, and the `benchmark` extra's
|
|
373
|
+
pyarrow has no such wheel; an x64 Python under emulation is the tested
|
|
374
|
+
route), and on any OS a workspace needs a local drive, not a network share or
|
|
375
|
+
synced folder. See `docs/platforms.md`.
|
|
376
|
+
|
|
377
|
+
[Unreleased]: https://github.com/tarne75/sorethumb/compare/v0.1.0...HEAD
|
|
378
|
+
[0.1.0]: https://github.com/tarne75/sorethumb/releases/tag/v0.1.0
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our
|
|
6
|
+
community a harassment-free experience for everyone, regardless of age, body
|
|
7
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
8
|
+
identity and expression, level of experience, education, socio-economic status,
|
|
9
|
+
nationality, personal appearance, race, caste, color, religion, or sexual
|
|
10
|
+
identity and orientation.
|
|
11
|
+
|
|
12
|
+
## Our Standards
|
|
13
|
+
|
|
14
|
+
Examples of behavior that contributes to a positive environment:
|
|
15
|
+
|
|
16
|
+
* Demonstrating empathy and kindness toward other people
|
|
17
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
|
18
|
+
* Giving and gracefully accepting constructive feedback
|
|
19
|
+
* Accepting responsibility and apologizing to those affected by our mistakes
|
|
20
|
+
|
|
21
|
+
Examples of unacceptable behavior:
|
|
22
|
+
|
|
23
|
+
* The use of sexualized language or imagery, and sexual attention or advances
|
|
24
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
25
|
+
* Public or private harassment
|
|
26
|
+
* Publishing others' private information without their explicit permission
|
|
27
|
+
|
|
28
|
+
## Enforcement
|
|
29
|
+
|
|
30
|
+
Community leaders are responsible for clarifying and enforcing our standards.
|
|
31
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
32
|
+
reported to the project maintainer, Tarne Westcott, by emailing
|
|
33
|
+
hello@t4-digital.uk (the project's contact address) with the subject line "sorethumb code of conduct". All complaints will be reviewed and
|
|
34
|
+
investigated promptly and fairly, and reporter identity will be kept
|
|
35
|
+
confidential.
|
|
36
|
+
|
|
37
|
+
## Attribution
|
|
38
|
+
|
|
39
|
+
This Code of Conduct is adapted from the
|
|
40
|
+
[Contributor Covenant](https://www.contributor-covenant.org), version 2.1.
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Contributing to sorethumb
|
|
2
|
+
|
|
3
|
+
## From zero to a passing test suite
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# 1. Clone and enter the repo
|
|
7
|
+
git clone https://github.com/tarne75/sorethumb.git
|
|
8
|
+
cd sorethumb
|
|
9
|
+
|
|
10
|
+
# 2. Install uv (if not already installed)
|
|
11
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
12
|
+
|
|
13
|
+
# 3. Create the virtualenv and install every extra (--frozen: use the
|
|
14
|
+
# committed uv.lock as-is, matching what CI installs)
|
|
15
|
+
uv sync --all-extras --frozen
|
|
16
|
+
|
|
17
|
+
# 4. Install pre-commit hooks
|
|
18
|
+
uv run pre-commit install
|
|
19
|
+
|
|
20
|
+
# 5. Verify everything passes (mirrors CI's required PR lanes)
|
|
21
|
+
uv run ruff check src/ tests/
|
|
22
|
+
uv run ruff format --check src/ tests/
|
|
23
|
+
uv run mypy src/
|
|
24
|
+
uv run python docs/generate_config_docs.py --check
|
|
25
|
+
uv run pytest -m "unit or contract"
|
|
26
|
+
uv run pytest -m integration
|
|
27
|
+
uv run pytest -m property
|
|
28
|
+
uv run pytest -m repo_check
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Developing on Windows
|
|
32
|
+
|
|
33
|
+
The same steps work in PowerShell, with two differences:
|
|
34
|
+
|
|
35
|
+
- Install uv with `powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"`
|
|
36
|
+
instead of the `curl` line.
|
|
37
|
+
- `uv run ...` needs no activated virtualenv. To activate one anyway, it is
|
|
38
|
+
`.venv\Scripts\Activate.ps1` (PowerShell may first need
|
|
39
|
+
`Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`).
|
|
40
|
+
|
|
41
|
+
Clone onto a local drive, not inside a OneDrive or other synced folder: sync
|
|
42
|
+
clients lock files mid-write, which shows up as intermittent "file is open in
|
|
43
|
+
another program" failures. The repository is checked out with LF line endings
|
|
44
|
+
on every platform (`.gitattributes`), whatever your `core.autocrlf` setting.
|
|
45
|
+
|
|
46
|
+
`scripts/release.sh` is a bash script for maintainers cutting a release; run it
|
|
47
|
+
from macOS, Linux or WSL. Everything else in this guide works natively.
|
|
48
|
+
|
|
49
|
+
If you changed `pyproject.toml`'s dependencies, run `uv lock` to update
|
|
50
|
+
`uv.lock` and commit both together — CI runs `uv lock --check` and will fail
|
|
51
|
+
a PR where they've drifted apart.
|
|
52
|
+
|
|
53
|
+
`uv.lock` pins what CI tests, not what a fresh `pip install sorethumb-ml` gets: that
|
|
54
|
+
resolves the newest release of every dependency. The `Newest dependencies` workflow
|
|
55
|
+
(`.github/workflows/newest-deps.yml`) re-resolves everything at its newest release
|
|
56
|
+
and runs the suite daily. When it fails, an upstream release broke something:
|
|
57
|
+
re-lock (`uv lock --upgrade-package NAME`) and fix the code, or add an upper bound
|
|
58
|
+
in `pyproject.toml` until you can.
|
|
59
|
+
|
|
60
|
+
## Optional extras
|
|
61
|
+
|
|
62
|
+
The mandatory install (`pip install sorethumb-ml` / `uv sync`) is deliberately
|
|
63
|
+
minimal. Everything below is opt-in, matched to the feature it enables:
|
|
64
|
+
|
|
65
|
+
| Extra | Adds | Enables |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| `explain` | shap, numba | TreeSHAP / KernelSHAP explanations. Without it, explanations fall back to the pure-numpy gradient method with a warning — the run itself never fails. |
|
|
68
|
+
| `benchmark` | datasets, pandas, pyarrow | `scripts/run_benchmark.py` (both the real-dataset and full-pipeline-scenario suites) and `pytest -m benchmark`. |
|
|
69
|
+
| `dev` | pytest, ruff, mypy, pre-commit, hypothesis, ... | Everything needed to run the test suite and quality checks in this repo. |
|
|
70
|
+
|
|
71
|
+
`uv sync --all-extras --frozen` installs all three, which is what you want for
|
|
72
|
+
contributing. A production install that only ever calls `run_detection`
|
|
73
|
+
without SHAP explanations can skip straight to core: `pip install sorethumb-ml`.
|
|
74
|
+
|
|
75
|
+
## Running specific test groups
|
|
76
|
+
|
|
77
|
+
Every test carries exactly one marker (see `[tool.pytest.ini_options]` in
|
|
78
|
+
`pyproject.toml`); bare `uv run pytest` runs only `unit`/`contract` by
|
|
79
|
+
default (its `addopts` excludes the rest). These mirror CI's own job-by-job
|
|
80
|
+
`-m` selection (`.github/workflows/ci.yml`) — nothing here should drift from
|
|
81
|
+
what a job actually runs.
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Default: unit + contract only — fast, deterministic, no filesystem/
|
|
85
|
+
# network/subprocess/model-serialisation I/O. Matches CI's fast-tests job.
|
|
86
|
+
uv run pytest
|
|
87
|
+
|
|
88
|
+
# Integration: real workspace, SQLite, CLI process, full pipeline, report
|
|
89
|
+
# rendering. May touch the local filesystem and subprocesses, not the network.
|
|
90
|
+
uv run pytest -m integration
|
|
91
|
+
|
|
92
|
+
# Hypothesis-driven property tests.
|
|
93
|
+
uv run pytest -m property
|
|
94
|
+
|
|
95
|
+
# Repository/docs consistency checks (generated docs, README snippets).
|
|
96
|
+
uv run pytest -m repo_check
|
|
97
|
+
|
|
98
|
+
# Benchmark suite (opt-in, measures accuracy on real or synthetic datasets).
|
|
99
|
+
uv run pytest -m benchmark
|
|
100
|
+
|
|
101
|
+
# Every required-PR-lane test in one invocation (matches CI's coverage job).
|
|
102
|
+
uv run pytest -m "unit or contract or integration or property or repo_check"
|
|
103
|
+
|
|
104
|
+
# With coverage. Start coverage.py before pytest: pytest's own startup imports
|
|
105
|
+
# sorethumb_ml, so `pytest --cov` reports every import-time line as untested.
|
|
106
|
+
# `combine` merges in the CLI subprocesses the tests start.
|
|
107
|
+
uv run coverage run -m pytest && uv run coverage combine && uv run coverage report
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Adding a detector
|
|
111
|
+
|
|
112
|
+
`sorethumb` discovers detectors via the `sorethumb_ml.detectors` entry-point group. You can
|
|
113
|
+
add a detector in a separate package without modifying this repository.
|
|
114
|
+
|
|
115
|
+
### 1. Implement the `Detector` protocol
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
# my_package/my_detector.py
|
|
119
|
+
from typing import ClassVar, Any
|
|
120
|
+
import numpy as np
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class MyDetector:
|
|
124
|
+
name: ClassVar[str] = "my_detector"
|
|
125
|
+
supports_tree_shap: ClassVar[bool] = False
|
|
126
|
+
default_train_row_cap: ClassVar[int] = 100_000
|
|
127
|
+
|
|
128
|
+
def fit(self, X: np.ndarray, *, seed: int) -> None:
|
|
129
|
+
# fit your model; store it on self
|
|
130
|
+
...
|
|
131
|
+
|
|
132
|
+
def score_samples(self, X: np.ndarray) -> np.ndarray:
|
|
133
|
+
# MUST return a finite 1-D numeric ndarray of shape (len(X),), one score
|
|
134
|
+
# per row in input order, higher = MORE NORMAL (sklearn's convention).
|
|
135
|
+
# No NaN/inf, no (n, 1) arrays, no lists.
|
|
136
|
+
...
|
|
137
|
+
|
|
138
|
+
def natural_flag(self, scores: np.ndarray) -> np.ndarray:
|
|
139
|
+
# MUST return a 1-D ndarray of dtype bool (not int 0/1), same length as
|
|
140
|
+
# `scores`: True = anomaly, using the model's own boundary
|
|
141
|
+
...
|
|
142
|
+
|
|
143
|
+
def get_params(self) -> dict[str, Any]:
|
|
144
|
+
return {}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 2. Register it via entry points in your package's `pyproject.toml`
|
|
148
|
+
|
|
149
|
+
```toml
|
|
150
|
+
[project.entry-points."sorethumb_ml.detectors"]
|
|
151
|
+
my_detector = "my_package.my_detector:MyDetector"
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 3. Verify it appears
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
pip install -e .
|
|
158
|
+
sorethumb detectors
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Your detector should appear in the list alongside the built-ins.
|
|
162
|
+
|
|
163
|
+
The exact output contract (shape, dtype, finiteness, fitted-state rules) and the
|
|
164
|
+
`DetectorError` raised when it is broken are in
|
|
165
|
+
[Writing a detector plugin](docs/models.md#writing-a-detector-plugin).
|
|
166
|
+
|
|
167
|
+
## Code standards
|
|
168
|
+
|
|
169
|
+
- ruff for linting and formatting (`uv run ruff check --fix src/`, then `uv run ruff format src/`)
|
|
170
|
+
- mypy strict on `src/` (`uv run mypy src/`)
|
|
171
|
+
- No `print` in `src/sorethumb_ml/` — use `logging.getLogger(__name__)`
|
|
172
|
+
- A threshold a user may need to change is a field in `config.py`, with a
|
|
173
|
+
description; a fixed internal constant is a named module-level constant with a
|
|
174
|
+
comment saying why it has that value, never a bare number in the logic
|
|
175
|
+
- Every new degradation point gets a named `SorethumbWarning` subclass in `errors.py`
|