hypertopos 0.2.1__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.1 → hypertopos-0.3.0}/CHANGELOG.md +56 -7
- {hypertopos-0.2.1 → hypertopos-0.3.0}/LICENSE.md +1 -1
- {hypertopos-0.2.1 → hypertopos-0.3.0}/PKG-INFO +3 -5
- {hypertopos-0.2.1 → hypertopos-0.3.0}/README.md +4 -4
- hypertopos-0.3.0/ROADMAP.md +88 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/SECURITY.md +1 -1
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/api-reference.md +8 -8
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/architecture.md +6 -2
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/concepts.md +1 -1
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/data-format.md +2 -1
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/__init__.py +1 -1
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/builder/builder.py +153 -10
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/cli/build.py +9 -1
- {hypertopos-0.2.1 → 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.1 → hypertopos-0.3.0}/hypertopos/navigation/navigator.py +168 -85
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/navigation/scanner.py +55 -72
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/storage/reader.py +34 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/storage/writer.py +35 -3
- {hypertopos-0.2.1 → hypertopos-0.3.0}/pyproject.toml +3 -6
- hypertopos-0.3.0/tests/conftest.py +89 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_aggregation_engine.py +0 -66
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_builder.py +28 -6
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_builder_derived.py +18 -3
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_calibration.py +10 -8
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_detection.py +25 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_fts.py +15 -5
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_geometric_path.py +107 -8
- {hypertopos-0.2.1 → 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.1 → hypertopos-0.3.0}/tests/test_navigation.py +34 -21
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_scanner.py +57 -0
- hypertopos-0.2.1/ROADMAP.md +0 -46
- hypertopos-0.2.1/hypertopos/engine/datafusion_agg.py +0 -602
- hypertopos-0.2.1/hypertopos/engine/subprocess_agg.py +0 -742
- hypertopos-0.2.1/tests/conftest.py +0 -83
- hypertopos-0.2.1/tests/test_datafusion_agg.py +0 -788
- hypertopos-0.2.1/tests/test_subprocess_agg.py +0 -961
- {hypertopos-0.2.1 → hypertopos-0.3.0}/.github/workflows/publish-pypi.yml +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/.gitignore +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/README.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/LICENSE +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/README.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/README.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/SUMMARY.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-settings.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-settings.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-settings.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-settings.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-settings.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-settings.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/prepare_accounts.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/prepare_cpty_banks.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/prepare_districts.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/prepare_loan_accounts.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/prepare_operations.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/prepare_orders.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/prepare_transactions.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/prepare_tx_types.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/sphere.yaml +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/README.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/_layer2_cross_eval.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/_layer3_cross_eval.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_accounts.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_currencies.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_payment_formats.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_transactions.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/hi_small_sphere/sphere.yaml +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/layer1_passive_scan.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/layer2_xgboost_sweep.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/layer3_typology_filter.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/li_small_sphere/prepare_accounts.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/li_small_sphere/prepare_currencies.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/li_small_sphere/prepare_payment_formats.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/li_small_sphere/prepare_transactions.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/li_small_sphere/sphere.yaml +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/passive-scan-calibration.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/README.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/SUMMARY.md +0 -0
- {hypertopos-0.2.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-settings.md +0 -0
- {hypertopos-0.2.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-settings.md +0 -0
- {hypertopos-0.2.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-settings.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/sphere/prepare_trips.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/sphere/sphere.yaml +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmarks.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/configuration.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/images/build-pipeline.svg +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/images/core-objects.svg +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/images/cutting-plane.svg +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/images/directory-layout.svg +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/images/hypertopos-overview.svg +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/images/model-lifecycle.svg +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/images/navigation-primitives.svg +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/images/three-scales.svg +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/quickstart.md +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/builder/__init__.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/builder/_stats.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/builder/_writer.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/builder/derived.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/builder/mapping.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/cli/__init__.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/cli/__main__.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/cli/info.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/cli/main.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/cli/schema.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/cli/sources.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/engine/__init__.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/engine/calibration.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/engine/chains.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/engine/composition.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/engine/forecast.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/engine/geometry.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/engine/invalidation.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/engine/investigation.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/model/__init__.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/model/manifest.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/model/objects.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/model/sphere.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/navigation/__init__.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/sphere.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/storage/__init__.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/storage/_schemas.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/storage/cache.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/utils/__init__.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/utils/arrow.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/.gitkeep +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/fixtures/generate_fixtures.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_arrow_utils.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_builder_alias.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_builder_edges.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_cache.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_chains.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_cli.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_cli_alias.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_cli_sources.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_compaction.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_composition.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_continuous_event_dims.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_data_quality_prop.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_edge_table.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_engine.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_forecast.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_geometry_stats.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_hybrid_search.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_invalidation.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_investigation.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_mapping.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_model.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_mvcc_session.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_navigator_alerts.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_observability.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_precision_stack.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_reputation.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_row_id_cache.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_sphere.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_sphere_overview_perf.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_stats_guard.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_storage.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_streaming_reads.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_temporal_centroids.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_trajectory_ann.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_witness_cohort.py +0 -0
- {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_witness_set.py +0 -0
|
@@ -5,6 +5,61 @@ 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
|
+
|
|
46
|
+
## [0.2.2] — 2026-04-11
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
**Temporal as-of reconstruction across edge-table graph primitives**
|
|
51
|
+
- `contagion_score()`, `contagion_score_batch()` — optional keyword-only `timestamp_cutoff: float | None` parameter. When set, only edges with `timestamp <= timestamp_cutoff` are considered. Batch forwards the cutoff to every per-entity call.
|
|
52
|
+
- `entity_flow()` — same `timestamp_cutoff` parameter; both outgoing and incoming edge reads honor the cutoff, so net flow reflects the as-of graph state.
|
|
53
|
+
- `degree_velocity()` — same parameter; buckets derive from the filtered edge set so the last bucket endpoint is naturally `<= timestamp_cutoff`.
|
|
54
|
+
- `propagate_influence()` — same parameter; threaded through the BFS `_expand_neighbors` closure so expansion follows only edges with `timestamp <= cutoff`.
|
|
55
|
+
- `find_counterparties()` — same parameter on the edge-table fast path via `_find_counterparties_via_edges`. The points-scan fallback has no timestamp column and **raises `GDSNavigationError`** when `timestamp_cutoff` is supplied — fail loudly instead of silently returning unfiltered results.
|
|
56
|
+
|
|
57
|
+
The semantic mirrors the existing `WitnessCohortConfig.timestamp_cutoff` from 0.2.1: edges with `timestamp <= timestamp_cutoff` are included. Enables agents to reconstruct contagion, flow, connection velocity, and influence propagation state at a prior point in time — useful for incident forensics ("what did this neighborhood look like on the day the alert fired?"), retroactive detection validation, and historical-snapshot comparisons.
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
|
|
61
|
+
- `detect_cross_pattern_discrepancy` no longer triggers full edge-table scans through `PassiveScanner.auto_discover`. The scanner gains an `include_graph: bool = True` keyword-only parameter; `detect_cross_pattern_discrepancy` calls it with `include_graph=False` because graph contagion plays no role in the downstream geometry-disagreement check. Eliminates the dominant latency regression on multi-pattern spheres with edge tables — the discrepancy detector previously paid one full edge-table read per event pattern with no signal benefit. Every other `auto_discover` caller (`composite_risk`, explicit scanner use) stays at the graph-enabled default.
|
|
62
|
+
|
|
8
63
|
## [0.2.1] — 2026-04-11
|
|
9
64
|
|
|
10
65
|
### Added
|
|
@@ -15,17 +70,11 @@ Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
15
70
|
- Excludes already-connected entities via BTREE edge lookup; bidirectional check by default; `timestamp_cutoff` for as-of evaluation in temporal hold-out
|
|
16
71
|
- Auto-resolves the event pattern with edge table covering an anchor's entity line; explicit `edge_pattern_id` override is validated
|
|
17
72
|
- Trajectory branch decided once per call (not per candidate) — when ref has trajectory, missing candidates get neutral 0.5 instead of mixed renormalization
|
|
18
|
-
- Batch trajectory load via single Lance scan instead of per-candidate (~11× speedup
|
|
73
|
+
- Batch trajectory load via single Lance scan instead of per-candidate (~11× speedup over the per-candidate path)
|
|
19
74
|
- `WitnessCohortConfig` and `WitnessCohortWeights` dataclasses group all tunable parameters; navigator API takes a single `config=` keyword
|
|
20
75
|
- `CohortMember` and `WitnessCohortResult` frozen dataclasses with per-component scores, exclusion counts, and reproducibility metadata
|
|
21
76
|
- `GDSEngine.witness_jaccard()`, `GDSEngine.trajectory_cosine()`, `GDSEngine.composite_link_score()` — pure scoring helpers exposed for reuse
|
|
22
77
|
|
|
23
|
-
**Empirical validation on AML HI-small** (5M edges, 6357 labeled launderers):
|
|
24
|
-
- Co-laundering precision@10: **25.3%** (vs 1.2% base rate = **20.5× lift**)
|
|
25
|
-
- 2.6× improvement over `find_similar_entities + is_anomaly` baseline (which scored 6.5%)
|
|
26
|
-
- Top-10 overlap with that baseline: 15.5% (function returns substantively different entities, not the same ranking)
|
|
27
|
-
- Temporal hold-out recall@10: **0.0%** — confirms the function does NOT forecast future edges; it surfaces existing geometric peers
|
|
28
|
-
|
|
29
78
|
## [0.2.0] — 2026-04-10
|
|
30
79
|
|
|
31
80
|
### 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
|
|
|
@@ -38,7 +38,7 @@ Navigation primitives turn this space into a stateful, step-by-step workspace, w
|
|
|
38
38
|
|
|
39
39
|
- **Geometry, not queries** — entities live in a population-calibrated coordinate space (μ, σ, θ). Position tells you what's typical. Distance reveals what's unusual, what's similar, and where clusters form.
|
|
40
40
|
- **Navigate, don't search** — twelve primitives (π1–π12) let agents walk lines, jump across relationships, cluster populations, compare groups, find hubs, and track drift. Stateful, composable, geometric.
|
|
41
|
-
- **Graph meets geometry** — edge tables give runtime graph traversal with geometric path scoring. Find paths scored by witness overlap and anomaly propagation — not just hop count. Lazy chain discovery without build-time extraction. Surface witness cohorts (`find_witness_cohort`) — entities sharing the target's anomaly signature, ranked by delta similarity, witness overlap, trajectory alignment, and graded anomaly bonus, with already-connected entities filtered out. Investigative peer ranking
|
|
41
|
+
- **Graph meets geometry** — edge tables give runtime graph traversal with geometric path scoring. Find paths scored by witness overlap and anomaly propagation — not just hop count. Lazy chain discovery without build-time extraction. Surface witness cohorts (`find_witness_cohort`) — entities sharing the target's anomaly signature, ranked by delta similarity, witness overlap, trajectory alignment, and graded anomaly bonus, with already-connected entities filtered out. Investigative peer ranking, not edge forecasting.
|
|
42
42
|
- **Time is built in** — polygons accumulate into solids. Trajectory similarity, regime change detection, and drift analysis are first-class — not bolted on.
|
|
43
43
|
- **Agents first** — designed for MCP tool-calling. Smart detection recipes, investigation workflows. The sphere is the agent's workspace.
|
|
44
44
|
- **Zero training** — no ML models, no labels, no training pipeline. Population statistics and empirical thresholds. Deploy in minutes.
|
|
@@ -56,7 +56,7 @@ Navigation primitives turn this space into a stateful, step-by-step workspace, w
|
|
|
56
56
|
| **Navigation** | 12 stateful primitives | Query-based | KNN search | N/A |
|
|
57
57
|
| **Multi-hop** | Edge table (runtime graph + lazy chains) + pre-computed chain geometry | Native (Cypher, ad-hoc) | No | No |
|
|
58
58
|
| **Anomaly propagation** | Contagion scoring + influence BFS in delta-space | No | No | Local neighborhood only |
|
|
59
|
-
| **Witness cohort discovery** | Witness overlap + delta + trajectory + edge absence
|
|
59
|
+
| **Witness cohort discovery** | Witness overlap + delta + trajectory + edge absence | Topological only (Adamic-Adar, common neighbors) | KNN, no graph awareness | Learned embeddings (training required) |
|
|
60
60
|
| **Agent-native (MCP)** | Yes | No | No | No |
|
|
61
61
|
| **Explains why** | Witness sets, dimension ranking | Centrality scores | Nearest neighbors | Anomaly scores |
|
|
62
62
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# hypertopos — Roadmap
|
|
2
|
+
|
|
3
|
+
> Planned direction. Priorities may change based on feedback.
|
|
4
|
+
|
|
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
|
|
12
|
+
|
|
13
|
+
As-of graph reconstruction across edge-table graph primitives + latency fix on `detect_cross_pattern_discrepancy`. See CHANGELOG.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 0.2.1
|
|
18
|
+
|
|
19
|
+
Witness cohort discovery — investigative peer ranking. See CHANGELOG.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 0.2.0
|
|
24
|
+
|
|
25
|
+
Graph meets geometry — edge table, runtime traversal, contagion/influence, +11 navigator functions. See CHANGELOG.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 0.1.0
|
|
30
|
+
|
|
31
|
+
First public release — full GDS stack, π1–π12, builder, MCP server, validation suite. See CHANGELOG.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Plan: 0.3.1
|
|
36
|
+
|
|
37
|
+
Two production-quality improvements on top of the 0.3.0 storage layer:
|
|
38
|
+
|
|
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
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
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.
|
|
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
|
+
- 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**
|
|
81
|
+
- Cross-sphere comparison — dimensionless metrics across independently calibrated coordinate spaces
|
|
82
|
+
- What-if analysis — hypothetical edge changes producing modified coordinate vectors
|
|
83
|
+
|
|
84
|
+
**Enterprise / governance**
|
|
85
|
+
- Dimension access control — per-agent visibility constraints on delta dimensions
|
|
86
|
+
|
|
87
|
+
**Tooling**
|
|
88
|
+
- Runtime latency benchmarks in package docs
|
|
@@ -124,15 +124,15 @@ with sphere.session("agent-1") as session:
|
|
|
124
124
|
|--------|-------------|
|
|
125
125
|
| `find_geometric_path(from_key, to_key, pattern_id, max_depth=5, beam_width=10, scoring="geometric")` | Beam search for paths between two entities via the edge table, scored by geometric coherence. Scoring modes: `"geometric"` (witness overlap + delta alignment + anomaly preservation), `"amount"` (geometric score modulated by log(transaction amount)), `"anomaly"` (prefer paths through anomalous entities), `"shortest"` (plain BFS, no geometric scoring). Returns best paths with per-hop scores |
|
|
126
126
|
| `discover_chains(primary_key, pattern_id, time_window_hours=168, max_hops=10, min_hops=2, max_chains=100, direction="forward")` | Runtime temporal BFS on the edge table to discover entity chains from a starting point. Unlike `find_chains_for_entity()` which queries pre-computed chains, this performs live traversal -- works without build-time chain extraction |
|
|
127
|
-
| `entity_flow(primary_key, pattern_id, top_n=20)` | Net flow analysis per counterparty via edge table. Two edge lookups (outgoing + incoming), sum amounts, compute per-counterparty net flow. Returns outgoing/incoming totals, net_flow, flow_direction, counterparties sorted by abs(net_flow) |
|
|
128
|
-
| `contagion_score(primary_key, pattern_id)` | Score how many of an entity's counterparties are anomalous via edge table + geometry check. Returns score (0.0–1.0), total/anomalous counterparty counts |
|
|
129
|
-
| `contagion_score_batch(primary_keys, pattern_id, max_keys=200)` | Batch contagion scoring for multiple entities. Returns per-entity scores plus summary (mean, max, high_contagion_count) |
|
|
130
|
-
| `degree_velocity(primary_key, pattern_id, n_buckets=4)` | Temporal connection velocity — buckets edges by timestamp, counts unique counterparties per bucket. Velocity = last_bucket_degree / first_bucket_degree. Returns buckets with out/in degree, velocity metrics |
|
|
127
|
+
| `entity_flow(primary_key, pattern_id, top_n=20, *, timestamp_cutoff=None)` | Net flow analysis per counterparty via edge table. Two edge lookups (outgoing + incoming), sum amounts, compute per-counterparty net flow. `timestamp_cutoff` (Unix seconds) restricts to edges with `timestamp <= cutoff`. Returns outgoing/incoming totals, net_flow, flow_direction, counterparties sorted by abs(net_flow) |
|
|
128
|
+
| `contagion_score(primary_key, pattern_id, *, timestamp_cutoff=None)` | Score how many of an entity's counterparties are anomalous via edge table + geometry check. `timestamp_cutoff` enables as-of contagion reconstruction. Returns score (0.0–1.0), total/anomalous counterparty counts |
|
|
129
|
+
| `contagion_score_batch(primary_keys, pattern_id, max_keys=200, *, timestamp_cutoff=None)` | Batch contagion scoring for multiple entities — forwards `timestamp_cutoff` to each per-entity call. Returns per-entity scores plus summary (mean, max, high_contagion_count) |
|
|
130
|
+
| `degree_velocity(primary_key, pattern_id, n_buckets=4, *, timestamp_cutoff=None)` | Temporal connection velocity — buckets edges by timestamp, counts unique counterparties per bucket. Velocity = last_bucket_degree / first_bucket_degree. `timestamp_cutoff` clamps the last bucket endpoint by filtering edges at the read level. Returns buckets with out/in degree, velocity metrics |
|
|
131
131
|
| `investigation_coverage(primary_key, pattern_id, explored_keys)` | Agent guidance: how much of an entity's edge neighborhood has been explored. Splits counterparties into explored/unexplored, batch anomaly check on unexplored |
|
|
132
|
-
| `propagate_influence(seed_keys, pattern_id, max_depth=3, decay=0.7, min_threshold=0.001, max_affected=10_000)` | BFS influence propagation from seed entities with geometric decay and tx_count weighting. At each hop: influence = parent_score * decay * geometric_coherence * tx_weight. Returns affected_entities with tx_count per neighbor |
|
|
132
|
+
| `propagate_influence(seed_keys, pattern_id, max_depth=3, decay=0.7, min_threshold=0.001, max_affected=10_000, *, timestamp_cutoff=None)` | BFS influence propagation from seed entities with geometric decay and tx_count weighting. At each hop: influence = parent_score * decay * geometric_coherence * tx_weight. `timestamp_cutoff` restricts BFS expansion to edges with `timestamp <= cutoff`. Returns affected_entities with tx_count per neighbor |
|
|
133
133
|
| `cluster_bridges(pattern_id, n_clusters=5, top_n_bridges=10)` | Find entities bridging geometric clusters via edge table. Runs π8 clustering then identifies cross-cluster edges and bridge entities |
|
|
134
134
|
| `anomalous_edges(from_key, to_key, pattern_id, top_n=10)` | Find edges between two entities enriched with event-level geometry (delta_norm, is_anomaly). Unlike path tools which score entities (anchor), this scores individual transactions (event geometry) |
|
|
135
|
-
| `find_witness_cohort(primary_key, pattern_id, top_n=10, *, config=None, edge_pattern_id=None)` | Rank entities that share the target's witness signature. Investigative peer ranking — NOT edge forecasting. Combines four signals: `exp(-distance/theta)` delta similarity, witness Jaccard overlap, trajectory cosine alignment (optional), and graded anomaly bonus from `delta_rank_pct`. Excludes entities already connected via BTREE edge lookup — this is the function's main contribution over plain ANN. Configure via `WitnessCohortConfig(weights=WitnessCohortWeights(...), candidate_pool, min_witness_overlap, min_score, use_trajectory, bidirectional_check, timestamp_cutoff)`. Returns `WitnessCohortResult` with ranked `CohortMember` items, per-component scores, exclusion counts, and reproducibility metadata
|
|
135
|
+
| `find_witness_cohort(primary_key, pattern_id, top_n=10, *, config=None, edge_pattern_id=None)` | Rank entities that share the target's witness signature. Investigative peer ranking — NOT edge forecasting. Combines four signals: `exp(-distance/theta)` delta similarity, witness Jaccard overlap, trajectory cosine alignment (optional), and graded anomaly bonus from `delta_rank_pct`. Excludes entities already connected via BTREE edge lookup — this is the function's main contribution over plain ANN. Configure via `WitnessCohortConfig(weights=WitnessCohortWeights(...), candidate_pool, min_witness_overlap, min_score, use_trajectory, bidirectional_check, timestamp_cutoff)`. Returns `WitnessCohortResult` with ranked `CohortMember` items, per-component scores, exclusion counts, and reproducibility metadata |
|
|
136
136
|
|
|
137
137
|
### Analysis and Detection
|
|
138
138
|
|
|
@@ -149,7 +149,7 @@ with sphere.session("agent-1") as session:
|
|
|
149
149
|
| `cross_pattern_profile(primary_key, line_id)` | Anomaly status from all patterns the entity participates in |
|
|
150
150
|
| `find_chains_for_entity(primary_key, pattern_id, top_n)` | Find chains involving a specific entity |
|
|
151
151
|
| `find_neighborhood(primary_key, pattern_id, max_hops)` | BFS through polygon edges to find reachable entities |
|
|
152
|
-
| `find_counterparties(primary_key, line_id, from_col, to_col, pattern_id, top_n, use_edge_table=True)` | Discover counterparty entities from event data. When pattern_id is given and edge table exists, uses BTREE fast path with amount_sum/amount_max per counterparty |
|
|
152
|
+
| `find_counterparties(primary_key, line_id, from_col, to_col, pattern_id, top_n, use_edge_table=True, *, timestamp_cutoff=None)` | Discover counterparty entities from event data. When pattern_id is given and edge table exists, uses BTREE fast path with amount_sum/amount_max per counterparty. `timestamp_cutoff` applies to the edge-table fast path only; raises `GDSNavigationError` when supplied without an edge-table-eligible configuration |
|
|
153
153
|
| `assess_false_positive(primary_key, pattern_id)` | Evaluate likelihood of false positive anomaly classification |
|
|
154
154
|
|
|
155
155
|
**Detection recipes (population-level patterns):**
|
|
@@ -422,7 +422,7 @@ Or use the navigator shortcut: `nav.passive_scan(home_line_id)`.
|
|
|
422
422
|
| `add_points_source(name, line_id, rules, combine)` | Register a points-rule source filtering by column thresholds |
|
|
423
423
|
| `add_compound_source(name, geometry_pattern_id, line_id, rules)` | Geometry expansion intersected with points rules |
|
|
424
424
|
| `add_graph_source(name, pattern_id, contagion_threshold=0.3, weight)` | Register a graph contagion source — flags entities whose anomalous counterparty ratio exceeds threshold. Requires event pattern with edge table |
|
|
425
|
-
| `auto_discover(home_line_id, include_borderline)` | Auto-register all patterns related to a line. Also auto-detects graph sources for event patterns with edge tables |
|
|
425
|
+
| `auto_discover(home_line_id, include_borderline, *, include_graph=True)` | Auto-register all patterns related to a line. Also auto-detects graph sources for event patterns with edge tables. Pass `include_graph=False` to skip graph source registration when the downstream scan does not need contagion signal — `detect_cross_pattern_discrepancy` uses this to avoid ~37s-per-event-pattern edge-table reads |
|
|
426
426
|
| `scan(home_line_id, scoring, threshold, top_n)` | Execute batch scan across all registered sources. Returns `ScanResult` |
|
|
427
427
|
|
|
428
428
|
### ScanResult
|
|
@@ -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
|
|
@@ -224,6 +224,10 @@ The scanner collects, deduplicates, and ranks results into a unified candidate l
|
|
|
224
224
|
is the primary entry point for broad screening workflows where the agent does not yet know
|
|
225
225
|
which specific primitive to focus on. `auto_discover()` registers the appropriate sources
|
|
226
226
|
automatically — graph sources are added for every event pattern that has an edge table.
|
|
227
|
+
Callers that do not need graph contagion signal (e.g. `detect_cross_pattern_discrepancy`,
|
|
228
|
+
which measures geometry disagreement between patterns, not neighbor contamination) pass
|
|
229
|
+
`include_graph=False` to skip graph source registration and avoid the per-pattern edge
|
|
230
|
+
table read that would otherwise dominate scan latency on large spheres.
|
|
227
231
|
|
|
228
232
|
## Build Flow
|
|
229
233
|
|
|
@@ -278,7 +282,7 @@ The MCP package is a transport and tool layer.
|
|
|
278
282
|
It does not define the core GDS model. Instead, it:
|
|
279
283
|
|
|
280
284
|
- opens and closes spheres
|
|
281
|
-
- exposes
|
|
285
|
+
- exposes 67 navigation and analysis tools
|
|
282
286
|
- formats results as JSON
|
|
283
287
|
- manages session state for an agentic client
|
|
284
288
|
|
|
@@ -132,7 +132,7 @@ An edge table is a flat Lance dataset that links anchor entities through an even
|
|
|
132
132
|
- **Runtime graph traversal** -- `find_geometric_path` uses beam search over the edge table, scoring paths by geometric coherence of intermediate entities
|
|
133
133
|
- **Lazy chain discovery** -- `discover_chains` performs temporal BFS on edges without requiring build-time chain extraction
|
|
134
134
|
- **Edge statistics** -- row counts, unique entity counts, timestamp and amount ranges
|
|
135
|
-
- **Witness cohort discovery** -- `find_witness_cohort` ranks entities that share the target's witness signature by combining delta similarity, witness overlap, trajectory alignment, and a graded anomaly bonus, while excluding entities already connected via the edge table. **Investigative peer ranking, not edge forecasting
|
|
135
|
+
- **Witness cohort discovery** -- `find_witness_cohort` ranks entities that share the target's witness signature by combining delta similarity, witness overlap, trajectory alignment, and a graded anomaly bonus, while excluding entities already connected via the edge table. **Investigative peer ranking, not edge forecasting** — surfaces existing peers rather than predicting future edges. Uniquely possible because population-relative geometry, witness sets, temporal solids, and the edge table all live in one storage layer
|
|
136
136
|
|
|
137
137
|
**Edge table fields:** `from_key` and `to_key` are anchor entity keys derived from event pattern FK columns. `event_key` links back to the event line for traceability. `timestamp` is epoch seconds from the column specified by `edge_table.timestamp_col` (or auto-detected). `amount` is the numeric value from `edge_table.amount_col` (or auto-detected from columns named `amount`, `value`, `total`, `amt`). The semantic meaning of `amount` depends on the domain (e.g. payment value, fare, order total, shipment weight). When no amount column is found, defaults to 0.0.
|
|
138
138
|
|
|
@@ -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)
|