hypertopos 0.7.2__tar.gz → 0.7.3__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.
- {hypertopos-0.7.2 → hypertopos-0.7.3}/CHANGELOG.md +22 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/LICENSE.md +1 -1
- {hypertopos-0.7.2 → hypertopos-0.7.3}/PKG-INFO +1 -1
- {hypertopos-0.7.2 → hypertopos-0.7.3}/README.md +1 -1
- {hypertopos-0.7.2 → hypertopos-0.7.3}/ROADMAP.md +3 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/SECURITY.md +1 -1
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/__init__.py +1 -1
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/builder/builder.py +134 -5
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/builder/derived.py +30 -6
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/aggregation.py +113 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/geometry.py +56 -0
- hypertopos-0.7.3/hypertopos/engine/graph.py +285 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/topology.py +182 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/model/sphere.py +58 -2
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/navigation/navigator.py +858 -20
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/storage/calibration_history.py +80 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/storage/reader.py +17 -3
- {hypertopos-0.7.2 → hypertopos-0.7.3}/pyproject.toml +1 -1
- hypertopos-0.7.3/tests/test_chain_signed_confidence_rollup.py +304 -0
- hypertopos-0.7.3/tests/test_cluster_a_label_aware_depth.py +416 -0
- hypertopos-0.7.3/tests/test_cluster_c1_sphere_overview_ergonomics.py +476 -0
- hypertopos-0.7.3/tests/test_cluster_c3_detector_closures.py +217 -0
- hypertopos-0.7.3/tests/test_cluster_d1_graph_phase3.py +315 -0
- hypertopos-0.7.3/tests/test_cluster_d2_influencer_auto_discovery.py +346 -0
- hypertopos-0.7.3/tests/test_local_trajectory_shape.py +88 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_navigation.py +110 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/.github/workflows/publish-pypi.yml +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/.gitignore +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/README.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/api-reference.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/architecture.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/LICENSE +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/README.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/README.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/SUMMARY.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-settings.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-settings.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-settings.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-settings.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-settings.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-settings.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/sphere/prepare_accounts.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/sphere/prepare_cpty_banks.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/sphere/prepare_districts.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/sphere/prepare_loan_accounts.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/sphere/prepare_operations.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/sphere/prepare_orders.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/sphere/prepare_transactions.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/sphere/prepare_tx_types.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/berka/sphere/sphere.yaml +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/ibm-aml/README.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/ibm-aml/_layer2_cross_eval.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/ibm-aml/_layer3_cross_eval.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_accounts.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_currencies.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_payment_formats.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_transactions.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/ibm-aml/hi_small_sphere/sphere.yaml +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/ibm-aml/layer1_passive_scan.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/ibm-aml/layer2_xgboost_sweep.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/ibm-aml/layer3_typology_filter.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/ibm-aml/li_small_sphere/prepare_accounts.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/ibm-aml/li_small_sphere/prepare_currencies.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/ibm-aml/li_small_sphere/prepare_payment_formats.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/ibm-aml/li_small_sphere/prepare_transactions.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/ibm-aml/li_small_sphere/sphere.yaml +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/ibm-aml/passive-scan-calibration.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/nyc-taxi/README.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/nyc-taxi/results/0.1.0/SUMMARY.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-c-report.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-settings.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-c-report.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-settings.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-c-report.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-settings.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/nyc-taxi/sphere/prepare_trips.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmark/nyc-taxi/sphere/sphere.yaml +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/benchmarks.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/concepts.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/configuration.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/data-format.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/external-chains-as-anchor-line.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/images/build-pipeline.svg +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/images/core-objects.svg +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/images/cutting-plane.svg +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/images/directory-layout.svg +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/images/hypertopos-overview.svg +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/images/model-lifecycle.svg +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/images/navigation-primitives.svg +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/images/three-scales.svg +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/introduction.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/patent-implementation-map.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/quickstart.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/docs/refutations.md +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/builder/__init__.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/builder/_bootstrap.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/builder/_bregman.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/builder/_scatter.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/builder/_stats.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/builder/_theta_sensitivity.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/builder/_writer.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/builder/conformance.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/builder/conformance_mapping.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/builder/dim_blocks.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/builder/mapping.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/builder/temporal_bucket.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/cli/__init__.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/cli/__main__.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/cli/build.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/cli/info.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/cli/main.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/cli/schema.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/cli/sources.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/__init__.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/adjacency.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/calibration.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/calibration_label_aware.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/chains.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/composition.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/conformance.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/counterfactual.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/density_gaps.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/diagnostics.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/dim_audit.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/edge_features.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/explanation.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/fdr.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/forecast.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/graph_algorithms.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/heredity.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/hop_predicate.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/invalidation.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/investigation.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/lance_sql_agg.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/p_value_calibration.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/selection.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/engine/structuring.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/model/__init__.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/model/manifest.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/model/objects.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/navigation/__init__.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/navigation/scanner.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/sphere.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/storage/__init__.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/storage/_lance_versions.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/storage/_schemas.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/storage/_tag_timestamps.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/storage/cache.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/storage/exceptions.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/storage/topology_cache.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/storage/writer.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/utils/__init__.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/hypertopos/utils/arrow.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/.gitkeep +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/conftest.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/fixtures/generate_fixtures.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/fixtures/synthetic_chain_sphere.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_adjacency.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_adjacency_vectorized.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_aggregation_engine.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_anomaly_propagation_in_chain.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_arrow_utils.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_audit_label_alignment.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_bootstrap.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_bregman.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_builder.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_builder_alias.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_builder_chunking.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_builder_contagion.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_builder_derived.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_builder_edges.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_builder_fdr_hierarchy.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_builder_fdr_hierarchy_inject.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_builder_fdr_yaml_end_to_end.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_builder_rolling_z.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_builder_scatter.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_builder_temporal_bucket.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_builder_temporal_perf.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_cache.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_calibration.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_calibration_drift.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_calibration_history.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_calibration_history_builder.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_calibration_history_reader.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_calibration_label_aware.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_chain_coherent_anomaly.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_chain_coherent_synthetic.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_chain_drift_trajectory.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_chain_edge_potentials.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_chain_investigation_summary.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_chain_typology_and_extend.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_chain_witness_intersection.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_chains.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_cli.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_cli_alias.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_cli_label_aware_flag.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_cli_sources.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_compaction.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_composition.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_conformance.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_conformance_mapping.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_conftest_magicmock_restore.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_continuous_event_dims.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_counterfactual.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_counterfactual_frozen_trajectory.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_data_quality_prop.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_decompose_drift.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_delta_dim_aggregation_consumers.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_delta_norm_signed.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_density_gaps_engine.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_detection.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_diagnostics.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_dim_audit.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_dim_blocks.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_dim_quality_warnings.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_distinct_neighbor_count.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_edge_dim_aggregations.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_edge_dim_aggregations_builder.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_edge_dim_aggregations_chain.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_edge_dim_aggregations_engine.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_edge_dim_aggregations_roundtrip.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_edge_dimensions_builder.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_edge_dimensions_yaml.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_edge_features.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_edge_features_reader.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_edge_potential.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_edge_table.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_engine.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_fdr.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_fdr_multi_resolution.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_fdr_per_dim.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_find_calibration_influencers.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_find_density_gaps.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_find_diverse_explanations.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_find_lead_lag.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_find_lead_lag_significance.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_find_motif_by_hops.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_forecast.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_fts.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_generate_sar_rationale.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_geometric_path.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_geometry_stats.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_gradient_alignment.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_graph_algorithms.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_graph_geometry_tension.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_heredity.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_hop_predicate_engine.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_hub_safety_caps.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_hybrid_search.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_incremental.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_invalidation.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_investigate_chain.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_investigate_entity.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_investigation.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_kind_detect.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_kind_tag_mismatch_warning.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_label_audit_schema.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_lance_sql_agg.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_m1_1_builder_wiring.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_mapping.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_model.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_motif.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_mvcc_session.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_native_mvcc_geometry.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_navigator_alerts.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_navigator_fdr_resolution.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_navigator_fdr_select.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_neighbors_window.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_observability.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_p_value_calibration.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_pattern_fdr_hierarchy.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_precision_stack.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_profile_motif_perf_smoke.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_reliability_flags.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_reputation.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_root_cause_trace.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_row_id_cache.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_scanner.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_schema_chain_eda_validation.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_selection.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_signed_confidence_ranking.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_simulate_dimension_change.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_sphere.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_sphere_format_version.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_sphere_overview_perf.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_sphere_validation_cheap_tier.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_stats_guard.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_storage.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_streaming_reads.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_structuring_engine.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_tag_timestamps.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_temporal_centroids.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_theta_sensitivity.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_topology.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_topology_anomalies.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_topology_cache.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_topology_trajectory_anomalies.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_trajectory_ann.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_witness_cohort.py +0 -0
- {hypertopos-0.7.2 → hypertopos-0.7.3}/tests/test_witness_set.py +0 -0
|
@@ -7,6 +7,28 @@ Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.7.3] — 2026-05-27
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `GDSNavigator.π12_attract_regime_change` (and its cache fast-path) — each detected changepoint now carries a `near_data_boundary: bool` field. Fires `True` when the changepoint timestamp index falls in the first two or last two temporal buckets of the filtered range — agents can suppress these as low-confidence detections sitting on insufficient one-sided context (the buckets at the data range edges have asymmetric neighbours, which inflates the shift magnitude). Applies to both the cache fast-path (`_pi12_from_cache`) and the cold-scan path; no schema change.
|
|
15
|
+
- `Pattern.signed_percentiles` — per-pattern percentile cache (`p1`, `p5`, `p50`, `p95`, `p99`) over the `delta_norm_signed` Lance column, populated at build time when label-aware calibration fires. Drives the new `signed_tail_concentration` dim_quality warning that flags one-sided extreme tails (`|p99| / max(|p50|, 1e-9) > 50`) on the Fisher LDA-projected delta distribution; suppressed when `Pattern.label_aware_n_pos < 30` to avoid noise from undersampled positive classes.
|
|
16
|
+
- `GDSEngine.decompose_displacement(delta, label_direction)` — pure-math helper that splits a polygon delta vector along (`intrinsic`) and orthogonal to (`extrinsic`) any label-discriminating axis, satisfying `intrinsic^2 + extrinsic^2 == ||delta||^2`. Pattern-level means `intrinsic_displacement_mean` / `extrinsic_displacement_mean` are persisted on `Pattern` at build time when label-aware calibration fires.
|
|
17
|
+
- `Pattern.label_aware_n_pos` / `Pattern.label_aware_n_neg` — positive / negative labelled sample counts captured at build time, co-populated with `label_aware_calibration`.
|
|
18
|
+
- `GDSNavigator.chain_signed_confidence_rollup(chain_id, chain_pattern=..., anchor_pattern=...)` — chain-level reliability rollup derived from per-member `signed_confidence` ranking on the anchor pattern. Returns `chain_mean_signed_confidence`, `chain_n_low_confidence_members`, `chain_n_single_dim_driven_members`, and `chain_confidence_verdict` ∈ {high, medium, low, label-aware-unavailable}. Falls back cleanly with verdict `"label-aware-unavailable"` and null numeric fields when the anchor pattern lacks `label_aware_calibration`.
|
|
19
|
+
- `GDSNavigator.aggregate` per-entity result rows gain `anomaly_rate: float | null` — fraction of an entity's events flagged anomalous (`delta_norm >= theta_diag`). `null` when entity has zero events or pattern lacks anomaly threshold.
|
|
20
|
+
- `GDSNavigator.sphere_overview` gains `cross_pattern_discrepancy: dict | null` — pairwise Jaccard overlap of anomalous primary_keys across cover-overlapping patterns. Lists pairs with `pattern_a`, `pattern_b`, `shared_line`, the four anomaly-bucket counts, and `jaccard_anomaly_overlap`. `null` on spheres with fewer than two cover-overlapping patterns.
|
|
21
|
+
- `GDSNavigator.find_anomalies(..., sample_size: int | None = None, boundary_aware: bool = False)` — opt-in sampling for the in-process scan path. `sample_size` caps geometry rows scored; `boundary_aware=True` allocates half the budget to entities within the `[0.8 * theta_norm, 1.2 * theta_norm]` band around the decision threshold, the other half drawn from the rest of the population. Defaults preserve the existing full-scan behaviour. `boundary_aware=True` requires `sample_size` to be set.
|
|
22
|
+
- `engine.topology.classify_trajectory(solid_table, *, sample_size=10000)` — wraps `trajectory_continuous_score` with a deterministic 4-category classifier (`outlier` / `lagging` / `leading` / `typical`). Combines DTW distance against the population-median trajectory with a first-derivative slope comparison via `scipy.stats.linregress`. Returns a list of `{primary_key, dtw_distance, category, category_evidence}` per entity, with `category_evidence` carrying the signed deviation that drove the classification.
|
|
23
|
+
- `GDSNavigator.classify_trajectory(primary_key, pattern_id, sample_size=10000)` — per-entity navigator wrapper that streams temporal `shape_snapshot` rows, projects to `delta_snapshot` via the pattern's `(mu, sigma_diag)`, and returns the classification for the requested entity.
|
|
24
|
+
- `sphere.yaml` `graph_features.features:` accepts a new token — `community_id` — Louvain modularity-optimizing community detection on the anchor-pattern graph. `igraph.Graph.community_multilevel` when igraph is installed; `networkx.algorithms.community.louvain_communities` otherwise; gracefully emits an empty mapping (caller fills nulls) when neither backend is available. Community IDs are renumbered so the largest community is `0`.
|
|
25
|
+
- `graph_features.features:` `pagerank` and `connected_component` now route through pure-numpy helpers (damped PageRank with damping `0.85`, max `100` iterations, L2 convergence threshold `1e-6`; Union-Find connected components with path compression and union-by-rank). No external graph library is required for these two; igraph remains optional and is used only for the other features in the block.
|
|
26
|
+
- `engine.graph.compute_pagerank`, `compute_louvain_community`, `compute_connected_components`, `compute_from_adjacency` — pure helpers exposed for direct use on an `AdjacencyIndex`. PageRank parameters (`damping`, `max_iter`, `tol`) are configurable per call.
|
|
27
|
+
- `GDSNavigator.find_calibration_influencers(..., auto_discover: bool = False, auto_k: int = 10)` — auto-discovery mode runs k-means on the pattern's geometry, surfaces top-K candidate influencers from cluster centroids, then ranks each by existing μ-impact algorithm. Returns the existing shape extended with per-candidate `cluster_size` and `cluster_centroid_distance`. Default `auto_discover=False` preserves manual mode requiring caller-supplied candidate_keys.
|
|
28
|
+
- `GDSNavigator.calibration_influencer_history(primary_key, *, pattern_id)` — per-epoch μ-impact history for a known influencer. Lazily populated write-through cache at `_gds_meta/calibration_history/<pattern>/influencer_<primary_key>.json`. Returns chronological list of `{epoch, calibrated_at, mu_impact, delta_norm_impact}` entries. Empty list + warning when cache absent.
|
|
29
|
+
- `engine.topology.local_trajectory_shape(delta_norms)` — pure-function classifier that categorises an entity's own `delta_norm` time-series as `arch` / `V` / `linear` / `flat` without requiring a population reference. Returns `None` when fewer than three samples are available. Used by `dive_solid` to surface trajectory shape on the entity's temporal slices.
|
|
30
|
+
- `Navigator.find_similar_entities(..., with_neighbor_anomaly=False)` — opt-in flag triggers a single point lookup over the returned top-N neighbour keys and populates the `SimilarityResult.is_anomaly_map` sidecar with stored anomaly flags. Existing 2-tuple iteration is preserved.
|
|
31
|
+
|
|
10
32
|
## [0.7.2] — 2026-05-21
|
|
11
33
|
|
|
12
34
|
### Added
|
|
@@ -26,7 +26,7 @@ For purposes of this License:
|
|
|
26
26
|
- **Internal use** within an organization is not a competitive offering. Your
|
|
27
27
|
organization includes entities under common control.
|
|
28
28
|
|
|
29
|
-
**Change Date:** 2029-05-
|
|
29
|
+
**Change Date:** 2029-05-27
|
|
30
30
|
|
|
31
31
|
**Change License:** Apache License, Version 2.0
|
|
32
32
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hypertopos
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.3
|
|
4
4
|
Summary: Understand the structure of your data — without training machine learning models
|
|
5
5
|
Project-URL: Homepage, https://github.com/hypertopos/hypertopos-py
|
|
6
6
|
Project-URL: Documentation, https://github.com/hypertopos/hypertopos-py/tree/main/docs
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
[](https://arrow.apache.org/docs/python/)
|
|
10
10
|
[](https://github.com/lance-format/lance)
|
|
11
11
|
[](https://modelcontextprotocol.io)
|
|
12
|
-
[](pyproject.toml)
|
|
13
13
|
|
|
14
14
|
hypertopos is not a database, and not a machine learning model. It is a layer that turns relational data into a coordinate system where every entity gets a position derived from its relationships and the population around it.
|
|
15
15
|
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
Detector composition and counterfactual primitives on native Lance MVCC — multi-detector consensus, per-edge / joint / per-counterparty influence math, topological cycle persistence, multi-resolution FDR, and the entity-side investigation orchestrator land on a modernized Lance 6.0 storage substrate with sphere format 3.0.
|
|
8
8
|
|
|
9
9
|
- **0.7.0** — Lance 4.0 → 6.0 bump; sphere format 2.4 → 3.0 (breaking — single Lance dataset per pattern, calibration epochs tagged `epoch_<N>`, no migration tool); Wilson HMP combiner + `combine_anomaly_pvalues` + `classify_detector_consensus` over five orthogonal anomaly axes; counterfactual family (`simulate_edge_removal`, `select_minimal_joint_edge_removal`, `simulate_counterparty_removal`); `find_topological_anomalies` H_1 cycle persistence + `find_topological_trajectory_anomalies`; `find_graph_geometry_tension` 2×2 behavioural-vs-graph cross-tab; multi-resolution FDR with `Pattern.fdr_hierarchy` + `fdr_temporal_hierarchy`; per-dim FDR axis + `rank_by="min_q_per_dim"`; `investigate_entity` orchestrator; `edge_curvature_frc` Forman-Ricci edge dimension; `reliability_flags` triage on five primitive outputs; `composite_risk` switches Fisher → HMP.
|
|
10
|
+
- **0.7.1** — label-aware calibration foundation: `label_audit:` YAML block, per-class μ/σ + Cohen's d + Fisher LDA direction on `Pattern`, `delta_norm_signed` Lance column, `audit_pattern_dims` full per-class field path, Levene's test homoscedasticity diagnostic in `dim_quality_warnings`.
|
|
11
|
+
- **0.7.2** — composition orchestrators on the 0.7.1 foundation: `find_anomalies(rank_by="signed_confidence")`, `chain_full_loop_summary` chain-side mirror of `investigate_entity`, `audit_label_alignment` Fisher LDA AUROC sibling to `audit_pattern_dims`, `kind_mismatch` warning + `recommended_action="kind_mismatch_review"`, `dive_solid(counterfactual_frozen_population=True)`, `Chain.to_dict() edge_potentials`, `changelog.d/` per-PR fragments convention.
|
|
12
|
+
- **0.7.3** — detector-composition continuation + investigation-orchestrator surface: per-dim AUROC + intrinsic / extrinsic decomposition on `audit_pattern_dims`, `signed_tail_concentration` warning, chain reliability rollup on `chain_full_loop_summary`, `find_anomalies(sample_size, boundary_aware)`, `classify_trajectory` + `local_trajectory_shape`, `sphere_overview.cross_pattern_discrepancy` + per-row `anomaly_rate`, `community_id` graph feature + pure-NumPy `pagerank` / `connected_component`, `find_calibration_influencers(auto_discover=True)` + `calibration_influencer_history`, `π12_attract_regime_change.near_data_boundary` flag, `find_similar_entities(with_neighbor_anomaly=True)`, `passive_scan` interpretation hint, `dive_solid.trajectory_shape`.
|
|
10
13
|
|
|
11
14
|
See [CHANGELOG](CHANGELOG.md) for the full feature list.
|
|
12
15
|
|
|
@@ -624,6 +624,15 @@ class PatternBuildResult:
|
|
|
624
624
|
# into sphere.json as ``label_aware_calibration`` and hydrated back
|
|
625
625
|
# into ``Pattern.label_aware_calibration`` by the storage reader.
|
|
626
626
|
label_aware_calibration: dict[str, Any] | None = None
|
|
627
|
+
# Co-populated with ``label_aware_calibration`` — positive / negative
|
|
628
|
+
# labelled sample counts and the percentiles + intrinsic / extrinsic
|
|
629
|
+
# displacement means computed from the signed delta projection. ``None``
|
|
630
|
+
# on patterns without label-aware calibration.
|
|
631
|
+
label_aware_n_pos: int | None = None
|
|
632
|
+
label_aware_n_neg: int | None = None
|
|
633
|
+
signed_percentiles: dict[str, float] | None = None
|
|
634
|
+
intrinsic_displacement_mean: float | None = None
|
|
635
|
+
extrinsic_displacement_mean: float | None = None
|
|
627
636
|
|
|
628
637
|
|
|
629
638
|
@dataclass
|
|
@@ -822,7 +831,8 @@ class GDSBuilder:
|
|
|
822
831
|
) -> dict[str, Any] | None:
|
|
823
832
|
"""Fit label-aware per-dim calibration when the pattern opts in.
|
|
824
833
|
|
|
825
|
-
Returns
|
|
834
|
+
Returns a bundle ``{"per_dim": {dim_label: DimCalibration},
|
|
835
|
+
"n_pos": int, "n_neg": int, "direction": np.ndarray}`` and
|
|
826
836
|
registers the global Fisher LDA direction in
|
|
827
837
|
``self._label_aware_directions[pat.pattern_id]`` so the downstream
|
|
828
838
|
geometry pass can populate ``delta_norm_signed``. Returns ``None``
|
|
@@ -924,10 +934,16 @@ class GDSBuilder:
|
|
|
924
934
|
)
|
|
925
935
|
return None
|
|
926
936
|
|
|
927
|
-
|
|
937
|
+
direction_vec = np.asarray(
|
|
928
938
|
result.signed_direction_vector, dtype=np.float32,
|
|
929
939
|
)
|
|
930
|
-
|
|
940
|
+
self._label_aware_directions[pat.pattern_id] = direction_vec
|
|
941
|
+
return {
|
|
942
|
+
"per_dim": dict(result.per_dim),
|
|
943
|
+
"n_pos": int(result.n_pos),
|
|
944
|
+
"n_neg": int(result.n_neg),
|
|
945
|
+
"direction": direction_vec,
|
|
946
|
+
}
|
|
931
947
|
|
|
932
948
|
def add_line(
|
|
933
949
|
self,
|
|
@@ -2916,6 +2932,105 @@ class GDSBuilder:
|
|
|
2916
2932
|
|
|
2917
2933
|
return result
|
|
2918
2934
|
|
|
2935
|
+
@staticmethod
|
|
2936
|
+
def _unpack_lac_bundle(
|
|
2937
|
+
lac_bundle: dict[str, Any] | None,
|
|
2938
|
+
deltas: np.ndarray | None,
|
|
2939
|
+
) -> dict[str, Any]:
|
|
2940
|
+
"""Translate the ``_run_label_aware_calibration`` bundle into
|
|
2941
|
+
PBR kwargs.
|
|
2942
|
+
|
|
2943
|
+
Returns a dict with keys ``label_aware_calibration``,
|
|
2944
|
+
``label_aware_n_pos``, ``label_aware_n_neg``,
|
|
2945
|
+
``signed_percentiles``, ``intrinsic_displacement_mean``,
|
|
2946
|
+
``extrinsic_displacement_mean`` — every value ``None`` when the
|
|
2947
|
+
bundle is missing or the delta matrix is unavailable, ready to
|
|
2948
|
+
splat into PatternBuildResult.
|
|
2949
|
+
"""
|
|
2950
|
+
if lac_bundle is None or deltas is None or deltas.ndim != 2:
|
|
2951
|
+
return {
|
|
2952
|
+
"label_aware_calibration": None,
|
|
2953
|
+
"label_aware_n_pos": None,
|
|
2954
|
+
"label_aware_n_neg": None,
|
|
2955
|
+
"signed_percentiles": None,
|
|
2956
|
+
"intrinsic_displacement_mean": None,
|
|
2957
|
+
"extrinsic_displacement_mean": None,
|
|
2958
|
+
}
|
|
2959
|
+
direction = lac_bundle.get("direction")
|
|
2960
|
+
if direction is None or direction.shape[0] != deltas.shape[1]:
|
|
2961
|
+
return {
|
|
2962
|
+
"label_aware_calibration": lac_bundle.get("per_dim"),
|
|
2963
|
+
"label_aware_n_pos": lac_bundle.get("n_pos"),
|
|
2964
|
+
"label_aware_n_neg": lac_bundle.get("n_neg"),
|
|
2965
|
+
"signed_percentiles": None,
|
|
2966
|
+
"intrinsic_displacement_mean": None,
|
|
2967
|
+
"extrinsic_displacement_mean": None,
|
|
2968
|
+
}
|
|
2969
|
+
signed_p, intr_mean, extr_mean = (
|
|
2970
|
+
GDSBuilder._compute_signed_artefacts(deltas, direction)
|
|
2971
|
+
)
|
|
2972
|
+
return {
|
|
2973
|
+
"label_aware_calibration": lac_bundle.get("per_dim"),
|
|
2974
|
+
"label_aware_n_pos": lac_bundle.get("n_pos"),
|
|
2975
|
+
"label_aware_n_neg": lac_bundle.get("n_neg"),
|
|
2976
|
+
"signed_percentiles": signed_p or None,
|
|
2977
|
+
"intrinsic_displacement_mean": intr_mean,
|
|
2978
|
+
"extrinsic_displacement_mean": extr_mean,
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2981
|
+
@staticmethod
|
|
2982
|
+
def _compute_signed_artefacts(
|
|
2983
|
+
deltas: np.ndarray,
|
|
2984
|
+
direction: np.ndarray,
|
|
2985
|
+
) -> tuple[dict[str, float], float, float]:
|
|
2986
|
+
"""Derive signed-projection artefacts from deltas + Fisher direction.
|
|
2987
|
+
|
|
2988
|
+
Computes:
|
|
2989
|
+
|
|
2990
|
+
- ``signed_percentiles``: dict with keys ``p1``, ``p5``, ``p50``,
|
|
2991
|
+
``p95``, ``p99`` of the signed projection
|
|
2992
|
+
``deltas @ direction_unit``. The LDA fit returns a unit-norm
|
|
2993
|
+
direction so today's caller passes the same vector the
|
|
2994
|
+
geometry pass uses to populate ``delta_norm_signed``; we still
|
|
2995
|
+
normalise defensively so any future non-unit caller gets the
|
|
2996
|
+
contract everyone else assumes.
|
|
2997
|
+
- ``intrinsic_displacement_mean``: mean of
|
|
2998
|
+
``|delta . direction_unit|`` across rows. Magnitude of motion
|
|
2999
|
+
along the label axis.
|
|
3000
|
+
- ``extrinsic_displacement_mean``: mean of
|
|
3001
|
+
``sqrt(||delta||^2 - intrinsic^2)`` across rows. Magnitude of
|
|
3002
|
+
motion orthogonal to the label axis.
|
|
3003
|
+
|
|
3004
|
+
Returns empty percentiles dict + (0.0, 0.0) when the delta matrix
|
|
3005
|
+
is empty or the direction is the zero vector.
|
|
3006
|
+
"""
|
|
3007
|
+
if deltas.ndim != 2 or deltas.shape[0] == 0:
|
|
3008
|
+
return {}, 0.0, 0.0
|
|
3009
|
+
d = deltas.astype(np.float64, copy=False)
|
|
3010
|
+
v = direction.astype(np.float64, copy=False)
|
|
3011
|
+
v_norm = float(np.linalg.norm(v))
|
|
3012
|
+
if v_norm <= 0.0:
|
|
3013
|
+
return {}, 0.0, 0.0
|
|
3014
|
+
v = v / v_norm
|
|
3015
|
+
projected = d @ v
|
|
3016
|
+
percs = np.percentile(projected, [1, 5, 50, 95, 99])
|
|
3017
|
+
signed_percentiles = {
|
|
3018
|
+
"p1": round(float(percs[0]), 4),
|
|
3019
|
+
"p5": round(float(percs[1]), 4),
|
|
3020
|
+
"p50": round(float(percs[2]), 4),
|
|
3021
|
+
"p95": round(float(percs[3]), 4),
|
|
3022
|
+
"p99": round(float(percs[4]), 4),
|
|
3023
|
+
}
|
|
3024
|
+
intrinsic = np.abs(projected)
|
|
3025
|
+
row_norms_sq = np.einsum("ij,ij->i", d, d)
|
|
3026
|
+
residual_sq = np.maximum(row_norms_sq - intrinsic * intrinsic, 0.0)
|
|
3027
|
+
extrinsic = np.sqrt(residual_sq)
|
|
3028
|
+
return (
|
|
3029
|
+
signed_percentiles,
|
|
3030
|
+
float(intrinsic.mean()),
|
|
3031
|
+
float(extrinsic.mean()),
|
|
3032
|
+
)
|
|
3033
|
+
|
|
2919
3034
|
def _compute_dim_percentiles(
|
|
2920
3035
|
self,
|
|
2921
3036
|
entity_line_id: str,
|
|
@@ -3221,6 +3336,20 @@ class GDSBuilder:
|
|
|
3221
3336
|
}
|
|
3222
3337
|
for label, dc in pbr.label_aware_calibration.items()
|
|
3223
3338
|
}
|
|
3339
|
+
if pbr.label_aware_n_pos is not None:
|
|
3340
|
+
pat_dict["label_aware_n_pos"] = int(pbr.label_aware_n_pos)
|
|
3341
|
+
if pbr.label_aware_n_neg is not None:
|
|
3342
|
+
pat_dict["label_aware_n_neg"] = int(pbr.label_aware_n_neg)
|
|
3343
|
+
if pbr.signed_percentiles:
|
|
3344
|
+
pat_dict["signed_percentiles"] = pbr.signed_percentiles
|
|
3345
|
+
if pbr.intrinsic_displacement_mean is not None:
|
|
3346
|
+
pat_dict["intrinsic_displacement_mean"] = float(
|
|
3347
|
+
pbr.intrinsic_displacement_mean,
|
|
3348
|
+
)
|
|
3349
|
+
if pbr.extrinsic_displacement_mean is not None:
|
|
3350
|
+
pat_dict["extrinsic_displacement_mean"] = float(
|
|
3351
|
+
pbr.extrinsic_displacement_mean,
|
|
3352
|
+
)
|
|
3224
3353
|
if pat.description:
|
|
3225
3354
|
pat_dict["description"] = pat.description
|
|
3226
3355
|
if pat.fdr_hierarchy:
|
|
@@ -3767,7 +3896,7 @@ class GDSBuilder:
|
|
|
3767
3896
|
edge_dim_agg_labels=ps.edge_dim_agg_labels,
|
|
3768
3897
|
),
|
|
3769
3898
|
edge_dim_names=ps.edge_dim_names,
|
|
3770
|
-
|
|
3899
|
+
**self._unpack_lac_bundle(lac, ps.deltas),
|
|
3771
3900
|
)
|
|
3772
3901
|
self._write_calibration_epoch_for_pattern(pat, pbr)
|
|
3773
3902
|
return pat_id, pbr
|
|
@@ -4671,7 +4800,7 @@ class GDSBuilder:
|
|
|
4671
4800
|
edge_dim_agg_labels=ps.edge_dim_agg_labels,
|
|
4672
4801
|
),
|
|
4673
4802
|
edge_dim_names=ps.edge_dim_names,
|
|
4674
|
-
|
|
4803
|
+
**self._unpack_lac_bundle(lac, ps.deltas),
|
|
4675
4804
|
)
|
|
4676
4805
|
|
|
4677
4806
|
def _build_shape_chunk(
|
|
@@ -833,15 +833,39 @@ def compute_graph_features(
|
|
|
833
833
|
# --- Graph algorithm features (computed on AdjacencyIndex) ---
|
|
834
834
|
_ALGO_FEATURES = {
|
|
835
835
|
"pagerank", "connected_component", "clustering_coefficient",
|
|
836
|
-
"community", "betweenness",
|
|
836
|
+
"community", "community_id", "betweenness",
|
|
837
837
|
}
|
|
838
838
|
needed_algos = _ALGO_FEATURES & set(features)
|
|
839
839
|
if needed_algos:
|
|
840
|
-
from hypertopos.engine.graph_algorithms import compute_all_from_lists
|
|
841
|
-
|
|
842
840
|
f_list = edges["_f"].to_pylist()
|
|
843
841
|
t_list = edges["_t"].to_pylist()
|
|
844
|
-
|
|
842
|
+
|
|
843
|
+
# pagerank / community_id / connected_component go through engine.graph
|
|
844
|
+
# (pure-numpy / igraph→NetworkX fallback chain). Remaining algos
|
|
845
|
+
# (clustering_coefficient, betweenness, label-prop "community") stay
|
|
846
|
+
# on the igraph fast path in graph_algorithms.
|
|
847
|
+
algo_results: dict[str, dict] = {}
|
|
848
|
+
_GRAPH_HELPERS = {"pagerank", "community_id", "connected_component"}
|
|
849
|
+
helper_algos = _GRAPH_HELPERS & needed_algos
|
|
850
|
+
if helper_algos:
|
|
851
|
+
from hypertopos.engine.adjacency import AdjacencyIndex
|
|
852
|
+
from hypertopos.engine.graph import compute_from_adjacency
|
|
853
|
+
n_edges_lance = len(f_list)
|
|
854
|
+
adj_for_helpers = AdjacencyIndex.from_edge_lists(
|
|
855
|
+
f_list, t_list,
|
|
856
|
+
[0.0] * n_edges_lance, [0.0] * n_edges_lance,
|
|
857
|
+
[f"_e{i}" for i in range(n_edges_lance)],
|
|
858
|
+
)
|
|
859
|
+
algo_results.update(
|
|
860
|
+
compute_from_adjacency(adj_for_helpers, helper_algos),
|
|
861
|
+
)
|
|
862
|
+
|
|
863
|
+
remaining_algos = needed_algos - _GRAPH_HELPERS
|
|
864
|
+
if remaining_algos:
|
|
865
|
+
from hypertopos.engine.graph_algorithms import compute_all_from_lists
|
|
866
|
+
algo_results.update(
|
|
867
|
+
compute_all_from_lists(f_list, t_list, remaining_algos),
|
|
868
|
+
)
|
|
845
869
|
|
|
846
870
|
_EDGE_MAX_OVERRIDES = {
|
|
847
871
|
"clustering_coefficient": 1,
|
|
@@ -853,7 +877,7 @@ def compute_graph_features(
|
|
|
853
877
|
idx = key_to_idx.get(k)
|
|
854
878
|
if idx is not None:
|
|
855
879
|
vals[idx] = float(v)
|
|
856
|
-
if feat_name in ("connected_component", "community"):
|
|
880
|
+
if feat_name in ("connected_component", "community", "community_id"):
|
|
857
881
|
n_unique = len(set(scores.values())) if scores else 1
|
|
858
882
|
edge_max = max(n_unique - 1, 1)
|
|
859
883
|
elif feat_name in _EDGE_MAX_OVERRIDES:
|
|
@@ -898,7 +922,7 @@ def compute_graph_features_temporal(
|
|
|
898
922
|
# PageRank/betweenness is meaningless and extremely expensive)
|
|
899
923
|
STATIC_ONLY_FEATURES = {
|
|
900
924
|
"pagerank", "connected_component", "clustering_coefficient",
|
|
901
|
-
"community", "betweenness",
|
|
925
|
+
"community", "community_id", "betweenness",
|
|
902
926
|
}
|
|
903
927
|
fast_indices = [i for i, f in enumerate(features) if f in FAST_FEATURES]
|
|
904
928
|
need_recip = "reciprocity" in features
|
|
@@ -638,6 +638,18 @@ def aggregate(
|
|
|
638
638
|
f"with a '{group_by_line}' edge. "
|
|
639
639
|
f"Use anomaly_summary for population-wide totals."
|
|
640
640
|
)
|
|
641
|
+
# Per-group anomaly_rate enrichment also runs on the reversed-scan
|
|
642
|
+
# fast path so the field is uniformly present regardless of which
|
|
643
|
+
# execution branch produced the counts.
|
|
644
|
+
if _should_emit_per_group_anomaly_rate(
|
|
645
|
+
False, metric, group_by_property, pivot_event_field,
|
|
646
|
+
group_by_line_2, distinct, result,
|
|
647
|
+
):
|
|
648
|
+
_enrich_per_group_anomaly_rate(
|
|
649
|
+
reader, event_pattern_id, pattern, group_by_line,
|
|
650
|
+
edge_col="entity_keys",
|
|
651
|
+
result=result,
|
|
652
|
+
)
|
|
641
653
|
return result
|
|
642
654
|
except Exception:
|
|
643
655
|
pass # Fall through to read_geometry path
|
|
@@ -2185,4 +2197,105 @@ def aggregate(
|
|
|
2185
2197
|
row["total_events"] = total
|
|
2186
2198
|
row["anomaly_rate"] = round(row["count"] / total, 4) if total > 0 else 0.0
|
|
2187
2199
|
|
|
2200
|
+
# --- Per-group anomaly_rate enrichment (inverse of _anomaly_rate_requested) ---
|
|
2201
|
+
# When metric='count' without an is_anomaly filter, each row['count'] is the
|
|
2202
|
+
# total event count for the group. Surface the share of anomalous events by
|
|
2203
|
+
# doing a second read filtered to is_anomaly=True and dividing.
|
|
2204
|
+
# Skipped when the existing block already wrote anomaly_rate (above), when
|
|
2205
|
+
# the metric is not 'count' (rate is undefined for sum/avg/median), when
|
|
2206
|
+
# composite keys are used (group_by_property / pivot_event_field /
|
|
2207
|
+
# group_by_line_2 — the row key is not a single group_by_line edge target),
|
|
2208
|
+
# and when no rows were returned.
|
|
2209
|
+
if _should_emit_per_group_anomaly_rate(
|
|
2210
|
+
_anomaly_rate_requested, metric, group_by_property, pivot_event_field,
|
|
2211
|
+
group_by_line_2, distinct, result,
|
|
2212
|
+
):
|
|
2213
|
+
_enrich_per_group_anomaly_rate(
|
|
2214
|
+
reader, event_pattern_id, pattern, group_by_line,
|
|
2215
|
+
edge_col=("entity_keys" if "entity_keys" in geo.schema.names else "edges"),
|
|
2216
|
+
result=result,
|
|
2217
|
+
)
|
|
2218
|
+
|
|
2188
2219
|
return result
|
|
2220
|
+
|
|
2221
|
+
|
|
2222
|
+
def _should_emit_per_group_anomaly_rate(
|
|
2223
|
+
anomaly_rate_requested: bool,
|
|
2224
|
+
metric: str,
|
|
2225
|
+
group_by_property: str | None,
|
|
2226
|
+
pivot_event_field: str | None,
|
|
2227
|
+
group_by_line_2: str | None,
|
|
2228
|
+
distinct: bool,
|
|
2229
|
+
result: dict,
|
|
2230
|
+
) -> bool:
|
|
2231
|
+
"""Predicate for the inverse anomaly_rate enrichment.
|
|
2232
|
+
|
|
2233
|
+
Triggers when the row's ``count`` is the total event count for the group
|
|
2234
|
+
(per-row composite keys excluded) and the existing "filter-by-is_anomaly"
|
|
2235
|
+
block has not already attached ``anomaly_rate`` to each row.
|
|
2236
|
+
"""
|
|
2237
|
+
return (
|
|
2238
|
+
not anomaly_rate_requested
|
|
2239
|
+
and metric == "count"
|
|
2240
|
+
and not group_by_property
|
|
2241
|
+
and not pivot_event_field
|
|
2242
|
+
and not group_by_line_2
|
|
2243
|
+
and not distinct
|
|
2244
|
+
and bool(result.get("results"))
|
|
2245
|
+
)
|
|
2246
|
+
|
|
2247
|
+
|
|
2248
|
+
def _enrich_per_group_anomaly_rate(
|
|
2249
|
+
reader: Any,
|
|
2250
|
+
event_pattern_id: str,
|
|
2251
|
+
pattern: Any,
|
|
2252
|
+
group_by_line: str,
|
|
2253
|
+
*,
|
|
2254
|
+
edge_col: str,
|
|
2255
|
+
result: dict,
|
|
2256
|
+
) -> None:
|
|
2257
|
+
"""Attach ``anomaly_rate = n_anomalous_in_group / total_in_group`` per row.
|
|
2258
|
+
|
|
2259
|
+
Performs one extra geometry read scoped to two columns (``is_anomaly`` +
|
|
2260
|
+
edge column) with a Lance ``is_anomaly = true`` filter pushed down via
|
|
2261
|
+
the bitmap index so only anomalous rows materialise. Counts anomalous
|
|
2262
|
+
edges per group and divides into the row's existing ``count``.
|
|
2263
|
+
``anomaly_rate`` is ``None`` when the group's total count is zero.
|
|
2264
|
+
The field is omitted when the geometry on disk has no ``is_anomaly``
|
|
2265
|
+
column (degraded / pre-schema spheres) — no zero-div crash, no
|
|
2266
|
+
spurious value.
|
|
2267
|
+
"""
|
|
2268
|
+
try:
|
|
2269
|
+
raw = reader.read_geometry(
|
|
2270
|
+
event_pattern_id, pattern.version,
|
|
2271
|
+
columns=["is_anomaly", edge_col],
|
|
2272
|
+
filter="is_anomaly = true",
|
|
2273
|
+
)
|
|
2274
|
+
except Exception:
|
|
2275
|
+
# Reader rejected the filter syntax (mock / pre-Lance reader) — fall
|
|
2276
|
+
# back to a no-filter read; the in-process predicate below still
|
|
2277
|
+
# narrows to anomalous rows.
|
|
2278
|
+
raw = reader.read_geometry(
|
|
2279
|
+
event_pattern_id, pattern.version,
|
|
2280
|
+
columns=["is_anomaly", edge_col],
|
|
2281
|
+
)
|
|
2282
|
+
if "is_anomaly" not in raw.schema.names:
|
|
2283
|
+
# Column was projected out (degraded / pre-schema sphere) —
|
|
2284
|
+
# cannot compute the rate. Leave rows unannotated.
|
|
2285
|
+
return
|
|
2286
|
+
import pyarrow.compute as pc
|
|
2287
|
+
# Apply the predicate in-process too — Lance may or may not honour the
|
|
2288
|
+
# pushed-down filter (mock readers silently ignore it). This guarantees
|
|
2289
|
+
# ``anom_only`` is strictly the anomalous subset regardless.
|
|
2290
|
+
anom_only = raw.filter(pc.equal(raw["is_anomaly"], True))
|
|
2291
|
+
anom_per_group, _ = _vectorized_count_with_warning(
|
|
2292
|
+
anom_only, group_by_line, geometry_filters=None, relations=pattern.relations,
|
|
2293
|
+
)
|
|
2294
|
+
for row in result.get("results", []):
|
|
2295
|
+
key = row.get("key")
|
|
2296
|
+
total = row.get("count")
|
|
2297
|
+
if key is None or total is None or total == 0:
|
|
2298
|
+
row["anomaly_rate"] = None
|
|
2299
|
+
continue
|
|
2300
|
+
n_anom = anom_per_group.get(key, 0)
|
|
2301
|
+
row["anomaly_rate"] = round(n_anom / total, 4)
|
|
@@ -178,6 +178,62 @@ class GDSEngine:
|
|
|
178
178
|
delta = delta * pattern.dimension_weights
|
|
179
179
|
return delta
|
|
180
180
|
|
|
181
|
+
@staticmethod
|
|
182
|
+
def decompose_displacement(
|
|
183
|
+
delta: np.ndarray,
|
|
184
|
+
label_direction: np.ndarray,
|
|
185
|
+
) -> dict[str, float]:
|
|
186
|
+
"""Decompose a single delta vector along the label-discriminating axis.
|
|
187
|
+
|
|
188
|
+
Splits ``delta`` into two scalar magnitudes:
|
|
189
|
+
|
|
190
|
+
- ``intrinsic`` = ``|delta . label_direction_unit|`` — magnitude of
|
|
191
|
+
the projection onto the unit-norm label direction. Captures how
|
|
192
|
+
far the entity moved along the axis that separates labelled
|
|
193
|
+
classes.
|
|
194
|
+
- ``extrinsic`` = ``sqrt(||delta||^2 - intrinsic^2)`` — magnitude of
|
|
195
|
+
the residual component orthogonal to the label axis.
|
|
196
|
+
|
|
197
|
+
Identity: ``intrinsic^2 + extrinsic^2 == ||delta||^2`` to floating
|
|
198
|
+
precision. ``label_direction`` is normalised internally — callers
|
|
199
|
+
may pass any non-zero vector.
|
|
200
|
+
|
|
201
|
+
This is the label-axis decomposition of a single polygon's delta
|
|
202
|
+
vector. It is NOT the calibration-drift intrinsic/extrinsic
|
|
203
|
+
decomposition (see ``_compute_intrinsic_extrinsic_decomposition``)
|
|
204
|
+
which splits drift between two calibration epochs into a
|
|
205
|
+
within-entity-shape component vs a coordinate-system component —
|
|
206
|
+
same vocabulary, different math.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
delta: 1-D delta vector for one polygon.
|
|
210
|
+
label_direction: 1-D vector of the same length, must be
|
|
211
|
+
non-zero. Direction sense is irrelevant (``intrinsic``
|
|
212
|
+
is absolute-valued).
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
``{"intrinsic": float, "extrinsic": float}``. Both
|
|
216
|
+
non-negative. When ``label_direction`` is the zero vector
|
|
217
|
+
both values are 0.0 (no axis to project onto, no decomposition
|
|
218
|
+
defined).
|
|
219
|
+
"""
|
|
220
|
+
d = np.asarray(delta, dtype=np.float64).ravel()
|
|
221
|
+
ld = np.asarray(label_direction, dtype=np.float64).ravel()
|
|
222
|
+
if d.shape != ld.shape:
|
|
223
|
+
raise ValueError(
|
|
224
|
+
f"delta and label_direction shape mismatch: "
|
|
225
|
+
f"{d.shape} vs {ld.shape}",
|
|
226
|
+
)
|
|
227
|
+
ld_norm = float(np.linalg.norm(ld))
|
|
228
|
+
if ld_norm <= 0.0:
|
|
229
|
+
return {"intrinsic": 0.0, "extrinsic": 0.0}
|
|
230
|
+
ld_unit = ld / ld_norm
|
|
231
|
+
intrinsic = float(abs(np.dot(d, ld_unit)))
|
|
232
|
+
d_sq = float(np.dot(d, d))
|
|
233
|
+
residual_sq = max(d_sq - intrinsic * intrinsic, 0.0)
|
|
234
|
+
extrinsic = float(np.sqrt(residual_sq))
|
|
235
|
+
return {"intrinsic": intrinsic, "extrinsic": extrinsic}
|
|
236
|
+
|
|
181
237
|
def _polygon_to_shape_vector(
|
|
182
238
|
self, polygon: Polygon, pattern: Pattern
|
|
183
239
|
) -> np.ndarray:
|