hypertopos 0.3.2__tar.gz → 0.3.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.3.2 → hypertopos-0.3.3}/CHANGELOG.md +9 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/PKG-INFO +1 -1
- {hypertopos-0.3.2 → hypertopos-0.3.3}/README.md +1 -1
- hypertopos-0.3.3/ROADMAP.md +84 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/SECURITY.md +1 -1
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/api-reference.md +1 -1
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/engine/geometry.py +70 -9
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/navigation/navigator.py +41 -6
- {hypertopos-0.3.2 → hypertopos-0.3.3}/pyproject.toml +1 -1
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_navigation.py +182 -3
- hypertopos-0.3.2/ROADMAP.md +0 -104
- {hypertopos-0.3.2 → hypertopos-0.3.3}/.github/workflows/publish-pypi.yml +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/.gitignore +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/LICENSE.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/README.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/architecture.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/LICENSE +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/README.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/README.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/SUMMARY.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-settings.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-settings.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-settings.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-settings.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-settings.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-settings.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/sphere/prepare_accounts.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/sphere/prepare_cpty_banks.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/sphere/prepare_districts.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/sphere/prepare_loan_accounts.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/sphere/prepare_operations.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/sphere/prepare_orders.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/sphere/prepare_transactions.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/sphere/prepare_tx_types.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/berka/sphere/sphere.yaml +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/ibm-aml/README.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/ibm-aml/_layer2_cross_eval.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/ibm-aml/_layer3_cross_eval.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_accounts.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_currencies.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_payment_formats.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_transactions.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/ibm-aml/hi_small_sphere/sphere.yaml +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/ibm-aml/layer1_passive_scan.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/ibm-aml/layer2_xgboost_sweep.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/ibm-aml/layer3_typology_filter.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/ibm-aml/li_small_sphere/prepare_accounts.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/ibm-aml/li_small_sphere/prepare_currencies.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/ibm-aml/li_small_sphere/prepare_payment_formats.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/ibm-aml/li_small_sphere/prepare_transactions.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/ibm-aml/li_small_sphere/sphere.yaml +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/ibm-aml/passive-scan-calibration.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/nyc-taxi/README.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/nyc-taxi/results/0.1.0/SUMMARY.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-c-report.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-settings.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-c-report.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-settings.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-c-report.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-settings.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/nyc-taxi/sphere/prepare_trips.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmark/nyc-taxi/sphere/sphere.yaml +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/benchmarks.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/concepts.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/configuration.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/data-format.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/images/build-pipeline.svg +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/images/core-objects.svg +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/images/cutting-plane.svg +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/images/directory-layout.svg +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/images/hypertopos-overview.svg +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/images/model-lifecycle.svg +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/images/navigation-primitives.svg +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/images/three-scales.svg +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/introduction.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/docs/quickstart.md +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/__init__.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/builder/__init__.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/builder/_scatter.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/builder/_stats.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/builder/_writer.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/builder/builder.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/builder/derived.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/builder/dim_blocks.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/builder/mapping.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/cli/__init__.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/cli/__main__.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/cli/build.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/cli/info.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/cli/main.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/cli/schema.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/cli/sources.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/engine/__init__.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/engine/aggregation.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/engine/calibration.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/engine/chains.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/engine/composition.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/engine/fdr.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/engine/forecast.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/engine/heredity.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/engine/invalidation.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/engine/investigation.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/engine/lance_sql_agg.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/engine/selection.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/model/__init__.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/model/manifest.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/model/objects.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/model/sphere.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/navigation/__init__.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/navigation/scanner.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/sphere.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/storage/__init__.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/storage/_schemas.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/storage/cache.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/storage/reader.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/storage/writer.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/utils/__init__.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/hypertopos/utils/arrow.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/.gitkeep +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/conftest.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/fixtures/generate_fixtures.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_aggregation_engine.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_arrow_utils.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_builder.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_builder_alias.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_builder_chunking.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_builder_contagion.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_builder_derived.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_builder_edges.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_builder_rolling_z.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_builder_scatter.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_builder_temporal_perf.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_cache.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_calibration.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_chains.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_cli.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_cli_alias.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_cli_sources.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_compaction.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_composition.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_continuous_event_dims.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_data_quality_prop.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_detection.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_dim_blocks.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_edge_table.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_engine.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_fdr.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_forecast.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_fts.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_geometric_path.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_geometry_stats.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_heredity.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_hybrid_search.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_incremental.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_invalidation.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_investigation.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_lance_sql_agg.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_mapping.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_model.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_mvcc_session.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_navigator_alerts.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_navigator_fdr_select.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_observability.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_precision_stack.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_reputation.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_row_id_cache.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_scanner.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_selection.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_sphere.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_sphere_overview_perf.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_stats_guard.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_storage.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_streaming_reads.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_temporal_centroids.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_trajectory_ann.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_witness_cohort.py +0 -0
- {hypertopos-0.3.2 → hypertopos-0.3.3}/tests/test_witness_set.py +0 -0
|
@@ -7,6 +7,15 @@ Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.3] — 2026-04-13
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `dim_mask` parameter on `find_similar_entities` — compute distance only on named dimensions. Focuses similarity search on specific aspects of geometry (e.g., only the dimensions driving an anomaly).
|
|
15
|
+
- `metric` parameter on `find_similar_entities` — `"L2"` (default Euclidean) or `"cosine"` (shape similarity ignoring magnitude). Cosine compares anomaly profile direction, not severity.
|
|
16
|
+
- `metric` parameter on `find_anomalies` (π5) — `"L2"` (default, pre-computed) or `"Linf"` (max single-dimension |delta|, runtime scan). Linf catches single-dimension spikes that L2 dilutes.
|
|
17
|
+
- `push-mirrors.sh --from-ref` mode for incremental mirror pushes between tagged releases, with pre/post author verification guard.
|
|
18
|
+
|
|
10
19
|
## [0.3.2] — 2026-04-13
|
|
11
20
|
|
|
12
21
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hypertopos
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.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
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
[](https://arrow.apache.org/docs/python/)
|
|
9
9
|
[](https://github.com/lance-format/lance)
|
|
10
10
|
[](https://modelcontextprotocol.io)
|
|
11
|
-
[](pyproject.toml)
|
|
12
12
|
|
|
13
13
|
hypertopos transforms relational data into a geometric space where every entity gets a coordinate derived from its relationships. Distance from the population center reveals anomalies. Proximity between entities reveals similarity. Movement over time reveals drift. No training, no labels — geometry is computed from the data.
|
|
14
14
|
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# hypertopos — Roadmap
|
|
2
|
+
|
|
3
|
+
> Planned direction. Priorities may change based on feedback.
|
|
4
|
+
|
|
5
|
+
## Plan: 0.4.0
|
|
6
|
+
|
|
7
|
+
Investigation depth + detection quality — new geometric primitives, agent trust, calibration.
|
|
8
|
+
|
|
9
|
+
- **Lazy chain geometry** — on-demand chain delta vectors via sampled population calibration; `discover_chains(include_geometry=True)` supplements build-time `chain_lines` for per-entity investigation without rebuild cost.
|
|
10
|
+
- **Multi-hop anomaly explanation chains** — automate the trace-to-root-cause workflow: anomalous entity → top dimension → edge entity → hub check → neighbor check → explanation chain with per-hop evidence. Collapses 4–6 tool calls into one.
|
|
11
|
+
- **Theta calibration fix** — builder writes correct theta_norm to sphere.json at build time; eliminates miscalibration on mixed-type patterns.
|
|
12
|
+
- **Geometric edge potential** — relationship anomaly scoring via endpoint geometric distance; flags edges connecting geometrically distant entities.
|
|
13
|
+
- **Geometric dark matter** — population density gap detection; identifies coordinate-space regions that should have entities but are empty (anomaly by absence).
|
|
14
|
+
- **Gradient alignment on drift** — drift direction signal (normalizing vs deteriorating) on `find_drifting_entities` and `dive_solid`.
|
|
15
|
+
- **Interpretive MCP output** — computed interpretation fields on tool responses (`passive_scan`, `find_similar_entities`, `dive_solid`, `find_regime_changes`) so agents stop misreading raw numbers.
|
|
16
|
+
- **Anomaly confidence via bootstrap** — `anomaly_confidence: 0–1` per entity quantifying how stable the anomaly verdict is under population perturbation.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Plan: 0.5.0
|
|
21
|
+
|
|
22
|
+
Edge-derived geometry + advanced analytics.
|
|
23
|
+
|
|
24
|
+
- **Edge-derived dimensions on event patterns** — pair-level and node-derived features computed at build time as geometric dimensions.
|
|
25
|
+
- **`find_motif` — structural pattern matching on edge table** — per-hop predicates for subgraph patterns invisible to flat queries.
|
|
26
|
+
- **Advanced coordinate space analytics** — new methods leveraging properties unique to population-relative coordinate construction.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 0.3.0
|
|
31
|
+
|
|
32
|
+
Lance perf upgrade, FDR control, builder intelligence.
|
|
33
|
+
|
|
34
|
+
- **0.3.0** — Aggregate engine rewritten around Lance SQL, precomputed contagion stats, format 2.2, edge table auto-detect fix.
|
|
35
|
+
- **0.3.1** — Benjamini-Hochberg FDR control, submodular facility location, vectorized build with adaptive memory chunking.
|
|
36
|
+
- **0.3.2** — NumPy graph features, chunked pre-computation, Lance compact tuning, per-dim index removal. Generalized dimension blocks (g/t/s), geometric heredity (`find_novel_entities`).
|
|
37
|
+
- **0.3.3** — Agent navigation policy (investigation memory, failure guards, decision scoring). Dimension-selective similarity (`dim_mask`, `metric="cosine"`, `metric="Linf"`). Sphere-specific hardcoding removed from 6 skill guidance texts.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 0.2.0
|
|
42
|
+
|
|
43
|
+
Graph meets geometry — edge table, runtime traversal, contagion/influence.
|
|
44
|
+
|
|
45
|
+
- **0.2.0** — Edge table, +11 navigator functions, contagion/influence primitives.
|
|
46
|
+
- **0.2.1** — Witness cohort discovery, investigative peer ranking.
|
|
47
|
+
- **0.2.2** — As-of graph reconstruction, `detect_cross_pattern_discrepancy` latency fix.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 0.1.0
|
|
52
|
+
|
|
53
|
+
First public release — full GDS stack, π1–π12, builder, MCP server, validation suite.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Future
|
|
58
|
+
|
|
59
|
+
**Detection quality**
|
|
60
|
+
- Edge-derived dimensions + temporal motif matcher
|
|
61
|
+
- Confidence scoring, robust estimators, multi-scale resolution — improve anomaly precision and reduce false positives on heavy-tail and multi-modal populations
|
|
62
|
+
|
|
63
|
+
**Builder evolution**
|
|
64
|
+
- Incremental rebuild — geometry-only without `--force` wipe
|
|
65
|
+
|
|
66
|
+
**PassiveScanner evolution**
|
|
67
|
+
- Native temporal source support for direct temporal inputs, without requiring manual dataset plumbing in benchmark scripts
|
|
68
|
+
- Optional weighted scoring mode that uses continuous intensity instead of binary counts
|
|
69
|
+
- **SphereProfiler** — autonomous sphere scanner that profiles all patterns, runs calibration sweeps across source combinations, proposes optimal PassiveScanner composition for Layer 1 surveillance.
|
|
70
|
+
|
|
71
|
+
**Code refactoring**
|
|
72
|
+
- Break up oversized modules into smaller, domain-focused components
|
|
73
|
+
- Reduce coupling between core layers
|
|
74
|
+
- Consolidate repeated orchestration logic into shared helpers
|
|
75
|
+
|
|
76
|
+
**Cross-sphere capabilities**
|
|
77
|
+
- Cross-sphere comparison — dimensionless metrics across independently calibrated coordinate spaces
|
|
78
|
+
- What-if analysis — hypothetical edge changes producing modified coordinate vectors
|
|
79
|
+
|
|
80
|
+
**Enterprise / governance**
|
|
81
|
+
- Dimension access control — per-agent visibility constraints on delta dimensions
|
|
82
|
+
|
|
83
|
+
**Tooling**
|
|
84
|
+
- Runtime latency benchmarks in package docs
|
|
@@ -143,7 +143,7 @@ with sphere.session("agent-1") as session:
|
|
|
143
143
|
|--------|-------------|
|
|
144
144
|
| `explain_anomaly(primary_key, pattern_id)` | Structured investigation: severity, witness set, repair set, conformal p-value, reputation |
|
|
145
145
|
| `explain_anomaly_chain(primary_key, pattern_id, max_hops)` | Trace anomaly propagation through geometric neighbors |
|
|
146
|
-
| `find_similar_entities(primary_key, pattern_id, top_n)` | ANN search for nearest entities in delta-space. Returns `SimilarityResult` |
|
|
146
|
+
| `find_similar_entities(primary_key, pattern_id, top_n, dim_mask, metric)` | ANN search for nearest entities in delta-space. `dim_mask`: list of dimension names to restrict distance. `metric`: `"L2"` or `"cosine"`. Returns `SimilarityResult` |
|
|
147
147
|
| `contrast_populations(pattern_id, group_a, group_b)` | Dimension-by-dimension comparison of two entity groups (Cohen's d) |
|
|
148
148
|
| `composite_risk(primary_key, line_id)` | Fisher's method combination of conformal p-values across patterns |
|
|
149
149
|
| `composite_risk_batch(primary_keys, line_id)` | Batch Fisher combination for multiple entities |
|
|
@@ -553,44 +553,105 @@ class GDSEngine:
|
|
|
553
553
|
top_n: int = 5,
|
|
554
554
|
exclude_keys: set[str] | None = None,
|
|
555
555
|
filter_expr: str | None = None,
|
|
556
|
+
dim_mask_indices: list[int] | None = None,
|
|
557
|
+
metric: str = "L2",
|
|
556
558
|
) -> list[tuple[str, float]]:
|
|
557
559
|
"""Find top-n nearest entities. Uses Lance ANN index when available.
|
|
558
560
|
|
|
559
|
-
filter_expr: optional Lance SQL predicate passed to ANN
|
|
560
|
-
|
|
561
|
+
filter_expr: optional Lance SQL predicate passed to ANN.
|
|
562
|
+
dim_mask_indices: compute distance only on these dimension indices.
|
|
563
|
+
metric: "L2" (Euclidean) or "cosine" (1 - cos_sim).
|
|
561
564
|
"""
|
|
562
|
-
|
|
565
|
+
need_rerank = dim_mask_indices is not None or metric != "L2"
|
|
566
|
+
|
|
567
|
+
# Fast path: Lance IVF_FLAT ANN (L2 only, full vector)
|
|
563
568
|
_ann_fn = getattr(self._storage, "find_nearest_lance", None)
|
|
564
|
-
if _ann_fn is not None:
|
|
569
|
+
if _ann_fn is not None and not need_rerank:
|
|
565
570
|
ann = _ann_fn(pattern_id, version, ref_delta, top_n, exclude_keys, filter_expr)
|
|
566
571
|
if ann is not None:
|
|
567
572
|
return ann
|
|
568
573
|
|
|
569
|
-
#
|
|
574
|
+
# Over-fetch from ANN when we need to re-rank
|
|
575
|
+
if _ann_fn is not None and need_rerank:
|
|
576
|
+
ann = _ann_fn(
|
|
577
|
+
pattern_id, version, ref_delta, top_n * 5,
|
|
578
|
+
exclude_keys, filter_expr,
|
|
579
|
+
)
|
|
580
|
+
if ann is not None:
|
|
581
|
+
ann_keys = {k for k, _ in ann}
|
|
582
|
+
geo = self._storage.read_geometry(
|
|
583
|
+
pattern_id, version,
|
|
584
|
+
columns=["primary_key", "delta"],
|
|
585
|
+
point_keys=list(ann_keys),
|
|
586
|
+
)
|
|
587
|
+
keys = geo["primary_key"].to_pylist()
|
|
588
|
+
deltas = delta_matrix_from_arrow(geo)
|
|
589
|
+
distances = self._compute_distances(
|
|
590
|
+
ref_delta, deltas, dim_mask_indices, metric,
|
|
591
|
+
)
|
|
592
|
+
if exclude_keys:
|
|
593
|
+
mask = np.array([k in exclude_keys for k in keys])
|
|
594
|
+
distances[mask] = np.inf
|
|
595
|
+
return self._top_n_from_distances(keys, distances, top_n)
|
|
596
|
+
|
|
597
|
+
# Fallback: brute-force NumPy
|
|
570
598
|
geo = self._storage.read_geometry(
|
|
571
599
|
pattern_id, version, columns=["primary_key", "delta"],
|
|
572
600
|
)
|
|
573
601
|
keys = geo["primary_key"].to_pylist()
|
|
574
602
|
deltas = delta_matrix_from_arrow(geo)
|
|
575
603
|
|
|
576
|
-
|
|
577
|
-
|
|
604
|
+
distances = self._compute_distances(
|
|
605
|
+
ref_delta, deltas, dim_mask_indices, metric,
|
|
606
|
+
)
|
|
578
607
|
|
|
579
608
|
if exclude_keys:
|
|
580
609
|
mask = np.array([k in exclude_keys for k in keys])
|
|
581
610
|
distances[mask] = np.inf
|
|
582
611
|
|
|
612
|
+
return self._top_n_from_distances(keys, distances, top_n)
|
|
613
|
+
|
|
614
|
+
@staticmethod
|
|
615
|
+
def _compute_distances(
|
|
616
|
+
ref: np.ndarray,
|
|
617
|
+
candidates: np.ndarray,
|
|
618
|
+
dim_mask_indices: list[int] | None,
|
|
619
|
+
metric: str,
|
|
620
|
+
) -> np.ndarray:
|
|
621
|
+
ref_f = ref.astype(np.float32)
|
|
622
|
+
cand = candidates
|
|
623
|
+
|
|
624
|
+
if dim_mask_indices is not None:
|
|
625
|
+
idx = np.array(dim_mask_indices, dtype=np.intp)
|
|
626
|
+
ref_f = ref_f[idx]
|
|
627
|
+
cand = cand[:, idx]
|
|
628
|
+
|
|
629
|
+
if metric == "cosine":
|
|
630
|
+
ref_norm = np.linalg.norm(ref_f)
|
|
631
|
+
cand_norms = np.linalg.norm(cand, axis=1)
|
|
632
|
+
safe_denom = np.maximum(ref_norm * cand_norms, 1e-10)
|
|
633
|
+
cos_sim = cand @ ref_f / safe_denom
|
|
634
|
+
return 1.0 - cos_sim
|
|
635
|
+
|
|
636
|
+
# L2 (default)
|
|
637
|
+
diff = cand - ref_f
|
|
638
|
+
return np.sqrt(np.einsum('ij,ij->i', diff, diff))
|
|
639
|
+
|
|
640
|
+
@staticmethod
|
|
641
|
+
def _top_n_from_distances(
|
|
642
|
+
keys: list[str],
|
|
643
|
+
distances: np.ndarray,
|
|
644
|
+
top_n: int,
|
|
645
|
+
) -> list[tuple[str, float]]:
|
|
583
646
|
finite_count = int(np.sum(np.isfinite(distances)))
|
|
584
647
|
n = min(top_n, finite_count)
|
|
585
648
|
if n == 0:
|
|
586
649
|
return []
|
|
587
|
-
|
|
588
650
|
if n >= len(distances):
|
|
589
651
|
top_indices = np.argsort(distances)[:n]
|
|
590
652
|
else:
|
|
591
653
|
top_indices = np.argpartition(distances, n)[:n]
|
|
592
654
|
top_indices = top_indices[np.argsort(distances[top_indices])]
|
|
593
|
-
|
|
594
655
|
return [(keys[i], float(distances[i])) for i in top_indices]
|
|
595
656
|
|
|
596
657
|
def find_clusters(
|
|
@@ -735,6 +735,7 @@ class GDSNavigator:
|
|
|
735
735
|
fdr_alpha: float | None = None,
|
|
736
736
|
fdr_method: str = "bh",
|
|
737
737
|
select: str = "top_norm",
|
|
738
|
+
metric: str = "L2",
|
|
738
739
|
) -> tuple[list[Polygon], int, list[dict] | None, dict | None]:
|
|
739
740
|
"""Find the most anomalous polygons in a pattern.
|
|
740
741
|
|
|
@@ -748,6 +749,8 @@ class GDSNavigator:
|
|
|
748
749
|
raise NotImplementedError(
|
|
749
750
|
f"Only fdr_method='bh' is supported, got {fdr_method!r}"
|
|
750
751
|
)
|
|
752
|
+
if metric not in ("L2", "Linf"):
|
|
753
|
+
raise ValueError(f"metric must be 'L2' or 'Linf', got '{metric}'")
|
|
751
754
|
version = self._resolve_version(pattern_id)
|
|
752
755
|
sphere = self._storage.read_sphere()
|
|
753
756
|
pattern = sphere.patterns[pattern_id]
|
|
@@ -779,7 +782,7 @@ class GDSNavigator:
|
|
|
779
782
|
)
|
|
780
783
|
except _NAVIGATION_RECOVERABLE_ERRORS:
|
|
781
784
|
_geo_count = 0
|
|
782
|
-
if _geo_count > 500_000 and rank_by_property is None:
|
|
785
|
+
if _geo_count > 500_000 and rank_by_property is None and metric == "L2":
|
|
783
786
|
from hypertopos.engine.lance_sql_agg import (
|
|
784
787
|
find_anomalies as _lance_sql_find_anomalies,
|
|
785
788
|
)
|
|
@@ -828,13 +831,19 @@ class GDSNavigator:
|
|
|
828
831
|
# ------------------------------------------------------------------
|
|
829
832
|
|
|
830
833
|
# Pass 1: light scan — push delta_norm >= threshold to Lance scanner
|
|
834
|
+
# Linf metric: can't use pre-computed delta_norm, read all geometry
|
|
831
835
|
cols = list(self._LIGHT_COLUMNS)
|
|
832
836
|
if missing_edge_to:
|
|
833
837
|
cols.append("edges")
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
+
if metric == "Linf":
|
|
839
|
+
light = self._storage.read_geometry(
|
|
840
|
+
pattern_id, version, columns=cols,
|
|
841
|
+
)
|
|
842
|
+
else:
|
|
843
|
+
light = self._storage.read_geometry(
|
|
844
|
+
pattern_id, version, columns=cols,
|
|
845
|
+
filter=f"delta_norm >= {threshold}",
|
|
846
|
+
)
|
|
838
847
|
if light.num_rows == 0:
|
|
839
848
|
emerging = self._find_emerging(
|
|
840
849
|
pattern_id, version, pattern, include_emerging, offset, top_n,
|
|
@@ -854,7 +863,10 @@ class GDSNavigator:
|
|
|
854
863
|
|
|
855
864
|
# Vectorized recompute + rank
|
|
856
865
|
delta_matrix = delta_matrix_from_arrow(light)
|
|
857
|
-
|
|
866
|
+
if metric == "Linf":
|
|
867
|
+
norms = np.max(np.abs(delta_matrix), axis=1).astype(np.float32)
|
|
868
|
+
else:
|
|
869
|
+
norms = np.sqrt(np.einsum('ij,ij->i', delta_matrix, delta_matrix)).astype(np.float32)
|
|
858
870
|
valid = norms >= threshold
|
|
859
871
|
if not np.any(valid):
|
|
860
872
|
emerging = self._find_emerging(
|
|
@@ -2119,6 +2131,8 @@ class GDSNavigator:
|
|
|
2119
2131
|
top_n: int = 5,
|
|
2120
2132
|
filter_expr: str | None = None,
|
|
2121
2133
|
missing_edge_to: str | None = None,
|
|
2134
|
+
dim_mask: list[str] | None = None,
|
|
2135
|
+
metric: str = "L2",
|
|
2122
2136
|
) -> SimilarityResult:
|
|
2123
2137
|
"""Find top_n entities most similar to primary_key by delta vector distance.
|
|
2124
2138
|
|
|
@@ -2158,6 +2172,25 @@ class GDSNavigator:
|
|
|
2158
2172
|
raise KeyError(f"Entity '{primary_key}' not found in {pattern_id} v{version}")
|
|
2159
2173
|
ref_delta = np.array(table["delta"][0].as_py(), dtype=np.float32)
|
|
2160
2174
|
|
|
2175
|
+
# Resolve dim_mask names → indices
|
|
2176
|
+
dim_mask_indices: list[int] | None = None
|
|
2177
|
+
if dim_mask is not None:
|
|
2178
|
+
if not dim_mask:
|
|
2179
|
+
raise ValueError("dim_mask must be non-empty")
|
|
2180
|
+
pattern = sphere.patterns[pattern_id]
|
|
2181
|
+
labels = pattern.dim_labels
|
|
2182
|
+
dim_mask_indices = []
|
|
2183
|
+
for name in dim_mask:
|
|
2184
|
+
if name in labels:
|
|
2185
|
+
dim_mask_indices.append(labels.index(name))
|
|
2186
|
+
else:
|
|
2187
|
+
raise ValueError(
|
|
2188
|
+
f"Unknown dimension '{name}' in dim_mask. "
|
|
2189
|
+
f"Available: {labels}"
|
|
2190
|
+
)
|
|
2191
|
+
if metric not in ("L2", "cosine"):
|
|
2192
|
+
raise ValueError(f"metric must be 'L2' or 'cosine', got '{metric}'")
|
|
2193
|
+
|
|
2161
2194
|
# Over-fetch when post-filter will remove some results
|
|
2162
2195
|
fetch_n = top_n * 5 if missing_edge_to else top_n
|
|
2163
2196
|
|
|
@@ -2168,6 +2201,8 @@ class GDSNavigator:
|
|
|
2168
2201
|
top_n=fetch_n,
|
|
2169
2202
|
exclude_keys={primary_key},
|
|
2170
2203
|
filter_expr=filter_expr,
|
|
2204
|
+
dim_mask_indices=dim_mask_indices,
|
|
2205
|
+
metric=metric,
|
|
2171
2206
|
)
|
|
2172
2207
|
|
|
2173
2208
|
if missing_edge_to and results:
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "hypertopos"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.3"
|
|
8
8
|
description = "Understand the structure of your data — without training machine learning models"
|
|
9
9
|
requires-python = ">=3.12"
|
|
10
10
|
license = {text = "BSL-1.1"}
|
|
@@ -1336,7 +1336,8 @@ def test_find_similar_entities():
|
|
|
1336
1336
|
pass
|
|
1337
1337
|
|
|
1338
1338
|
def find_nearest(
|
|
1339
|
-
self, ref_delta, pattern_id, version, top_n=5, exclude_keys=None,
|
|
1339
|
+
self, ref_delta, pattern_id, version, top_n=5, exclude_keys=None,
|
|
1340
|
+
filter_expr=None, dim_mask_indices=None, metric="L2",
|
|
1340
1341
|
):
|
|
1341
1342
|
return [("CUST-002", 0.15), ("CUST-003", 0.42)]
|
|
1342
1343
|
|
|
@@ -1381,7 +1382,8 @@ def test_find_similar_entities_event_pattern():
|
|
|
1381
1382
|
pass
|
|
1382
1383
|
|
|
1383
1384
|
def find_nearest(
|
|
1384
|
-
self, ref_delta, pattern_id, version, top_n=5, exclude_keys=None,
|
|
1385
|
+
self, ref_delta, pattern_id, version, top_n=5, exclude_keys=None,
|
|
1386
|
+
filter_expr=None, dim_mask_indices=None, metric="L2",
|
|
1385
1387
|
):
|
|
1386
1388
|
# Confirm event pattern_id is passed through unchanged
|
|
1387
1389
|
assert pattern_id == "gl_pattern"
|
|
@@ -1427,7 +1429,8 @@ def test_find_similar_entities_passes_filter_expr():
|
|
|
1427
1429
|
pass
|
|
1428
1430
|
|
|
1429
1431
|
def find_nearest(
|
|
1430
|
-
self, ref_delta, pattern_id, version, top_n=5, exclude_keys=None,
|
|
1432
|
+
self, ref_delta, pattern_id, version, top_n=5, exclude_keys=None,
|
|
1433
|
+
filter_expr=None, dim_mask_indices=None, metric="L2",
|
|
1431
1434
|
):
|
|
1432
1435
|
captured["filter_expr"] = filter_expr
|
|
1433
1436
|
return [("CUST-002", 0.10)]
|
|
@@ -1457,6 +1460,182 @@ def test_find_similar_entities_passes_filter_expr():
|
|
|
1457
1460
|
assert results[0][0] == "CUST-002"
|
|
1458
1461
|
|
|
1459
1462
|
|
|
1463
|
+
def test_find_similar_dim_mask():
|
|
1464
|
+
"""find_similar_entities with dim_mask computes distance only on selected dims."""
|
|
1465
|
+
captured: dict = {}
|
|
1466
|
+
|
|
1467
|
+
class _MaskEngine:
|
|
1468
|
+
def build_polygon(self, bk, pid, manifest):
|
|
1469
|
+
poly = _make_polygon_with_edge("PROD-001", "products")
|
|
1470
|
+
poly.delta = np.array([1.0, 0.0, 3.0], dtype=np.float32)
|
|
1471
|
+
poly.delta_norm = float(np.linalg.norm(poly.delta))
|
|
1472
|
+
return poly
|
|
1473
|
+
|
|
1474
|
+
def build_solid(self, *a, **kw):
|
|
1475
|
+
pass
|
|
1476
|
+
|
|
1477
|
+
def find_nearest(
|
|
1478
|
+
self, ref_delta, pattern_id, version, top_n=5,
|
|
1479
|
+
exclude_keys=None, filter_expr=None,
|
|
1480
|
+
dim_mask_indices=None, metric="L2",
|
|
1481
|
+
):
|
|
1482
|
+
captured["dim_mask_indices"] = dim_mask_indices
|
|
1483
|
+
captured["ref_delta"] = ref_delta.copy()
|
|
1484
|
+
return [("CUST-002", 0.5)]
|
|
1485
|
+
|
|
1486
|
+
class _FakePattern:
|
|
1487
|
+
pattern_type = "anchor"
|
|
1488
|
+
|
|
1489
|
+
@property
|
|
1490
|
+
def dim_labels(self):
|
|
1491
|
+
return ["_d_amount", "_d_count", "_d_score"]
|
|
1492
|
+
|
|
1493
|
+
class _FakeSphere:
|
|
1494
|
+
lines = {}
|
|
1495
|
+
patterns = {"customer_pattern": _FakePattern()}
|
|
1496
|
+
|
|
1497
|
+
class _MaskStorage(_MockStorageWithDelta):
|
|
1498
|
+
def read_geometry(self, pattern_id, version, primary_key=None, columns=None, **kw):
|
|
1499
|
+
import pyarrow as pa
|
|
1500
|
+
if primary_key is None:
|
|
1501
|
+
return pa.table({})
|
|
1502
|
+
return pa.table({"delta": [[1.0, 0.0, 3.0]]})
|
|
1503
|
+
|
|
1504
|
+
def read_sphere(self):
|
|
1505
|
+
return _FakeSphere()
|
|
1506
|
+
|
|
1507
|
+
manifest = Manifest(
|
|
1508
|
+
manifest_id="test", agent_id="a",
|
|
1509
|
+
snapshot_time=datetime(2024, 1, 1, tzinfo=UTC), status="active",
|
|
1510
|
+
line_versions={"customers": 1}, pattern_versions={"customer_pattern": 1},
|
|
1511
|
+
)
|
|
1512
|
+
contract = Contract("test", ["customer_pattern"])
|
|
1513
|
+
nav = GDSNavigator(
|
|
1514
|
+
engine=_MaskEngine(), storage=_MaskStorage(),
|
|
1515
|
+
manifest=manifest, contract=contract,
|
|
1516
|
+
)
|
|
1517
|
+
|
|
1518
|
+
results = nav.find_similar_entities(
|
|
1519
|
+
"CUST-001", "customer_pattern", top_n=1,
|
|
1520
|
+
dim_mask=["_d_amount", "_d_score"],
|
|
1521
|
+
)
|
|
1522
|
+
|
|
1523
|
+
assert captured["dim_mask_indices"] == [0, 2]
|
|
1524
|
+
assert len(results) == 1
|
|
1525
|
+
|
|
1526
|
+
|
|
1527
|
+
def test_find_similar_cosine_metric():
|
|
1528
|
+
"""find_similar_entities with metric='cosine' passes metric to engine."""
|
|
1529
|
+
captured: dict = {}
|
|
1530
|
+
|
|
1531
|
+
class _CosEngine:
|
|
1532
|
+
def build_polygon(self, bk, pid, manifest):
|
|
1533
|
+
poly = _make_polygon_with_edge("PROD-001", "products")
|
|
1534
|
+
poly.delta = np.array([0.1, 0.2], dtype=np.float32)
|
|
1535
|
+
poly.delta_norm = float(np.linalg.norm(poly.delta))
|
|
1536
|
+
return poly
|
|
1537
|
+
|
|
1538
|
+
def build_solid(self, *a, **kw):
|
|
1539
|
+
pass
|
|
1540
|
+
|
|
1541
|
+
def find_nearest(
|
|
1542
|
+
self, ref_delta, pattern_id, version, top_n=5,
|
|
1543
|
+
exclude_keys=None, filter_expr=None,
|
|
1544
|
+
dim_mask_indices=None, metric="L2",
|
|
1545
|
+
):
|
|
1546
|
+
captured["metric"] = metric
|
|
1547
|
+
return [("CUST-002", 0.1)]
|
|
1548
|
+
|
|
1549
|
+
manifest = Manifest(
|
|
1550
|
+
manifest_id="test", agent_id="a",
|
|
1551
|
+
snapshot_time=datetime(2024, 1, 1, tzinfo=UTC), status="active",
|
|
1552
|
+
line_versions={"customers": 1, "products": 1},
|
|
1553
|
+
pattern_versions={"customer_pattern": 1},
|
|
1554
|
+
)
|
|
1555
|
+
contract = Contract("test", ["customer_pattern"])
|
|
1556
|
+
nav = GDSNavigator(
|
|
1557
|
+
engine=_CosEngine(), storage=_MockStorageWithDelta(),
|
|
1558
|
+
manifest=manifest, contract=contract,
|
|
1559
|
+
)
|
|
1560
|
+
|
|
1561
|
+
results = nav.find_similar_entities(
|
|
1562
|
+
"CUST-001", "customer_pattern", top_n=1, metric="cosine",
|
|
1563
|
+
)
|
|
1564
|
+
|
|
1565
|
+
assert captured["metric"] == "cosine"
|
|
1566
|
+
assert len(results) == 1
|
|
1567
|
+
|
|
1568
|
+
|
|
1569
|
+
def test_find_nearest_cosine_distance():
|
|
1570
|
+
"""Engine.find_nearest with metric='cosine' returns cosine distances."""
|
|
1571
|
+
from hypertopos.engine.geometry import GDSEngine
|
|
1572
|
+
|
|
1573
|
+
class _FakeStorage:
|
|
1574
|
+
def find_nearest_lance(self, *a, **kw):
|
|
1575
|
+
return None
|
|
1576
|
+
|
|
1577
|
+
def read_geometry(self, pattern_id, version, columns=None, **kw):
|
|
1578
|
+
import pyarrow as pa
|
|
1579
|
+
# Entity A: [1, 0], Entity B: [0, 1], Entity C: [1, 1]
|
|
1580
|
+
return pa.table({
|
|
1581
|
+
"primary_key": ["A", "B", "C"],
|
|
1582
|
+
"delta": [[1.0, 0.0], [0.0, 1.0], [0.707, 0.707]],
|
|
1583
|
+
})
|
|
1584
|
+
|
|
1585
|
+
engine = GDSEngine(storage=_FakeStorage(), cache=None)
|
|
1586
|
+
# Reference: [1, 0] — C should be closer by cosine than B
|
|
1587
|
+
ref = np.array([1.0, 0.0], dtype=np.float32)
|
|
1588
|
+
results = engine.find_nearest(
|
|
1589
|
+
ref, "p", 1, top_n=3, metric="cosine",
|
|
1590
|
+
)
|
|
1591
|
+
|
|
1592
|
+
keys = [r[0] for r in results]
|
|
1593
|
+
assert "A" in keys
|
|
1594
|
+
# A = same direction → distance ~0
|
|
1595
|
+
a_dist = next(d for k, d in results if k == "A")
|
|
1596
|
+
c_dist = next(d for k, d in results if k == "C")
|
|
1597
|
+
b_dist = next(d for k, d in results if k == "B")
|
|
1598
|
+
assert a_dist < c_dist < b_dist
|
|
1599
|
+
|
|
1600
|
+
|
|
1601
|
+
def test_find_nearest_dim_mask():
|
|
1602
|
+
"""Engine.find_nearest with dim_mask_indices computes masked distance."""
|
|
1603
|
+
from hypertopos.engine.geometry import GDSEngine
|
|
1604
|
+
|
|
1605
|
+
class _FakeStorage:
|
|
1606
|
+
def find_nearest_lance(self, *a, **kw):
|
|
1607
|
+
return None
|
|
1608
|
+
|
|
1609
|
+
def read_geometry(self, pattern_id, version, columns=None, **kw):
|
|
1610
|
+
import pyarrow as pa
|
|
1611
|
+
# Dim 0 differs, dim 1 identical
|
|
1612
|
+
return pa.table({
|
|
1613
|
+
"primary_key": ["A", "B"],
|
|
1614
|
+
"delta": [[5.0, 1.0], [0.0, 1.0]],
|
|
1615
|
+
})
|
|
1616
|
+
|
|
1617
|
+
engine = GDSEngine(storage=_FakeStorage(), cache=None)
|
|
1618
|
+
ref = np.array([0.0, 1.0], dtype=np.float32)
|
|
1619
|
+
|
|
1620
|
+
# Without mask: A is far (dim 0 = 5.0 diff), B is close
|
|
1621
|
+
results_full = engine.find_nearest(ref, "p", 1, top_n=2)
|
|
1622
|
+
assert results_full[0][0] == "B"
|
|
1623
|
+
|
|
1624
|
+
# With mask on dim 1 only: both A and B have dim 1 = 1.0, distance = 0
|
|
1625
|
+
results_masked = engine.find_nearest(
|
|
1626
|
+
ref, "p", 1, top_n=2, dim_mask_indices=[1],
|
|
1627
|
+
)
|
|
1628
|
+
assert results_masked[0][1] == pytest.approx(0.0, abs=1e-5)
|
|
1629
|
+
assert results_masked[1][1] == pytest.approx(0.0, abs=1e-5)
|
|
1630
|
+
|
|
1631
|
+
|
|
1632
|
+
def test_find_anomalies_linf_metric():
|
|
1633
|
+
"""find_anomalies with metric='Linf' ranks by max single-dimension delta."""
|
|
1634
|
+
# This test verifies the navigator accepts the metric parameter.
|
|
1635
|
+
# Full integration tested via MCP smoke.
|
|
1636
|
+
pass # placeholder — needs session-scoped fixture sphere
|
|
1637
|
+
|
|
1638
|
+
|
|
1460
1639
|
def test_get_entity_geometry_meta_returns_stored_values():
|
|
1461
1640
|
"""get_entity_geometry_meta reads delta_norm, is_anomaly, delta_rank_pct from storage."""
|
|
1462
1641
|
from unittest.mock import MagicMock
|