hypertopos 0.2.2__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {hypertopos-0.2.2 → hypertopos-0.3.0}/CHANGELOG.md +38 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/LICENSE.md +1 -1
- {hypertopos-0.2.2 → hypertopos-0.3.0}/PKG-INFO +3 -5
- {hypertopos-0.2.2 → hypertopos-0.3.0}/README.md +2 -2
- {hypertopos-0.2.2 → hypertopos-0.3.0}/ROADMAP.md +48 -18
- {hypertopos-0.2.2 → hypertopos-0.3.0}/SECURITY.md +1 -1
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/architecture.md +1 -1
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/data-format.md +2 -1
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/__init__.py +1 -1
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/builder/builder.py +153 -10
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/cli/build.py +9 -1
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/engine/aggregation.py +282 -523
- hypertopos-0.3.0/hypertopos/engine/lance_sql_agg.py +798 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/navigation/navigator.py +59 -57
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/navigation/scanner.py +41 -64
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/storage/reader.py +34 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/storage/writer.py +35 -3
- {hypertopos-0.2.2 → hypertopos-0.3.0}/pyproject.toml +3 -6
- hypertopos-0.3.0/tests/conftest.py +89 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_aggregation_engine.py +0 -66
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_builder.py +28 -6
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_builder_derived.py +18 -3
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_calibration.py +10 -8
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_fts.py +15 -5
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_geometric_path.py +7 -8
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_incremental.py +4 -3
- hypertopos-0.3.0/tests/test_lance_sql_agg.py +548 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_navigation.py +34 -21
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_scanner.py +45 -0
- hypertopos-0.2.2/hypertopos/engine/datafusion_agg.py +0 -602
- hypertopos-0.2.2/hypertopos/engine/subprocess_agg.py +0 -742
- hypertopos-0.2.2/tests/conftest.py +0 -83
- hypertopos-0.2.2/tests/test_datafusion_agg.py +0 -788
- hypertopos-0.2.2/tests/test_subprocess_agg.py +0 -961
- {hypertopos-0.2.2 → hypertopos-0.3.0}/.github/workflows/publish-pypi.yml +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/.gitignore +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/README.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/api-reference.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/LICENSE +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/README.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/README.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/SUMMARY.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-settings.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-settings.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-settings.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-settings.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-settings.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-settings.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/prepare_accounts.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/prepare_cpty_banks.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/prepare_districts.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/prepare_loan_accounts.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/prepare_operations.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/prepare_orders.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/prepare_transactions.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/prepare_tx_types.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/sphere.yaml +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/README.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/_layer2_cross_eval.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/_layer3_cross_eval.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_accounts.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_currencies.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_payment_formats.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_transactions.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/hi_small_sphere/sphere.yaml +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/layer1_passive_scan.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/layer2_xgboost_sweep.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/layer3_typology_filter.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/li_small_sphere/prepare_accounts.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/li_small_sphere/prepare_currencies.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/li_small_sphere/prepare_payment_formats.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/li_small_sphere/prepare_transactions.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/li_small_sphere/sphere.yaml +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/passive-scan-calibration.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/README.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/SUMMARY.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-c-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-settings.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-c-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-settings.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-c-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-settings.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/sphere/prepare_trips.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/sphere/sphere.yaml +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/benchmarks.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/concepts.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/configuration.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/images/build-pipeline.svg +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/images/core-objects.svg +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/images/cutting-plane.svg +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/images/directory-layout.svg +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/images/hypertopos-overview.svg +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/images/model-lifecycle.svg +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/images/navigation-primitives.svg +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/images/three-scales.svg +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/docs/quickstart.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/builder/__init__.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/builder/_stats.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/builder/_writer.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/builder/derived.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/builder/mapping.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/cli/__init__.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/cli/__main__.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/cli/info.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/cli/main.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/cli/schema.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/cli/sources.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/engine/__init__.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/engine/calibration.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/engine/chains.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/engine/composition.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/engine/forecast.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/engine/geometry.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/engine/invalidation.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/engine/investigation.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/model/__init__.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/model/manifest.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/model/objects.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/model/sphere.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/navigation/__init__.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/sphere.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/storage/__init__.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/storage/_schemas.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/storage/cache.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/utils/__init__.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/hypertopos/utils/arrow.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/.gitkeep +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/fixtures/generate_fixtures.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_arrow_utils.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_builder_alias.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_builder_edges.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_cache.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_chains.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_cli.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_cli_alias.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_cli_sources.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_compaction.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_composition.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_continuous_event_dims.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_data_quality_prop.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_detection.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_edge_table.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_engine.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_forecast.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_geometry_stats.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_hybrid_search.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_invalidation.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_investigation.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_mapping.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_model.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_mvcc_session.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_navigator_alerts.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_observability.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_precision_stack.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_reputation.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_row_id_cache.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_sphere.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_sphere_overview_perf.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_stats_guard.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_storage.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_streaming_reads.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_temporal_centroids.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_trajectory_ann.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_witness_cohort.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.0}/tests/test_witness_set.py +0 -0
|
@@ -5,6 +5,44 @@ All notable changes to hypertopos will be documented in this file.
|
|
|
5
5
|
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
6
|
Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.0] — 2026-04-12
|
|
9
|
+
|
|
10
|
+
> **Theme:** Lance 3.x perf upgrade. Aggregate engine moves from a persistent subprocess worker plus an optional external DataFusion package to the Lance scanner's built-in DataFusion executor via `LanceDataset.sql(...)`. The build-time contagion stats table replaces the runtime edge-table replay in the graph contagion scanner. Format 2.2 is the new write default.
|
|
11
|
+
>
|
|
12
|
+
> **Mandatory rebuild for spheres built before this release.** See the Migration section below.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- `pylance` minimum bumped to 4.x. New writes target Lance format 2.2 with structural-decode parallelism, BSS float compression, cascading codecs, and block-level RLE on top of the previous baseline. Older spheres written with format 2.0 / 2.1 remain readable transparently.
|
|
17
|
+
- The aggregate engine now pushes GROUP BY computation directly into the Lance scanner. The new `lance_sql_agg` module hosts `aggregate_count`, `aggregate_metric` (sum / avg / min / max), `aggregate_filtered_metric`, `aggregate_pivot`, `aggregate_property`, `aggregate_percentile`, and `find_anomalies`. Each helper streams the columns it needs from the geometry / event-line / property-line Lance datasets through `LanceDataset.sql(...)` and finishes the join + aggregate in pyarrow on the small post-projection result. The full geometry table is never loaded into Python memory and the persistent subprocess worker is no longer involved for any of these paths.
|
|
18
|
+
- Test fixture cloning uses `lance.LanceDataset.shallow_clone` for every Lance dataset directory inside a sphere tree, with a thin `clone_sphere` helper in `tests/conftest.py` that walks the directory and falls back to `shutil.copy2` for non-Lance files. Shallow clone copies only metadata and references — measurably faster on Windows where deep `copytree` over a sphere built from many small Lance fragment files is dominated by per-file NTFS overhead.
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- `GDSBuilder` rejects patterns with zero declared dimensions (no relations, no event dimensions, no derived dimensions, no tracked properties) at validation time. A pattern with width-0 delta vectors had no meaningful geometry to compute even before this release; format 2.0 silently allowed the construct, format 2.1+ refuses to encode the resulting `fixed_size_list[0]` columns at all. The validation surfaces a typed error up front instead of letting the call descend into an opaque write-time panic.
|
|
23
|
+
- Builder precomputes per-entity contagion statistics for every pattern with an edge table and writes them to `_gds_meta/contagion_stats/{pattern_id}.lance` with a BTREE index on `primary_key`. The table holds `(primary_key, neighbor_count, anomalous_neighbor_count, contagion_ratio)` snapshotted at build time against the just-calibrated anchor geometry.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- `_scan_graph` (the graph contagion source feeding `passive_scan` and `composite_risk`) reads the precomputed `_gds_meta/contagion_stats/{pattern_id}.lance` table directly and threshold-filters inline. The legacy edge-table-replay path is gone; spheres built before 0.3.0 must be rebuilt to get graph contagion hits.
|
|
28
|
+
- `contagion_score` anchor pattern resolution now correctly resolves via sibling lines when the anchor pattern is not directly associated with the target entity's line. Previously crashed with `KeyError` on spheres where the anchor pattern entity line differed from the navigated line.
|
|
29
|
+
- Edge table auto-detect excludes metadata columns (`created_at`, `changed_at`, `version`) from the timestamp type-based fallback, preventing incorrect timestamp column selection on entity lines with versioning columns. Amount candidate list widened to cover `fare_amount`, `total_amount`, `amount_received`, `amount_paid`.
|
|
30
|
+
|
|
31
|
+
### Security
|
|
32
|
+
|
|
33
|
+
- `lance_sql_agg` validates user-controlled SQL inputs up front. Lance's `LanceDataset.sql(...)` does not expose parameter binding, so values and identifiers are inlined into the query string. Filter values (entity primary keys) are escaped with `_escape_sql_string`, which doubles single quotes and rejects backslash and ASCII control characters. Column-name identifiers (`metric_col`, `pivot_field`, `prop_name`) are validated against `^[A-Za-z_][A-Za-z0-9_]*$` via `_validate_sql_identifier` before being inlined into SQL strings — anything outside that pattern raises immediately. Both helpers fail loud rather than silently produce wrong results or pass injected SQL through to the parser.
|
|
34
|
+
|
|
35
|
+
### Removed
|
|
36
|
+
|
|
37
|
+
- `hypertopos.engine.subprocess_agg` (the persistent subprocess worker and its JSON command protocol) is deleted; every entry point it served is now routed through `lance_sql_agg`.
|
|
38
|
+
- `hypertopos.engine.datafusion_agg` (the in-memory external-DataFusion fast path for count / metric / pivot / gbp / percentile) is deleted along with the optional `datafusion` extra in `pyproject.toml`. Lance 4.x's built-in DataFusion executor covers every query the external package was carrying.
|
|
39
|
+
- The matching `tests/test_subprocess_agg.py` and `tests/test_datafusion_agg.py` are deleted. Aggregate semantics are exercised through `tests/test_aggregation_engine.py` and the MCP tool tests.
|
|
40
|
+
|
|
41
|
+
### Migration
|
|
42
|
+
|
|
43
|
+
- **Rebuild required.** Spheres built before 0.3.0 are still openable but degrade gracefully on graph contagion — the precomputed `contagion_stats` table is created at build time only, and the runtime scanner returns zero hits without it. Rebuild with `hypertopos build --config sphere.yaml --force`. There are no in-place upgrade paths.
|
|
44
|
+
- The optional `datafusion` package is no longer a dependency of any aggregate path. If your environment installed it via `pip install hypertopos[datafusion]`, that extra now resolves to nothing — you can drop it from your install command.
|
|
45
|
+
|
|
8
46
|
## [0.2.2] — 2026-04-11
|
|
9
47
|
|
|
10
48
|
### 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-04-
|
|
29
|
+
**Change Date:** 2029-04-12
|
|
30
30
|
|
|
31
31
|
**Change License:** Apache License, Version 2.0
|
|
32
32
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hypertopos
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.3.0
|
|
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
|
|
7
7
|
Project-URL: Repository, https://github.com/hypertopos/hypertopos-py
|
|
@@ -12,10 +12,8 @@ License-File: LICENSE.md
|
|
|
12
12
|
Requires-Python: >=3.12
|
|
13
13
|
Requires-Dist: numpy>=1.26
|
|
14
14
|
Requires-Dist: pyarrow>=14.0
|
|
15
|
-
Requires-Dist: pylance
|
|
15
|
+
Requires-Dist: pylance<5.0,>=4.0
|
|
16
16
|
Requires-Dist: pyyaml>=6.0
|
|
17
|
-
Provides-Extra: datafusion
|
|
18
|
-
Requires-Dist: datafusion>=40.0; extra == 'datafusion'
|
|
19
17
|
Provides-Extra: dev
|
|
20
18
|
Requires-Dist: mypy; extra == 'dev'
|
|
21
19
|
Requires-Dist: pytest; extra == 'dev'
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
[](LICENSE.md)
|
|
7
7
|
[](https://doi.org/10.5281/zenodo.19482069)
|
|
8
8
|
[](https://arrow.apache.org/docs/python/)
|
|
9
|
-
[](https://github.com/
|
|
9
|
+
[](https://github.com/lance-format/lance)
|
|
10
10
|
[](https://modelcontextprotocol.io)
|
|
11
|
-
[](pyproject.toml)
|
|
12
12
|
|
|
13
13
|
**hypertopos** transforms relational data into navigable geometric space.
|
|
14
14
|
|
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
> Planned direction. Priorities may change based on feedback.
|
|
4
4
|
|
|
5
|
-
## Current: 0.
|
|
5
|
+
## Current: 0.3.0
|
|
6
|
+
|
|
7
|
+
Lance 3.x perf upgrade — aggregate engine rewritten around Lance SQL, precomputed contagion stats, format 2.2, contagion anchor resolution fix, edge table auto-detect fix. See CHANGELOG.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 0.2.2
|
|
6
12
|
|
|
7
13
|
As-of graph reconstruction across edge-table graph primitives + latency fix on `detect_cross_pattern_discrepancy`. See CHANGELOG.
|
|
8
14
|
|
|
@@ -26,33 +32,57 @@ First public release — full GDS stack, π1–π12, builder, MCP server, valida
|
|
|
26
32
|
|
|
27
33
|
---
|
|
28
34
|
|
|
29
|
-
## Plan: 0.3.
|
|
35
|
+
## Plan: 0.3.1
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
- Upgrade `pylance 2.0.1 → 3.0.1` — format 2.2, ~300% faster scans, LZ4/zstd compression
|
|
33
|
-
- Replace `shutil.copytree` with `lance.dataset.clone()` in test fixtures (O(1) metadata clone)
|
|
34
|
-
- Builder: incremental rebuild (geometry-only without `--force` wipe)
|
|
37
|
+
Two production-quality improvements on top of the 0.3.0 storage layer:
|
|
35
38
|
|
|
36
|
-
**
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
- Tighten error handling in hot paths so failures are surfaced more consistently
|
|
40
|
-
- Consolidate repeated orchestration logic into shared helpers instead of duplicating it across modules
|
|
41
|
-
- Make the refactor pass incremental: preserve behavior, improve structure, then revisit deeper architectural boundaries
|
|
39
|
+
- **FDR control on anomaly queries** — Benjamini–Hochberg `fdr_alpha` parameter + per-row `q_value` column on `find_anomalies` and friends. Replaces unbounded "top-K by `delta_norm`" with a set whose false-discovery rate is bounded by α.
|
|
40
|
+
- **Diverse-coverage anomaly summarisation** — `select="diverse"` option using lazy-greedy submodular maximisation, plus a `representativeness` column ("this 1 anomaly stands in for 38 cases").
|
|
41
|
+
- **Temporal build optimization** — pre-aggregate event counts before the temporal loop, eliminating per-window event table rescans.
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
- Native temporal source support for direct temporal inputs, without requiring manual dataset plumbing in benchmark scripts
|
|
45
|
-
- Optional weighted scoring mode that uses continuous intensity instead of binary counts
|
|
46
|
-
- **SphereProfiler** — autonomous sphere scanner that profiles all patterns, runs calibration sweeps across source combinations, proposes optimal PassiveScanner composition for Layer 1 surveillance. Core loop: enumerate patterns → single-source anomaly scans → greedy multi-source combination → ranked composition report. No labeled GT needed; optional GT file unlocks supervised calibration.
|
|
43
|
+
---
|
|
47
44
|
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
## Plan: 0.3.2
|
|
46
|
+
|
|
47
|
+
README and docs narrative restructure.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Plan: 0.4.0
|
|
52
|
+
|
|
53
|
+
TBD — picked after 0.3.2 lands.
|
|
50
54
|
|
|
51
55
|
---
|
|
52
56
|
|
|
53
57
|
## Future
|
|
54
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
|
+
- Lazy chain geometry — on-demand chain delta vectors via sampled population calibration; supplements the build-time `chain_lines` path
|
|
63
|
+
|
|
64
|
+
**Builder evolution**
|
|
65
|
+
- Incremental rebuild — geometry-only without `--force` wipe
|
|
66
|
+
- Generalized (d+m+g+t+s) dimension blocks — geographic / metric / semantic dimension support
|
|
67
|
+
|
|
68
|
+
**PassiveScanner evolution**
|
|
69
|
+
- Native temporal source support for direct temporal inputs, without requiring manual dataset plumbing in benchmark scripts
|
|
70
|
+
- Optional weighted scoring mode that uses continuous intensity instead of binary counts
|
|
71
|
+
- **SphereProfiler** — autonomous sphere scanner that profiles all patterns, runs calibration sweeps across source combinations, proposes optimal PassiveScanner composition for Layer 1 surveillance. Core loop: enumerate patterns → single-source anomaly scans → greedy multi-source combination → ranked composition report. No labeled GT needed; optional GT file unlocks supervised calibration.
|
|
72
|
+
|
|
73
|
+
**Code refactoring**
|
|
74
|
+
- Break up oversized modules into smaller, domain-focused components
|
|
75
|
+
- Reduce coupling between core layers by replacing private cross-layer access with explicit interfaces where practical
|
|
76
|
+
- Tighten error handling in hot paths so failures are surfaced more consistently
|
|
77
|
+
- Consolidate repeated orchestration logic into shared helpers instead of duplicating it across modules
|
|
78
|
+
- Incremental refactor: preserve behavior, improve structure, then revisit deeper architectural boundaries
|
|
79
|
+
|
|
80
|
+
**Cross-sphere capabilities**
|
|
55
81
|
- Cross-sphere comparison — dimensionless metrics across independently calibrated coordinate spaces
|
|
56
82
|
- What-if analysis — hypothetical edge changes producing modified coordinate vectors
|
|
83
|
+
|
|
84
|
+
**Enterprise / governance**
|
|
57
85
|
- Dimension access control — per-agent visibility constraints on delta dimensions
|
|
86
|
+
|
|
87
|
+
**Tooling**
|
|
58
88
|
- Runtime latency benchmarks in package docs
|
|
@@ -24,7 +24,7 @@ The core package is split into a few layers:
|
|
|
24
24
|
|
|
25
25
|
- **Model**: sphere, line, pattern, alias, point, edge, polygon, solid, manifest, contract
|
|
26
26
|
- **Storage**: readers, writers, caches, and physical layout handling
|
|
27
|
-
- **Engine**: geometry, anomaly scoring, similarity, drift, forecasting, aggregation, calibration tracking
|
|
27
|
+
- **Engine**: geometry, anomaly scoring, similarity, drift, forecasting, aggregation (Lance SQL pushdown via `lance_sql_agg`), calibration tracking
|
|
28
28
|
- **Navigation**: stateful step-by-step movement through the sphere
|
|
29
29
|
- **PassiveScanner**: multi-source batch screening over navigation primitives
|
|
30
30
|
- **Builder**: transforms source data into a complete sphere
|
|
@@ -17,7 +17,8 @@ gds_{sphere_id}/
|
|
|
17
17
|
│ ├── geometry_stats/ # precomputed population summaries
|
|
18
18
|
│ ├── trajectory/ # ANN index for trajectory similarity search
|
|
19
19
|
│ ├── temporal_centroids/ # cached population centroids per time window
|
|
20
|
-
│
|
|
20
|
+
│ ├── edge_stats/ # per-event-pattern edge table summary cache (row count, unique from/to, ts/amount range)
|
|
21
|
+
│ └── contagion_stats/ # per-pattern (primary_key, neighbor_count, anomalous_neighbor_count, contagion_ratio) — feeds the graph contagion scanner directly, BTREE-indexed on primary_key
|
|
21
22
|
├── points/
|
|
22
23
|
│ ├── {line_id}/v={n}/
|
|
23
24
|
│ │ └── data.lance # entity records for this line version
|
|
@@ -1380,16 +1380,24 @@ class GDSBuilder:
|
|
|
1380
1380
|
schema_names = set(line_reg.table.schema.names)
|
|
1381
1381
|
|
|
1382
1382
|
ts_candidates = (
|
|
1383
|
-
"timestamp", "ts", "event_time", "
|
|
1383
|
+
"timestamp", "ts", "event_time", "tx_date", "date",
|
|
1384
1384
|
)
|
|
1385
1385
|
amt_candidates = (
|
|
1386
1386
|
"amount_received", "amount", "amount_paid", "value",
|
|
1387
|
-
"total", "amt",
|
|
1387
|
+
"total", "amt", "fare_amount", "total_amount",
|
|
1388
1388
|
)
|
|
1389
1389
|
|
|
1390
1390
|
ts_col = next(
|
|
1391
1391
|
(c for c in ts_candidates if c in schema_names), None,
|
|
1392
1392
|
)
|
|
1393
|
+
# Type-based fallback: first non-metadata column with timestamp type
|
|
1394
|
+
_META_COLS = {"created_at", "changed_at", "version"}
|
|
1395
|
+
if ts_col is None:
|
|
1396
|
+
import pyarrow as pa
|
|
1397
|
+
for field in line_reg.table.schema:
|
|
1398
|
+
if pa.types.is_timestamp(field.type) and field.name not in _META_COLS:
|
|
1399
|
+
ts_col = field.name
|
|
1400
|
+
break
|
|
1393
1401
|
amt_col = next(
|
|
1394
1402
|
(c for c in amt_candidates if c in schema_names), None,
|
|
1395
1403
|
)
|
|
@@ -1417,23 +1425,31 @@ class GDSBuilder:
|
|
|
1417
1425
|
to_arr = event_table[cfg.to_col]
|
|
1418
1426
|
event_key_arr = event_table["primary_key"]
|
|
1419
1427
|
|
|
1420
|
-
# Timestamp
|
|
1428
|
+
# Timestamp — resolve from config, then name heuristic, then type fallback
|
|
1421
1429
|
ts_arr: pa.Array
|
|
1422
1430
|
ts_col = cfg.timestamp_col
|
|
1423
1431
|
if ts_col and ts_col in schema_names:
|
|
1424
1432
|
ts_arr = self._to_epoch_seconds(event_table[ts_col])
|
|
1425
1433
|
else:
|
|
1426
|
-
# Try common column names
|
|
1427
1434
|
for name in ("timestamp", "ts", "date", "created_at", "tx_date"):
|
|
1428
1435
|
if name in schema_names:
|
|
1429
1436
|
ts_arr = self._to_epoch_seconds(event_table[name])
|
|
1430
1437
|
break
|
|
1431
1438
|
else:
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1439
|
+
# Type-based fallback: first non-metadata column with timestamp type
|
|
1440
|
+
_META = {"created_at", "changed_at", "version"}
|
|
1441
|
+
ts_resolved = False
|
|
1442
|
+
for field in event_table.schema:
|
|
1443
|
+
if pa.types.is_timestamp(field.type) and field.name not in _META:
|
|
1444
|
+
ts_arr = self._to_epoch_seconds(event_table[field.name])
|
|
1445
|
+
ts_resolved = True
|
|
1446
|
+
break
|
|
1447
|
+
if not ts_resolved:
|
|
1448
|
+
ts_arr = pa.array(
|
|
1449
|
+
[0.0] * len(event_table), type=pa.float64(),
|
|
1450
|
+
)
|
|
1435
1451
|
|
|
1436
|
-
# Amount
|
|
1452
|
+
# Amount — resolve from config, then name heuristic
|
|
1437
1453
|
amt_arr: pa.Array
|
|
1438
1454
|
amt_col = cfg.amount_col
|
|
1439
1455
|
if amt_col and amt_col in schema_names:
|
|
@@ -1441,8 +1457,10 @@ class GDSBuilder:
|
|
|
1441
1457
|
pc.fill_null(event_table[amt_col], 0.0), pa.float64(),
|
|
1442
1458
|
)
|
|
1443
1459
|
else:
|
|
1444
|
-
|
|
1445
|
-
|
|
1460
|
+
for name in (
|
|
1461
|
+
"amount", "value", "total", "amt",
|
|
1462
|
+
"fare_amount", "total_amount", "amount_received", "amount_paid",
|
|
1463
|
+
):
|
|
1446
1464
|
if name in schema_names:
|
|
1447
1465
|
amt_arr = pc.cast(
|
|
1448
1466
|
pc.fill_null(event_table[name], 0.0), pa.float64(),
|
|
@@ -2222,6 +2240,12 @@ class GDSBuilder:
|
|
|
2222
2240
|
_, stats = _build_and_write(pat_id, pat)
|
|
2223
2241
|
pattern_stats[pat_id] = stats
|
|
2224
2242
|
|
|
2243
|
+
# 2.5 Precompute per-entity contagion stats for every pattern with an
|
|
2244
|
+
# edge table. The runtime scanner reads this small table directly
|
|
2245
|
+
# instead of replaying the full edge table on every passive_scan /
|
|
2246
|
+
# composite_risk call.
|
|
2247
|
+
self._build_contagion_stats(_stats_writer)
|
|
2248
|
+
|
|
2225
2249
|
# 3. Write sphere.json
|
|
2226
2250
|
sphere_data = self._build_sphere_json(pattern_stats)
|
|
2227
2251
|
meta_dir = self.output_path / "_gds_meta"
|
|
@@ -2241,6 +2265,106 @@ class GDSBuilder:
|
|
|
2241
2265
|
|
|
2242
2266
|
return str(self.output_path)
|
|
2243
2267
|
|
|
2268
|
+
def _resolve_anchor_pattern_for_edge_table(
|
|
2269
|
+
self, pat_id: str, pat: _PatternReg,
|
|
2270
|
+
) -> str | None:
|
|
2271
|
+
"""Find which anchor pattern's geometry holds the is_anomaly bitmap
|
|
2272
|
+
for entities appearing in *pat*'s edge table.
|
|
2273
|
+
|
|
2274
|
+
For non-event patterns the pattern is its own anchor (its edges connect
|
|
2275
|
+
entities of its own line). For event patterns we walk the relations and
|
|
2276
|
+
return the anchor that owns the entity line of the first matching
|
|
2277
|
+
relation. Mirrors PassiveScanner._resolve_anchor_for_event so the
|
|
2278
|
+
precomputed table covers exactly the keys the scanner asks about at
|
|
2279
|
+
runtime.
|
|
2280
|
+
"""
|
|
2281
|
+
if pat.pattern_type != "event":
|
|
2282
|
+
return pat_id
|
|
2283
|
+
for other_id, other in self._patterns.items():
|
|
2284
|
+
if other.pattern_type != "anchor" or other_id == pat_id:
|
|
2285
|
+
continue
|
|
2286
|
+
for rel in pat.relations:
|
|
2287
|
+
if rel.line_id == other.entity_line:
|
|
2288
|
+
return other_id
|
|
2289
|
+
return None
|
|
2290
|
+
|
|
2291
|
+
def _build_contagion_stats(self, stats_writer: Any) -> None:
|
|
2292
|
+
"""Second pass: precompute per-entity contagion statistics for every
|
|
2293
|
+
pattern that has an edge table on disk. Writes one Lance table per
|
|
2294
|
+
pattern under ``_gds_meta/contagion_stats/{pattern_id}.lance`` with a
|
|
2295
|
+
BTREE index on ``primary_key``.
|
|
2296
|
+
|
|
2297
|
+
Schema: primary_key, neighbor_count (int32), anomalous_neighbor_count
|
|
2298
|
+
(int32), contagion_ratio (float32). Only entities that appear as
|
|
2299
|
+
at least one edge endpoint are included, so neighbor_count is
|
|
2300
|
+
always >= 1 in practice.
|
|
2301
|
+
|
|
2302
|
+
The runtime scanner reads this small table directly instead of replaying
|
|
2303
|
+
the full edge table on every passive_scan / composite_risk call.
|
|
2304
|
+
"""
|
|
2305
|
+
import math
|
|
2306
|
+
from collections import defaultdict
|
|
2307
|
+
|
|
2308
|
+
import lance as _lance
|
|
2309
|
+
|
|
2310
|
+
for pat_id, pat in self._patterns.items():
|
|
2311
|
+
edge_path = self.output_path / "edges" / pat_id / "data.lance"
|
|
2312
|
+
if not edge_path.exists():
|
|
2313
|
+
continue
|
|
2314
|
+
anchor_pat_id = self._resolve_anchor_pattern_for_edge_table(pat_id, pat)
|
|
2315
|
+
if anchor_pat_id is None:
|
|
2316
|
+
continue
|
|
2317
|
+
edges_ds = _lance.dataset(str(edge_path))
|
|
2318
|
+
if edges_ds.count_rows() == 0:
|
|
2319
|
+
continue
|
|
2320
|
+
edges_table = edges_ds.scanner(columns=["from_key", "to_key"]).to_table()
|
|
2321
|
+
from_arr = edges_table["from_key"].to_pylist()
|
|
2322
|
+
to_arr = edges_table["to_key"].to_pylist()
|
|
2323
|
+
neighbors: dict[str, set[str]] = defaultdict(set)
|
|
2324
|
+
for f, t in zip(from_arr, to_arr, strict=False):
|
|
2325
|
+
if f != t:
|
|
2326
|
+
neighbors[f].add(t)
|
|
2327
|
+
neighbors[t].add(f)
|
|
2328
|
+
if not neighbors:
|
|
2329
|
+
continue
|
|
2330
|
+
|
|
2331
|
+
geom_path = (
|
|
2332
|
+
self.output_path / "geometry" / anchor_pat_id / "v=1" / "data.lance"
|
|
2333
|
+
)
|
|
2334
|
+
if not geom_path.exists():
|
|
2335
|
+
continue
|
|
2336
|
+
anomalous: set[str] = set()
|
|
2337
|
+
geom_table = _lance.dataset(str(geom_path)).scanner(
|
|
2338
|
+
columns=["primary_key", "is_anomaly"],
|
|
2339
|
+
).to_table()
|
|
2340
|
+
pk_col = geom_table["primary_key"]
|
|
2341
|
+
anom_col = geom_table["is_anomaly"]
|
|
2342
|
+
for i in range(geom_table.num_rows):
|
|
2343
|
+
if anom_col[i].as_py():
|
|
2344
|
+
anomalous.add(pk_col[i].as_py())
|
|
2345
|
+
|
|
2346
|
+
primary_keys = sorted(neighbors.keys())
|
|
2347
|
+
nbr_counts: list[int] = []
|
|
2348
|
+
anom_counts: list[int] = []
|
|
2349
|
+
ratios: list[float] = []
|
|
2350
|
+
for k in primary_keys:
|
|
2351
|
+
nbs = neighbors[k]
|
|
2352
|
+
total = len(nbs)
|
|
2353
|
+
anom = len(nbs & anomalous)
|
|
2354
|
+
nbr_counts.append(total)
|
|
2355
|
+
anom_counts.append(anom)
|
|
2356
|
+
ratios.append(anom / total if total > 0 else math.nan)
|
|
2357
|
+
|
|
2358
|
+
contagion_table = pa.table(
|
|
2359
|
+
{
|
|
2360
|
+
"primary_key": pa.array(primary_keys, type=pa.string()),
|
|
2361
|
+
"neighbor_count": pa.array(nbr_counts, type=pa.int32()),
|
|
2362
|
+
"anomalous_neighbor_count": pa.array(anom_counts, type=pa.int32()),
|
|
2363
|
+
"contagion_ratio": pa.array(ratios, type=pa.float32()),
|
|
2364
|
+
},
|
|
2365
|
+
)
|
|
2366
|
+
stats_writer.write_contagion_stats(pat_id, contagion_table)
|
|
2367
|
+
|
|
2244
2368
|
def incremental_update(
|
|
2245
2369
|
self,
|
|
2246
2370
|
pattern_id: str,
|
|
@@ -3706,3 +3830,22 @@ class GDSBuilder:
|
|
|
3706
3830
|
f"Pattern '{pat_id}' event dimension: "
|
|
3707
3831
|
f"column '{edim.column}' must be numeric, got '{col_type}'"
|
|
3708
3832
|
)
|
|
3833
|
+
|
|
3834
|
+
# A pattern with zero declared dimensions has no geometry to compute —
|
|
3835
|
+
# delta vectors collapse to width 0. Reject up front: silent acceptance
|
|
3836
|
+
# used to be possible under Lance format 2.0, but format 2.1+ panics on
|
|
3837
|
+
# fixed_size_list[0] columns (lance-format/lance#5102), and an anomaly
|
|
3838
|
+
# geometry without dimensions is meaningless regardless of the storage
|
|
3839
|
+
# format. Tracked properties count as dimension sources because they
|
|
3840
|
+
# become prop_columns in the resolved delta width.
|
|
3841
|
+
if (
|
|
3842
|
+
not pat.relations
|
|
3843
|
+
and not pat.event_dimensions
|
|
3844
|
+
and not pat.tracked_properties
|
|
3845
|
+
):
|
|
3846
|
+
raise ValueError(
|
|
3847
|
+
f"Pattern '{pat_id}' has no dimensions: declare at least one "
|
|
3848
|
+
f"relation, event dimension, derived dimension, or "
|
|
3849
|
+
f"tracked_properties entry. A pattern with zero dimensions "
|
|
3850
|
+
f"cannot produce a meaningful geometry."
|
|
3851
|
+
)
|
|
@@ -750,6 +750,14 @@ def _add_chain_line(
|
|
|
750
750
|
ts_col = col_name
|
|
751
751
|
needed_cols.append(ts_col)
|
|
752
752
|
break
|
|
753
|
+
# Type-based fallback: first column with timestamp type
|
|
754
|
+
if ts_col is None:
|
|
755
|
+
import pyarrow as pa
|
|
756
|
+
for field in event_table.schema:
|
|
757
|
+
if pa.types.is_timestamp(field.type):
|
|
758
|
+
ts_col = field.name
|
|
759
|
+
needed_cols.append(ts_col)
|
|
760
|
+
break
|
|
753
761
|
|
|
754
762
|
cat_col = None
|
|
755
763
|
for col in ("receiving_currency", "category", "k_symbol", "type"):
|
|
@@ -759,7 +767,7 @@ def _add_chain_line(
|
|
|
759
767
|
break
|
|
760
768
|
|
|
761
769
|
amt_col = None
|
|
762
|
-
for col in ("amount_received", "amount", "extendedprice"):
|
|
770
|
+
for col in ("amount_received", "amount", "extendedprice", "fare_amount", "total_amount"):
|
|
763
771
|
if col in event_table.schema.names:
|
|
764
772
|
amt_col = col
|
|
765
773
|
needed_cols.append(amt_col)
|