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.
Files changed (202) hide show
  1. {hypertopos-0.2.1 → hypertopos-0.3.0}/CHANGELOG.md +56 -7
  2. {hypertopos-0.2.1 → hypertopos-0.3.0}/LICENSE.md +1 -1
  3. {hypertopos-0.2.1 → hypertopos-0.3.0}/PKG-INFO +3 -5
  4. {hypertopos-0.2.1 → hypertopos-0.3.0}/README.md +4 -4
  5. hypertopos-0.3.0/ROADMAP.md +88 -0
  6. {hypertopos-0.2.1 → hypertopos-0.3.0}/SECURITY.md +1 -1
  7. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/api-reference.md +8 -8
  8. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/architecture.md +6 -2
  9. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/concepts.md +1 -1
  10. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/data-format.md +2 -1
  11. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/__init__.py +1 -1
  12. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/builder/builder.py +153 -10
  13. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/cli/build.py +9 -1
  14. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/engine/aggregation.py +282 -523
  15. hypertopos-0.3.0/hypertopos/engine/lance_sql_agg.py +798 -0
  16. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/navigation/navigator.py +168 -85
  17. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/navigation/scanner.py +55 -72
  18. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/storage/reader.py +34 -0
  19. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/storage/writer.py +35 -3
  20. {hypertopos-0.2.1 → hypertopos-0.3.0}/pyproject.toml +3 -6
  21. hypertopos-0.3.0/tests/conftest.py +89 -0
  22. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_aggregation_engine.py +0 -66
  23. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_builder.py +28 -6
  24. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_builder_derived.py +18 -3
  25. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_calibration.py +10 -8
  26. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_detection.py +25 -0
  27. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_fts.py +15 -5
  28. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_geometric_path.py +107 -8
  29. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_incremental.py +4 -3
  30. hypertopos-0.3.0/tests/test_lance_sql_agg.py +548 -0
  31. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_navigation.py +34 -21
  32. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_scanner.py +57 -0
  33. hypertopos-0.2.1/ROADMAP.md +0 -46
  34. hypertopos-0.2.1/hypertopos/engine/datafusion_agg.py +0 -602
  35. hypertopos-0.2.1/hypertopos/engine/subprocess_agg.py +0 -742
  36. hypertopos-0.2.1/tests/conftest.py +0 -83
  37. hypertopos-0.2.1/tests/test_datafusion_agg.py +0 -788
  38. hypertopos-0.2.1/tests/test_subprocess_agg.py +0 -961
  39. {hypertopos-0.2.1 → hypertopos-0.3.0}/.github/workflows/publish-pypi.yml +0 -0
  40. {hypertopos-0.2.1 → hypertopos-0.3.0}/.gitignore +0 -0
  41. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/README.md +0 -0
  42. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/LICENSE +0 -0
  43. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/README.md +0 -0
  44. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/README.md +0 -0
  45. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/SUMMARY.md +0 -0
  46. {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
  47. {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
  48. {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
  49. {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
  50. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-settings.md +0 -0
  51. {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
  52. {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
  53. {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
  54. {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
  55. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-settings.md +0 -0
  56. {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
  57. {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
  58. {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
  59. {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
  60. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-settings.md +0 -0
  61. {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
  62. {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
  63. {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
  64. {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
  65. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-settings.md +0 -0
  66. {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
  67. {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
  68. {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
  69. {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
  70. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-settings.md +0 -0
  71. {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
  72. {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
  73. {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
  74. {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
  75. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-settings.md +0 -0
  76. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/prepare_accounts.py +0 -0
  77. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/prepare_cpty_banks.py +0 -0
  78. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/prepare_districts.py +0 -0
  79. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/prepare_loan_accounts.py +0 -0
  80. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/prepare_operations.py +0 -0
  81. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/prepare_orders.py +0 -0
  82. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/prepare_transactions.py +0 -0
  83. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/prepare_tx_types.py +0 -0
  84. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/berka/sphere/sphere.yaml +0 -0
  85. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/README.md +0 -0
  86. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/_layer2_cross_eval.py +0 -0
  87. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/_layer3_cross_eval.py +0 -0
  88. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_accounts.py +0 -0
  89. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_currencies.py +0 -0
  90. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_payment_formats.py +0 -0
  91. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_transactions.py +0 -0
  92. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/hi_small_sphere/sphere.yaml +0 -0
  93. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/layer1_passive_scan.py +0 -0
  94. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/layer2_xgboost_sweep.py +0 -0
  95. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/layer3_typology_filter.py +0 -0
  96. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/li_small_sphere/prepare_accounts.py +0 -0
  97. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/li_small_sphere/prepare_currencies.py +0 -0
  98. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/li_small_sphere/prepare_payment_formats.py +0 -0
  99. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/li_small_sphere/prepare_transactions.py +0 -0
  100. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/li_small_sphere/sphere.yaml +0 -0
  101. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/ibm-aml/passive-scan-calibration.md +0 -0
  102. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/README.md +0 -0
  103. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/SUMMARY.md +0 -0
  104. {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
  105. {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
  106. {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
  107. {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
  108. {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
  109. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-settings.md +0 -0
  110. {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
  111. {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
  112. {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
  113. {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
  114. {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
  115. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-settings.md +0 -0
  116. {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
  117. {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
  118. {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
  119. {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
  120. {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
  121. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-settings.md +0 -0
  122. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/sphere/prepare_trips.py +0 -0
  123. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmark/nyc-taxi/sphere/sphere.yaml +0 -0
  124. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/benchmarks.md +0 -0
  125. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/configuration.md +0 -0
  126. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/images/build-pipeline.svg +0 -0
  127. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/images/core-objects.svg +0 -0
  128. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/images/cutting-plane.svg +0 -0
  129. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/images/directory-layout.svg +0 -0
  130. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/images/hypertopos-overview.svg +0 -0
  131. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/images/model-lifecycle.svg +0 -0
  132. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/images/navigation-primitives.svg +0 -0
  133. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/images/three-scales.svg +0 -0
  134. {hypertopos-0.2.1 → hypertopos-0.3.0}/docs/quickstart.md +0 -0
  135. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/builder/__init__.py +0 -0
  136. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/builder/_stats.py +0 -0
  137. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/builder/_writer.py +0 -0
  138. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/builder/derived.py +0 -0
  139. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/builder/mapping.py +0 -0
  140. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/cli/__init__.py +0 -0
  141. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/cli/__main__.py +0 -0
  142. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/cli/info.py +0 -0
  143. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/cli/main.py +0 -0
  144. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/cli/schema.py +0 -0
  145. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/cli/sources.py +0 -0
  146. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/engine/__init__.py +0 -0
  147. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/engine/calibration.py +0 -0
  148. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/engine/chains.py +0 -0
  149. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/engine/composition.py +0 -0
  150. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/engine/forecast.py +0 -0
  151. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/engine/geometry.py +0 -0
  152. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/engine/invalidation.py +0 -0
  153. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/engine/investigation.py +0 -0
  154. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/model/__init__.py +0 -0
  155. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/model/manifest.py +0 -0
  156. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/model/objects.py +0 -0
  157. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/model/sphere.py +0 -0
  158. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/navigation/__init__.py +0 -0
  159. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/sphere.py +0 -0
  160. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/storage/__init__.py +0 -0
  161. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/storage/_schemas.py +0 -0
  162. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/storage/cache.py +0 -0
  163. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/utils/__init__.py +0 -0
  164. {hypertopos-0.2.1 → hypertopos-0.3.0}/hypertopos/utils/arrow.py +0 -0
  165. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/.gitkeep +0 -0
  166. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/fixtures/generate_fixtures.py +0 -0
  167. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_arrow_utils.py +0 -0
  168. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_builder_alias.py +0 -0
  169. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_builder_edges.py +0 -0
  170. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_cache.py +0 -0
  171. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_chains.py +0 -0
  172. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_cli.py +0 -0
  173. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_cli_alias.py +0 -0
  174. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_cli_sources.py +0 -0
  175. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_compaction.py +0 -0
  176. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_composition.py +0 -0
  177. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_continuous_event_dims.py +0 -0
  178. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_data_quality_prop.py +0 -0
  179. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_edge_table.py +0 -0
  180. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_engine.py +0 -0
  181. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_forecast.py +0 -0
  182. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_geometry_stats.py +0 -0
  183. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_hybrid_search.py +0 -0
  184. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_invalidation.py +0 -0
  185. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_investigation.py +0 -0
  186. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_mapping.py +0 -0
  187. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_model.py +0 -0
  188. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_mvcc_session.py +0 -0
  189. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_navigator_alerts.py +0 -0
  190. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_observability.py +0 -0
  191. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_precision_stack.py +0 -0
  192. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_reputation.py +0 -0
  193. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_row_id_cache.py +0 -0
  194. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_sphere.py +0 -0
  195. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_sphere_overview_perf.py +0 -0
  196. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_stats_guard.py +0 -0
  197. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_storage.py +0 -0
  198. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_streaming_reads.py +0 -0
  199. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_temporal_centroids.py +0 -0
  200. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_trajectory_ann.py +0 -0
  201. {hypertopos-0.2.1 → hypertopos-0.3.0}/tests/test_witness_cohort.py +0 -0
  202. {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: 3.7s → ~330ms on AML HI-small)
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-11
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.2.1
4
- Summary: Navigate your data like space.
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>=0.20
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: BSL 1.1](https://img.shields.io/badge/license-BSL%201.1-orange.svg)](LICENSE.md)
7
7
  [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.19482069.svg)](https://doi.org/10.5281/zenodo.19482069)
8
8
  [![PyArrow](https://img.shields.io/badge/format-PyArrow-red.svg)](https://arrow.apache.org/docs/python/)
9
- [![Lance](https://img.shields.io/badge/storage-Lance-blueviolet.svg)](https://github.com/lancedb/lancedb)
9
+ [![Lance](https://img.shields.io/badge/storage-Lance-blueviolet.svg)](https://github.com/lance-format/lance)
10
10
  [![MCP](https://img.shields.io/badge/MCP-compatible-green.svg)](https://modelcontextprotocol.io)
11
- [![Version](https://img.shields.io/badge/version-0.2.1-blue.svg)](pyproject.toml)
11
+ [![Version](https://img.shields.io/badge/version-0.3.0-blue.svg)](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 validated on AML HI-small at **20.5× lift** over random base rate for co-laundering precision@10.
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 (validated 20.5× lift on AML) | Topological only (Adamic-Adar, common neighbors) | KNN, no graph awareness | Learned embeddings (training required) |
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
@@ -1,6 +1,6 @@
1
1
  # Security
2
2
 
3
- ## Current version: 0.2.x
3
+ ## Current version: 0.3.0
4
4
 
5
5
  hypertopos runs locally. No network services, no auth layer, no multi-tenancy.
6
6
 
@@ -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. Validated on AML HI-small: 20.5× lift over random for co-laundering precision@10 (25.3% vs 1.2%), 2.6× over `find_similar_entities + is_anomaly` baseline (6.5%), 15.5% top-10 overlap with that baseline |
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 66 navigation and analysis tools
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.** Validated on AML HI-small: 25.3% co-laundering precision@10 vs 1.2% random base rate (20.5× lift), 2.6× improvement over `find_similar_entities + is_anomaly` baseline (6.5%), 15.5% top-10 overlap with that baseline (substantively different results, not ANN with extra steps); temporal hold-out recall@10 is 0%, confirming the function 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
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
- │ └── edge_stats/ # per-event-pattern edge table summary cache (row count, unique from/to, ts/amount range)
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
@@ -16,4 +16,4 @@ __all__ = [
16
16
  "GDSBuilder",
17
17
  "RelationSpec",
18
18
  ]
19
- __version__ = "0.1.0"
19
+ __version__ = "0.3.0"
@@ -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", "created_at", "tx_date", "date",
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
- ts_arr = pa.array(
1433
- [0.0] * len(event_table), type=pa.float64(),
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
- # Try common column names
1445
- for name in ("amount", "value", "total", "amt"):
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)