hypertopos 0.5.2__tar.gz → 0.6.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 (249) hide show
  1. {hypertopos-0.5.2 → hypertopos-0.6.0}/CHANGELOG.md +228 -0
  2. {hypertopos-0.5.2 → hypertopos-0.6.0}/LICENSE.md +1 -1
  3. {hypertopos-0.5.2 → hypertopos-0.6.0}/PKG-INFO +1 -1
  4. {hypertopos-0.5.2 → hypertopos-0.6.0}/README.md +1 -1
  5. {hypertopos-0.5.2 → hypertopos-0.6.0}/ROADMAP.md +16 -9
  6. {hypertopos-0.5.2 → hypertopos-0.6.0}/SECURITY.md +1 -1
  7. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/api-reference.md +294 -0
  8. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/architecture.md +2 -2
  9. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/concepts.md +122 -0
  10. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/configuration.md +1078 -1066
  11. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/data-format.md +55 -0
  12. hypertopos-0.6.0/hypertopos/__init__.py +47 -0
  13. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/builder/builder.py +469 -10
  14. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/builder/mapping.py +355 -243
  15. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/cli/build.py +1001 -992
  16. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/cli/schema.py +898 -895
  17. hypertopos-0.6.0/hypertopos/engine/density_gaps.py +150 -0
  18. hypertopos-0.6.0/hypertopos/engine/edge_features.py +189 -0
  19. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/engine/geometry.py +604 -7
  20. hypertopos-0.6.0/hypertopos/engine/hop_predicate.py +285 -0
  21. hypertopos-0.6.0/hypertopos/engine/structuring.py +127 -0
  22. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/model/sphere.py +283 -0
  23. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/navigation/navigator.py +1722 -110
  24. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/storage/_schemas.py +65 -52
  25. hypertopos-0.6.0/hypertopos/storage/calibration_history.py +206 -0
  26. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/storage/reader.py +143 -4
  27. {hypertopos-0.5.2 → hypertopos-0.6.0}/pyproject.toml +1 -1
  28. hypertopos-0.6.0/tests/test_calibration_drift.py +398 -0
  29. hypertopos-0.6.0/tests/test_calibration_history.py +345 -0
  30. hypertopos-0.6.0/tests/test_calibration_history_builder.py +841 -0
  31. hypertopos-0.6.0/tests/test_calibration_history_reader.py +271 -0
  32. hypertopos-0.6.0/tests/test_decompose_drift.py +564 -0
  33. hypertopos-0.6.0/tests/test_density_gaps_engine.py +111 -0
  34. hypertopos-0.6.0/tests/test_edge_dimensions_builder.py +145 -0
  35. hypertopos-0.6.0/tests/test_edge_dimensions_yaml.py +163 -0
  36. hypertopos-0.6.0/tests/test_edge_features.py +229 -0
  37. hypertopos-0.6.0/tests/test_edge_features_reader.py +55 -0
  38. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_edge_potential.py +7 -0
  39. hypertopos-0.6.0/tests/test_find_calibration_influencers.py +806 -0
  40. hypertopos-0.6.0/tests/test_find_density_gaps.py +105 -0
  41. hypertopos-0.6.0/tests/test_find_lead_lag.py +459 -0
  42. hypertopos-0.6.0/tests/test_find_lead_lag_significance.py +111 -0
  43. hypertopos-0.6.0/tests/test_find_motif_by_hops.py +130 -0
  44. hypertopos-0.6.0/tests/test_hop_predicate_engine.py +369 -0
  45. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_navigation.py +50 -0
  46. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_root_cause_trace.py +14 -0
  47. hypertopos-0.6.0/tests/test_structuring_engine.py +111 -0
  48. hypertopos-0.5.2/hypertopos/__init__.py +0 -19
  49. {hypertopos-0.5.2 → hypertopos-0.6.0}/.github/workflows/publish-pypi.yml +0 -0
  50. {hypertopos-0.5.2 → hypertopos-0.6.0}/.gitignore +0 -0
  51. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/README.md +0 -0
  52. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/LICENSE +0 -0
  53. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/README.md +0 -0
  54. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/README.md +0 -0
  55. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/SUMMARY.md +0 -0
  56. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-a-report.md +0 -0
  57. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-a-scoring.md +0 -0
  58. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-b-report.md +0 -0
  59. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-b-scoring.md +0 -0
  60. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-settings.md +0 -0
  61. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-a-report.md +0 -0
  62. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-a-scoring.md +0 -0
  63. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-b-report.md +0 -0
  64. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-b-scoring.md +0 -0
  65. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-settings.md +0 -0
  66. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-a-report.md +0 -0
  67. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-a-scoring.md +0 -0
  68. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-b-report.md +0 -0
  69. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-b-scoring.md +0 -0
  70. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-settings.md +0 -0
  71. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-a-report.md +0 -0
  72. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-a-scoring.md +0 -0
  73. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-b-report.md +0 -0
  74. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-b-scoring.md +0 -0
  75. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-settings.md +0 -0
  76. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-a-report.md +0 -0
  77. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-a-scoring.md +0 -0
  78. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-b-report.md +0 -0
  79. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-b-scoring.md +0 -0
  80. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-settings.md +0 -0
  81. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-a-report.md +0 -0
  82. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-a-scoring.md +0 -0
  83. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-b-report.md +0 -0
  84. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-b-scoring.md +0 -0
  85. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-settings.md +0 -0
  86. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/sphere/prepare_accounts.py +0 -0
  87. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/sphere/prepare_cpty_banks.py +0 -0
  88. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/sphere/prepare_districts.py +0 -0
  89. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/sphere/prepare_loan_accounts.py +0 -0
  90. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/sphere/prepare_operations.py +0 -0
  91. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/sphere/prepare_orders.py +0 -0
  92. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/sphere/prepare_transactions.py +0 -0
  93. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/sphere/prepare_tx_types.py +0 -0
  94. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/berka/sphere/sphere.yaml +0 -0
  95. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/ibm-aml/README.md +0 -0
  96. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/ibm-aml/_layer2_cross_eval.py +0 -0
  97. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/ibm-aml/_layer3_cross_eval.py +0 -0
  98. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_accounts.py +0 -0
  99. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_currencies.py +0 -0
  100. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_payment_formats.py +0 -0
  101. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_transactions.py +0 -0
  102. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/ibm-aml/hi_small_sphere/sphere.yaml +0 -0
  103. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/ibm-aml/layer1_passive_scan.py +0 -0
  104. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/ibm-aml/layer2_xgboost_sweep.py +0 -0
  105. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/ibm-aml/layer3_typology_filter.py +0 -0
  106. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/ibm-aml/li_small_sphere/prepare_accounts.py +0 -0
  107. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/ibm-aml/li_small_sphere/prepare_currencies.py +0 -0
  108. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/ibm-aml/li_small_sphere/prepare_payment_formats.py +0 -0
  109. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/ibm-aml/li_small_sphere/prepare_transactions.py +0 -0
  110. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/ibm-aml/li_small_sphere/sphere.yaml +0 -0
  111. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/ibm-aml/passive-scan-calibration.md +0 -0
  112. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/nyc-taxi/README.md +0 -0
  113. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/nyc-taxi/results/0.1.0/SUMMARY.md +0 -0
  114. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-a-report.md +0 -0
  115. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-a-scoring.md +0 -0
  116. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-b-report.md +0 -0
  117. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-b-scoring.md +0 -0
  118. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-c-report.md +0 -0
  119. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-settings.md +0 -0
  120. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-a-report.md +0 -0
  121. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-a-scoring.md +0 -0
  122. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-b-report.md +0 -0
  123. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-b-scoring.md +0 -0
  124. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-c-report.md +0 -0
  125. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-settings.md +0 -0
  126. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-a-report.md +0 -0
  127. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-a-scoring.md +0 -0
  128. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-b-report.md +0 -0
  129. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-b-scoring.md +0 -0
  130. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-c-report.md +0 -0
  131. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-settings.md +0 -0
  132. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/nyc-taxi/sphere/prepare_trips.py +0 -0
  133. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmark/nyc-taxi/sphere/sphere.yaml +0 -0
  134. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/benchmarks.md +0 -0
  135. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/images/build-pipeline.svg +0 -0
  136. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/images/core-objects.svg +0 -0
  137. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/images/cutting-plane.svg +0 -0
  138. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/images/directory-layout.svg +0 -0
  139. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/images/hypertopos-overview.svg +0 -0
  140. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/images/model-lifecycle.svg +0 -0
  141. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/images/navigation-primitives.svg +0 -0
  142. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/images/three-scales.svg +0 -0
  143. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/introduction.md +0 -0
  144. {hypertopos-0.5.2 → hypertopos-0.6.0}/docs/quickstart.md +0 -0
  145. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/builder/__init__.py +0 -0
  146. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/builder/_bootstrap.py +0 -0
  147. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/builder/_bregman.py +0 -0
  148. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/builder/_scatter.py +0 -0
  149. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/builder/_stats.py +0 -0
  150. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/builder/_writer.py +0 -0
  151. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/builder/derived.py +0 -0
  152. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/builder/dim_blocks.py +0 -0
  153. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/cli/__init__.py +0 -0
  154. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/cli/__main__.py +0 -0
  155. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/cli/info.py +0 -0
  156. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/cli/main.py +0 -0
  157. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/cli/sources.py +0 -0
  158. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/engine/__init__.py +0 -0
  159. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/engine/adjacency.py +0 -0
  160. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/engine/aggregation.py +0 -0
  161. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/engine/calibration.py +0 -0
  162. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/engine/chains.py +0 -0
  163. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/engine/composition.py +0 -0
  164. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/engine/fdr.py +0 -0
  165. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/engine/forecast.py +0 -0
  166. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/engine/graph_algorithms.py +0 -0
  167. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/engine/heredity.py +0 -0
  168. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/engine/invalidation.py +0 -0
  169. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/engine/investigation.py +0 -0
  170. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/engine/lance_sql_agg.py +0 -0
  171. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/engine/selection.py +0 -0
  172. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/model/__init__.py +0 -0
  173. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/model/manifest.py +0 -0
  174. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/model/objects.py +0 -0
  175. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/navigation/__init__.py +0 -0
  176. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/navigation/scanner.py +0 -0
  177. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/sphere.py +0 -0
  178. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/storage/__init__.py +0 -0
  179. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/storage/cache.py +0 -0
  180. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/storage/writer.py +0 -0
  181. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/utils/__init__.py +0 -0
  182. {hypertopos-0.5.2 → hypertopos-0.6.0}/hypertopos/utils/arrow.py +0 -0
  183. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/.gitkeep +0 -0
  184. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/conftest.py +0 -0
  185. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/fixtures/generate_fixtures.py +0 -0
  186. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_adjacency.py +0 -0
  187. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_aggregation_engine.py +0 -0
  188. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_arrow_utils.py +0 -0
  189. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_bootstrap.py +0 -0
  190. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_bregman.py +0 -0
  191. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_builder.py +0 -0
  192. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_builder_alias.py +0 -0
  193. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_builder_chunking.py +0 -0
  194. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_builder_contagion.py +0 -0
  195. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_builder_derived.py +0 -0
  196. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_builder_edges.py +0 -0
  197. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_builder_rolling_z.py +0 -0
  198. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_builder_scatter.py +0 -0
  199. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_builder_temporal_perf.py +0 -0
  200. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_cache.py +0 -0
  201. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_calibration.py +0 -0
  202. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_chains.py +0 -0
  203. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_cli.py +0 -0
  204. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_cli_alias.py +0 -0
  205. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_cli_sources.py +0 -0
  206. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_compaction.py +0 -0
  207. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_composition.py +0 -0
  208. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_continuous_event_dims.py +0 -0
  209. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_data_quality_prop.py +0 -0
  210. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_detection.py +0 -0
  211. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_dim_blocks.py +0 -0
  212. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_edge_table.py +0 -0
  213. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_engine.py +0 -0
  214. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_fdr.py +0 -0
  215. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_forecast.py +0 -0
  216. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_fts.py +0 -0
  217. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_geometric_path.py +0 -0
  218. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_geometry_stats.py +0 -0
  219. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_gradient_alignment.py +0 -0
  220. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_graph_algorithms.py +0 -0
  221. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_heredity.py +0 -0
  222. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_hybrid_search.py +0 -0
  223. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_incremental.py +0 -0
  224. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_invalidation.py +0 -0
  225. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_investigation.py +0 -0
  226. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_kind_detect.py +0 -0
  227. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_lance_sql_agg.py +0 -0
  228. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_mapping.py +0 -0
  229. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_model.py +0 -0
  230. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_motif.py +0 -0
  231. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_mvcc_session.py +0 -0
  232. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_navigator_alerts.py +0 -0
  233. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_navigator_fdr_select.py +0 -0
  234. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_observability.py +0 -0
  235. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_precision_stack.py +0 -0
  236. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_profile_motif_perf_smoke.py +0 -0
  237. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_reputation.py +0 -0
  238. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_row_id_cache.py +0 -0
  239. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_scanner.py +0 -0
  240. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_selection.py +0 -0
  241. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_sphere.py +0 -0
  242. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_sphere_overview_perf.py +0 -0
  243. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_stats_guard.py +0 -0
  244. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_storage.py +0 -0
  245. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_streaming_reads.py +0 -0
  246. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_temporal_centroids.py +0 -0
  247. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_trajectory_ann.py +0 -0
  248. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_witness_cohort.py +0 -0
  249. {hypertopos-0.5.2 → hypertopos-0.6.0}/tests/test_witness_set.py +0 -0
@@ -7,6 +7,234 @@ Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.6.0] — 2026-04-30
11
+
12
+ ### Theme
13
+
14
+ **Architecture-emergent analytics, properly enabled.** Calibration is
15
+ now versioned (multi-epoch retention, sphere format 2.4) and four
16
+ patent-aligned analytics ride on that versioning:
17
+ `compare_calibrations`, intrinsic / extrinsic drift decomposition,
18
+ hidden-influencer matrix, cross-pattern temporal lead-lag. Companion
19
+ features round out the release: edge-derived build-time dimensions,
20
+ joint density gap detection, and a declarative `HopPredicate` motif
21
+ API as power-user escape hatch from the closed-vocab `find_motif`
22
+ registry.
23
+
24
+ ### Added
25
+ - `GDSNavigator.find_motif_by_hops(pattern_id, hops, *, seed_keys, max_results, score)`
26
+ — declarative motif API. Caller passes a list of `HopPredicate`s
27
+ describing per-hop constraints (`amount_min` / `amount_max` /
28
+ `time_delta_max_hours` / `direction` (`"forward"` / `"reverse"` /
29
+ `"any"`) / `edge_dim_predicates` referencing the per-edge sidecar from
30
+ the edge_dimensions YAML block) and the navigator walks the edge table
31
+ for matching chains of length 1..6. Power-user escape hatch from the
32
+ closed-vocab `find_motif` registry — composes cleanly with
33
+ `pair_edge_count`, `position_in_chain`, `time_since_pair_last_edge`,
34
+ `pair_amount_zscore`, `find_motif_structuring`. New `HopPredicate`
35
+ frozen dataclass in `hypertopos.model.sphere` (re-exported from the
36
+ package root). Seeded queries (`seed_keys` provided) build a scoped
37
+ `AdjacencyIndex` via Lance BTREE-pushdown reads expanding the BFS
38
+ frontier hop-by-hop — the visited subgraph alone is materialised, not
39
+ the full edge table. Unseeded full enumeration uses the cached global
40
+ `AdjacencyIndex` so repeat calls are O(1) per node lookup. Bounded
41
+ MVP — `amount_ratio_to_prev` and
42
+ `require_anomalous_entity` predicates plus k>6 land in a follow-up.
43
+ Scoring path uses the existing `_score_motif_from_edges` infrastructure
44
+ on anchor patterns; on event patterns the score is silently skipped
45
+ (anchor-companion scoring is the follow-up wiring); `score` defaults to
46
+ `False` since only event patterns are accepted today. Direction-aware
47
+ temporal monotonicity: `direction="forward"` enforces strict-increasing
48
+ timestamps, `direction="reverse"` enforces strict-decreasing
49
+ (causal-predecessor chain), `direction="any"` drops the monotonic
50
+ constraint and treats the time window as `|Δt|`. `hops[0].time_delta_max_hours`
51
+ must be `None` (rejected at validation — first hop has no previous
52
+ timestamp); `time_delta_max_hours` on subsequent hops must be positive.
53
+ Sidecar lookup is materialised lazily as `ek→idx` plus per-dim parallel
54
+ lists (rather than a 5M-row dict-of-dicts) — avoids the per-event
55
+ nested-dict allocation when `edge_dim_predicates` are used.
56
+ - `GDSNavigator.find_density_gaps(pattern_id, *, top_n, dim_pairs, bins, alpha, r_min, r_max, sample_size)`
57
+ — joint density gap detection via probability integral transform plus
58
+ independence null on dim pairs. For each pattern dim that survives the
59
+ usability check (≥30 finite values, σ > 0, more than 2 unique levels)
60
+ the empirical CDF transform produces a uniform marginal on `[0, 1]`.
61
+ Pairs whose Pearson `|r|` lands inside the configurable
62
+ `[r_min, r_max]` window become the test set. Per pair the `bins x bins`
63
+ joint histogram is compared against the uniform-independence
64
+ expectation `N / bins²` via per-cell chi² residual; only
65
+ under-populated cells are kept and Benjamini-Hochberg correction is
66
+ applied across all under-populated cells to control FDR. Each flagged
67
+ cell maps back to a named delta-space (z-score) range via
68
+ `ECDFEntry.inverse` — values live on geometry-delta units; raw
69
+ property unit mapping is deferred follow-up.
70
+ Returns a dict with `gaps` (sorted by `expected/observed` ratio
71
+ descending), `excluded_dims` reporting (with reason), and
72
+ `n_pairs_tested`. Anchor patterns only (event patterns are not the
73
+ primary use case); patterns with `< 100` entities raise. Geometry is
74
+ read with column projection narrowed to `["delta"]` and Lance-side
75
+ random sampling via `sample_size` (default `100,000`; pass `0` to
76
+ scan the full population). Delta matrix is built via
77
+ `combine_chunks().values.to_numpy()` reshape rather than
78
+ `to_pylist()` + `np.array`. New `engine.density_gaps` module exposes
79
+ `ECDFEntry`, `compute_density_gaps_for_pair`, `is_usable_for_gap`,
80
+ and `select_pairs_by_corr` as building blocks.
81
+ - Edge-derived dimensions on event patterns:
82
+ `engine.edge_features` exposes five build-time per-edge dim functions
83
+ (`pair_edge_count`, `position_in_chain` with default `min_position=5`
84
+ and parse-time reject below 3, `time_since_pair_last_edge`,
85
+ `pair_amount_zscore` LOW_VAR pairs only, `find_motif_structuring`)
86
+ plus an orchestrator `compute_all_edge_dims(edges, config)` that runs
87
+ every dim listed in a config dict and returns a single Arrow table
88
+ keyed by `event_key`. `EDGE_DIM_KINDS` table publishes the kind tag
89
+ (poisson / gaussian / bernoulli) for each dim so builder calibration
90
+ can pick the right normaliser. New `engine.structuring` module hosts
91
+ the structuring motif enumerator (single-seed +
92
+ all-seeds sweep returning the set of `event_key`s in any motif) for
93
+ the build-time `find_motif_structuring` dim; the navigator runtime
94
+ `_enumerate_structuring` path is unchanged. New
95
+ `EDGE_FEATURES_SCHEMA` constant in `storage._schemas` describes the
96
+ per-edge sidecar Lance dataset layout. New
97
+ `GDSReader.read_edge_features(pattern_id)` reads
98
+ `_gds_meta/edge_features/{pid}/data.lance` (empty table when sidecar
99
+ absent), forward-compatible with the
100
+ HopPredicate.edge_dim_predicates query API planned in a future
101
+ release. YAML surface for the new dims: `patterns.<pid>.edge_dimensions`
102
+ list of either bare dim names or single-key dicts with overrides;
103
+ parsed into `EdgeDimensionsConfig` and attached to `PatternMapping`.
104
+ Builder integration baked the per-event dim values into the event
105
+ polygon `shape_snapshot` (one extra dim per declared entry) and
106
+ extends `dimension_kinds` so downstream Bregman / theta-norm
107
+ calibration treats the new dims natively. Sidecar Lance dataset at
108
+ `_gds_meta/edge_features/{pid}/data.lance` is written alongside,
109
+ forward-compatible with the planned HopPredicate.edge_dim_predicates
110
+ query API. Anchor patterns and event patterns without edge_dimensions
111
+ are byte-identical to builds without the new YAML block.
112
+ - Multi-epoch calibration retention: builder writes per-pattern historical
113
+ fits to `_gds_meta/calibration_history/{pid}/v={N}.json` on every full
114
+ build; sphere format bumps to 2.4 with `pattern.calibration_epoch`,
115
+ `pattern.schema_hash`, and root `calibration_history_policy`. New reader
116
+ API: `read_calibration_fit`, `list_calibration_versions`,
117
+ `read_calibration_history_policy`. New types: `CalibrationFit`,
118
+ `CalibrationNotFoundError`. GC keeps the most-recent K epochs (default 5).
119
+ Schema drift wipes history; 2.3 spheres open read-only and migrate on
120
+ first 0.6.0 build.
121
+ - `GDSNavigator.compare_calibrations(pattern_id, v_from, v_to, top_n, verbose)`
122
+ — per-dimension μ/σ/θ drift between two calibration epochs, with auto-resolve
123
+ defaults and aggregate RMS scalar. New types: `CalibrationDriftReport`,
124
+ `DimensionDrift`. Diagnostic for inspecting calibration shifts after a
125
+ builder rebuild; reads via the multi-epoch retention API added earlier in
126
+ this release.
127
+ - `GDSNavigator.decompose_drift(entity_key, pattern_id, v_from, v_to, timestamp_from, timestamp_to, top_n, verbose)`
128
+ — per-entity intrinsic vs extrinsic decomposition of geometric drift between
129
+ two temporal slices viewed across two calibration epochs. Returns
130
+ `IntrinsicExtrinsicReport` with aggregate L2 displacements, sum-of-squares
131
+ `intrinsic_fraction` bounded `[0, 1]`, and ranked per-dimension breakdown.
132
+ New types: `IntrinsicExtrinsicReport`, `DimensionDecomposition`.
133
+ - `find_drifting_entities` per-entity dict gains 3 additive scalar fields:
134
+ `intrinsic_displacement`, `extrinsic_displacement`, `intrinsic_fraction`.
135
+ Auto-defaults to (oldest retained, current) calibration epochs;
136
+ resolves to `null` per-entity when decomposition isn't computable.
137
+ - `GDSNavigator.find_calibration_influencers(pattern_id, top_n, classify, high_threshold_pct, sample_size, verbose)`
138
+ — per-entity exact leave-one-out impact on coordinate system calibration with
139
+ 4-cell classification matrix (hidden / distorter / standard_anomaly / normal).
140
+ Math uses exact leave-one-out via rolling Σs/Σs² (NOT first-order μ-only
141
+ approximation — that approximation makes the "hidden influencer" cell empty
142
+ by construction; corrected during brainstorm). New types:
143
+ `DimensionContribution`, `InfluenceEntry`, `InfluenceReport`. `verbose=True`
144
+ attaches `cascading_flip_count` per entry — count of OTHER entities
145
+ flipping is_anomaly after this entity's removal.
146
+ - `GDSNavigator.find_group_influence(pattern_id, groups)` — caller-
147
+ supplied leave-set-out impact with `reinforcing_factor = total_impact_set
148
+ / Σ_individuals`. Reinforcing > 1 detects coordinated population-shift
149
+ attacks (collusion rings, duplicate-record contamination); < 1 detects
150
+ canceling sets. New type: `GroupInfluenceReport`.
151
+ - `find_anomalies` MCP per-entity dict gains 2 additive scalar fields:
152
+ `total_impact`, `classification`. Hooked at the MCP-layer polygon→dict
153
+ conversion via `_attach_influence_fields_to_anomaly_entries`; resolves to
154
+ `null` per-entity when pattern is event-type, N<2, or storage backend
155
+ lacks shape-reconstruction prerequisites.
156
+ - `GDSNavigator.find_lead_lag(pattern_a, pattern_b, *, timestamp_from, timestamp_to, cohort, min_epochs, max_lag, fdr_alpha, fdr_method, verbose, entity_key)`
157
+ — cross-pattern temporal lead-lag in population-relative coordinates.
158
+ Population-aggregated centroid drift cross-correlation (primary signal,
159
+ patent-line) plus mean step-magnitude volatility confirmation series with
160
+ `agreement` label. Per-dim D_A × D_B matrix with BH or Storey FDR over
161
+ Bonferroni-over-lag-adjusted p-values (`top_dim_pairs` ranked by ascending
162
+ q-value, full matrix in `per_dim_pairs` when `verbose=True`). Per-entity
163
+ drill-down via `entity_key` parameter. Time alignment by intersection of
164
+ pattern timestamp sets, hard floor `min_epochs=8`, default
165
+ `cohort="fixed"` (panel-clean centroid). Significance: peak Bonferroni
166
+ adjustment for the population peak (`max_corr_threshold` cut-off,
167
+ `bartlett_ci_95` reported alongside, `is_significant` boolean,
168
+ `degenerate_signal` flag set when either centroid drift series has zero
169
+ variance — agreement forced to `"divergent"`). Reliability label mirrors
170
+ `engine.forecast.reliability_label` convention (high N-1 ≥ 24, medium
171
+ ≥ 12, else low). New types: `LeadLagReport`, `DimPairLeadLag`. Two-phase
172
+ Lance scan: a column-projected meta read picks the cohort and
173
+ pre-validates a 1 GB tensor budget against `count_geometry_rows` upper
174
+ bounds, so cross-pattern queries over disjoint entity_lines (e.g. AML
175
+ accounts vs account_pairs) raise immediately with an actionable error
176
+ pointing the agent at `cohort='fixed'` or `entity_key=<id>` instead of
177
+ allocating gigabytes. `read_temporal_batched` gains optional `columns=`
178
+ projection.
179
+
180
+ ### Fixed
181
+ - `find_anomalies` polygon construction crashed with
182
+ `TypeError: float() argument must be a string or a real number, not 'NoneType'`
183
+ on rows whose `delta_rank_pct` / `bregman_divergence` / `anomaly_confidence`
184
+ column existed but stored a null Arrow value. The reconstruction code
185
+ used `float(row.get(field, 0.0))` guarded by `if field in row`, but
186
+ `row.get` returns `None` (not the default) for null cells, so the
187
+ default never fired. Replaced with explicit
188
+ `None if row.get(field) is None else float(row[field])` across both
189
+ `engine.geometry._reconstruct_polygons_from_geometry_table` and the
190
+ two find_anomalies branches in `navigation.navigator` (full-rank and
191
+ rank-by-property). No agent-visible API change.
192
+ - `aggregate_anomalies`: negative `ungrouped_anomalies` when
193
+ `read_points_batch` returns duplicate rows per entity across Lance
194
+ partitions. Added dedup via `seen_pks` set — each entity is now counted
195
+ in exactly one group regardless of partition layout. `ungrouped_anomalies`
196
+ is now guaranteed non-negative.
197
+ - `detect_neighbor_contamination`: replaced row-by-row `as_py()` loop
198
+ (O(N) PyArrow element access) with vectorised `to_pylist()` for the
199
+ initial geometry scan. Also vectorised the small batch-read in the
200
+ unknown-keys path.
201
+ - `detect_segment_shift`: removed internal `π12_attract_regime_change`
202
+ call used only to populate the non-essential `changepoint_date` output
203
+ field. Calling full changepoint detection (O(N×buckets)) for a single
204
+ optional output field dominated the tool's wall-clock time. Field
205
+ removed from output; callers needing changepoint context should call
206
+ `find_regime_changes` separately. Per-segment Python loop over the
207
+ points table also replaced with vectorised PyArrow ops — `pc.is_in`
208
+ for the anomalous-PK mask, `pc.count_distinct` for the high-
209
+ cardinality early exit, and
210
+ `group_by(...).aggregate([("_pk","count"),("is_anomaly","sum")])` for
211
+ the per-segment counts.
212
+ - `find_high_potential_edges` (`attract_edge_potential`) on event
213
+ patterns with large edge tables: replaced the
214
+ `AdjacencyIndex.from_lance()` rebuild — which read all five edge
215
+ columns and called `to_pylist()` over the full table — with a direct
216
+ two-column PyArrow `group_by(["from_key","to_key"])` aggregate.
217
+ Eliminates the per-call full-table materialisation that dominated
218
+ cost on multi-million-edge event patterns. Added an entity-type ratio
219
+ guard that fires BEFORE the groupby: read the cached
220
+ `_gds_meta/edge_stats/<pid>.json` (built at sphere-write time) for
221
+ `unique_from + unique_to` and divide by `pattern.population_size`
222
+ (already loaded from `sphere.json`). When the ratio is < 1 % the
223
+ edge endpoints belong to a different entity type than the host
224
+ geometry (e.g. zone IDs in a trip-edge pattern whose geometry holds
225
+ trips); the tool returns an empty ranking without opening either the
226
+ Lance edge table or the geometry dataset. New
227
+ `GDSReader.edge_stats_cached(pid)` helper that reads the build-time
228
+ JSON only — never falls back to the live full-table scan that
229
+ `edge_table_stats` performs as a recovery path — so primitives that
230
+ need a cheap guard cannot accidentally trigger a scan when the cache
231
+ is missing.
232
+ - `detect_trajectory_anomaly`: changed `sample_size` default from
233
+ `None` (full population scan) to `10,000`. Full-population temporal
234
+ streaming on large spheres caused multi-minute latency; the default
235
+ now caps at 10k entities. Pass `sample_size=0` to restore the full
236
+ scan.
237
+
10
238
  ## [0.5.2] — 2026-04-28
11
239
 
12
240
  ### 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-28
29
+ **Change Date:** 2029-04-30
30
30
 
31
31
  **Change License:** Apache License, Version 2.0
32
32
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hypertopos
3
- Version: 0.5.2
3
+ Version: 0.6.0
4
4
  Summary: Understand the structure of your data — without training machine learning models
5
5
  Project-URL: Homepage, https://github.com/hypertopos/hypertopos-py
6
6
  Project-URL: Documentation, https://github.com/hypertopos/hypertopos-py/tree/main/docs
@@ -8,7 +8,7 @@
8
8
  [![PyArrow](https://img.shields.io/badge/format-PyArrow-red.svg)](https://arrow.apache.org/docs/python/)
9
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.5.2-%235500FF.svg)](pyproject.toml)
11
+ [![Version](https://img.shields.io/badge/version-0.6.0-%235500FF.svg)](pyproject.toml)
12
12
 
13
13
  hypertopos is not a database, and not a machine learning model. It is a layer that turns relational data into a coordinate system where every entity gets a position derived from its relationships and the population around it.
14
14
 
@@ -2,18 +2,25 @@
2
2
 
3
3
  > Planned direction. Priorities may change based on feedback.
4
4
 
5
- ## Plan: 0.6.0
5
+ ## Plan: 0.6.1
6
6
 
7
- Theme: **architecture-emergent analytics, properly enabled.**
7
+ Theme stays **architecture-emergent analytics, properly enabled** — 0.6.1 is a fast-follow patch that finishes the items deferred from the 0.6.0 MVP and lands the natural extensions of S1 / X1 once the 0.6.0 stress test surfaces real workloads.
8
8
 
9
- - Multi-epoch calibration retention in the builder (versioned μ/σ/θ per pattern) — prerequisite infrastructure that gates the three patent-aligned analytics below.
10
- - Intrinsic/extrinsic decomposition — split an entity's temporal position change into entity-caused vs population-caused components.
11
- - Entity influence on coordinate system — leave-one-out influence score; hidden-influencer detection.
12
- - Cross-pattern temporal lead-lag — cross-correlation between per-entity displacement series across independently calibrated patterns.
13
- - Edge-derived dimensions on event patterns — five build-time edge dimensions empirically validated for AML recall.
14
- - Joint density gap detection — distribution-free "anomaly by absence" via probability integral transform + independence null.
9
+ - **Anchor-pattern aggregation of edge-derived dimensions** — extension of S1: aggregate the per-edge sidecar (`pair_edge_count`, `position_in_chain`, `time_since_pair_last_edge`, `pair_amount_zscore`, `find_motif_structuring`) up to the entity / pair / chain anchor patterns so account-level recall benefits from the same structural signals that lifted transaction-level recall in 0.6.0.
10
+ - **HopPredicate full PBL vectorisation + k>6** — replace the depth-first Python walk with the full Paranjape-Benson-Leskovec δ-temporal-motif algorithm; bumps the bound on hop count and unlocks the larger temporal windows where structuring chains nest inside each other.
11
+ - **`HopPredicate.amount_ratio_to_prev` and `require_anomalous_entity`** — the two predicate vocab items deferred from the 0.6.0 X1 MVP. `amount_ratio_to_prev` lets a hop assert "≤ 60% of the previous hop's amount" without baking absolute thresholds; `require_anomalous_entity` filters intermediates by their anchor-pattern anomaly score.
12
+ - **`find_motif_by_hops` anchor-companion scoring** — wire the existing `_score_motif_from_edges` infrastructure to event-pattern motif results via the anchor-companion lookup, so callers get a `score` field instead of the silent 0.6.0 skip.
13
+ - **Stress-test-driven fixes** — anything the 0.6.0 multi-sphere stress test surfaces (perf regressions, edge-case crashes, MCP serialisation gaps) lands here.
15
14
 
16
- Any item may slip to 0.7.0 if scope pressure requires. The multi-epoch retention infrastructure is non-negotiable for the theme.
15
+ The 0.6.1 surface stays additive — no sphere format bump, no new tools beyond the deferred predicate fields. If any item turns out to need a format change, it slips to 0.7.0.
16
+
17
+ ---
18
+
19
+ ## 0.6.0
20
+
21
+ Architecture-emergent analytics, properly enabled — calibration is now **versioned**, and the patent-aligned analytics ride on that versioning.
22
+
23
+ - **0.6.0** — Multi-epoch calibration retention in the builder (sphere format 2.4, back-compat for 2.3) makes calibration `(pattern_id, version)`-keyed; the previous epoch is retained alongside the active one so all subsequent analytics can compare epochs without rebuilding the sphere. `compare_calibrations(pattern_id, v1, v2)` reports per-dim μ/σ drift between any two retained epochs. Intrinsic / extrinsic drift decomposition splits an entity's temporal position change into entity-caused vs population-caused components and ships through `decompose_drift` plus an inline path on `find_drifting_entities`. Hidden-influencer matrix (`find_group_influence`) computes leave-one-out influence on the coordinate system to surface entities whose presence reshapes the population geometry without themselves being anomalous. Cross-pattern temporal lead-lag (`find_lead_lag`) cross-correlates per-entity displacement series across independently calibrated patterns and reports significant leaders / laggards with FDR control. Edge-derived dimensions on event patterns ship five build-time per-edge dim functions (`pair_edge_count`, `position_in_chain`, `time_since_pair_last_edge`, `pair_amount_zscore` LOW_VAR pairs only, `find_motif_structuring`) baked into the polygon shape via the `edge_dimensions:` YAML block; sidecar Lance dataset at `_gds_meta/edge_features/{pid}/data.lance` enables runtime per-edge predicate filtering. Joint density gap detection (`find_density_gaps`) produces distribution-free "anomaly by absence" via probability integral transform plus independence null on dim pairs, with BH-corrected χ² residuals on a 2-D histogram. Declarative motif API (`find_motif_by_hops`) ships as a power-user escape hatch from the closed-vocab `find_motif` registry — `HopPredicate` per-hop predicates (`amount_min` / `amount_max` / `time_delta_max_hours` / `direction` ∈ {forward, reverse, any} / `edge_dim_predicates`) walk chains of length 1..6 over the cached `AdjacencyIndex`, with direction-aware temporal monotonicity (forward = strict-↑, reverse = strict-↓ causal-predecessor, any = `|Δt|` window) and a lazy ek→idx + per-dim sidecar lookup that avoids the per-event nested-dict allocation.
17
24
 
18
25
  ---
19
26
 
@@ -1,6 +1,6 @@
1
1
  # Security
2
2
 
3
- ## Current version: 0.5.2
3
+ ## Current version: 0.6.0
4
4
 
5
5
  hypertopos runs locally. No network services, no auth layer, no multi-tenancy.
6
6
 
@@ -245,6 +245,7 @@ class RelationSpec:
245
245
  | `read_edges(pattern_id, from_keys=None, to_keys=None, timestamp_from=None, timestamp_to=None, columns=None)` | Read edge table with Lance BTREE-indexed push-down filters. Returns `pa.Table` |
246
246
  | `has_edge_table(pattern_id)` | Check if an edge table exists for a pattern. Returns `bool` |
247
247
  | `edge_table_stats(pattern_id)` | Quick statistics (row count, unique entities, timestamp/amount ranges). Returns `dict` or `None` |
248
+ | `edge_stats_cached(pattern_id)` | Read precomputed edge_stats JSON only — never falls back to a live scan. Returns `dict` or `None` if cache missing |
248
249
 
249
250
  ### Edge Table (GDSWriter)
250
251
 
@@ -254,6 +255,299 @@ class RelationSpec:
254
255
  | `append_edges(pattern_id, new_edges)` | Append new edges to an existing Lance dataset (streaming build) |
255
256
  | `create_edge_indexes(pattern_id)` | Build BTREE indexes on `from_key` and `to_key` after streaming writes |
256
257
 
258
+ ### Edge Features Sidecar (GDSReader)
259
+
260
+ | Method | Description |
261
+ |--------|-------------|
262
+ | `read_edge_features(pattern_id)` | Read the per-edge derived dimension sidecar at `_gds_meta/edge_features/{pattern_id}/data.lance`. Returns an empty `pa.Table` conforming to `EDGE_FEATURES_SCHEMA` when no sidecar exists (the pattern did not declare an `edge_dimensions:` block). Forward-compatibility entry for a future HopPredicate query API; current navigator primitives read the dim values from the polygon `shape_snapshot` directly. |
263
+
264
+ ### `GDSNavigator.find_motif_by_hops`
265
+
266
+ ```python
267
+ nav.find_motif_by_hops(
268
+ pattern_id: str,
269
+ hops: list[HopPredicate],
270
+ *,
271
+ seed_keys: list[str] | None = None,
272
+ max_results: int = 100,
273
+ score: bool = True,
274
+ ) -> dict
275
+ ```
276
+
277
+ Declarative motif API — power-user escape hatch from the closed-vocab `find_motif` registry. Caller passes a list of `HopPredicate`s describing per-hop constraints (`amount_min`, `amount_max`, `time_delta_max_hours`, `direction` (`"forward"` / `"reverse"` / `"any"`), `edge_dim_predicates: dict[str, tuple[op, value]]`); the navigator walks the in-memory `AdjacencyIndex` for matching chains. `seed_keys=None` enumerates from all `from_key` nodes (capped at `max_results`). Returns `{pattern_id, n_results, motifs}` with each motif carrying `nodes`, `edges` (event_keys), `timestamps`, `amounts`, `dim_values_per_hop` (only when `edge_dim_predicates` were used), optional `score` (anchor patterns only) and `score_breakdown`. Raises on anchor pattern (event-only), unknown pattern, empty hops, k>6, `max_results<1`. Bounded MVP — `amount_ratio_to_prev` and `require_anomalous_entity` predicates plus k>6 land in a follow-up.
278
+
279
+ ### `HopPredicate` (dataclass, frozen)
280
+
281
+ Fields: `amount_min`, `amount_max`, `time_delta_max_hours`, `direction` (Literal["forward", "reverse", "any"], default `"forward"`), `edge_dim_predicates` (`dict[str, tuple[str, float]]`, e.g. `{"pair_edge_count": (">=", 20.0)}`). Operators: `<`, `<=`, `>`, `>=`, `==`.
282
+
283
+ ### Density Gaps Engine (`hypertopos.engine.density_gaps`)
284
+
285
+ | Symbol | Description |
286
+ |--------|-------------|
287
+ | `ECDFEntry(sorted_values)` | Frozen dataclass with `transform(x)` (raw → uniform `[0, 1]`) and `inverse(u)` (uniform → raw). Constructed via `ECDFEntry.from_values(x)` |
288
+ | `is_usable_for_gap(col)` | `(bool, reason)` admissibility check — rejects `too_sparse` (<30 finite), `degenerate` (σ≈0), `bernoulli_like` (≤2 unique) |
289
+ | `select_pairs_by_corr(corr, *, r_min, r_max, top_k)` | Pick top-`top_k` dim pairs with Pearson `|r| ∈ [r_min, r_max]`, sorted by `|r|` desc |
290
+ | `compute_density_gaps_for_pair(u_i, u_j, *, n, bins, alpha)` | Per-cell chi² residuals against uniform-independence expectation, Benjamini-Hochberg correction. Returns under-populated cells with `p_value`, `q_value`, `is_gap` |
291
+
292
+ ### `GDSNavigator.find_density_gaps`
293
+
294
+ ```python
295
+ nav.find_density_gaps(
296
+ pattern_id: str,
297
+ *,
298
+ top_n: int = 10,
299
+ dim_pairs: list[tuple[str, str]] | None = None,
300
+ bins: int = 10,
301
+ alpha: float = 0.05,
302
+ r_min: float = 0.1,
303
+ r_max: float = 0.7,
304
+ ) -> dict
305
+ ```
306
+
307
+ Returns dict with `pattern_id`, `n_entities`, `gaps` (each sorted by
308
+ `ratio` desc with `dim_i` / `dim_j` / `delta_range_i` / `delta_range_j`
309
+ (z-score space — geometry deltas, not raw property values) / `u_range_i`
310
+ / `u_range_j` / `observed` / `expected` / `q_value` / `correlation`), `excluded_dims`, and `n_pairs_tested`. Raises
311
+ `GDSNavigationError` for unknown pattern, fewer than 100 entities,
312
+ invalid `alpha` / `bins` / `r_min`/`r_max` / `top_n`, or unknown dim
313
+ names in user-supplied `dim_pairs`.
314
+
315
+ ### Edge Features Engine (`hypertopos.engine.edge_features`)
316
+
317
+ | Symbol | Description |
318
+ |--------|-------------|
319
+ | `EDGE_DIM_KINDS: dict[str, str]` | Per-dim Bregman kind tag (poisson / gaussian / bernoulli) keyed by dim name |
320
+ | `compute_pair_edge_count(edges)` | edges per `(from_key, to_key)` directed pair |
321
+ | `compute_position_in_chain(edges, *, min_position)` | depth in longest reverse-temporal chain ending at this edge; values below `min_position` zero out |
322
+ | `compute_time_since_pair_last_edge(edges, *, burst_seconds, dormant_seconds)` | seconds since previous edge in same pair; first edge → `dormant_seconds` |
323
+ | `compute_pair_amount_zscore(edges, *, cv_threshold, min_count)` | signed z-score of amount within LOW_VAR pairs |
324
+ | `compute_find_motif_structuring(edges, *, time_window_hours, amt1_min, amt2_max)` | 1.0 if edge participates in any A→B→C→D structuring motif |
325
+ | `compute_all_edge_dims(edges, config)` | orchestrator — runs each dim listed in config, returns Arrow table keyed by `event_key` |
326
+
327
+ ### Structuring Engine (`hypertopos.engine.structuring`)
328
+
329
+ | Symbol | Description |
330
+ |--------|-------------|
331
+ | `enumerate_structuring_for_seed(seed, edges, *, time_window_sec, amt1_min, amt2_max, max_instances)` | Single-seed enumeration of A→B→C→D motifs anchored at `seed`; returns list of motif dicts |
332
+ | `enumerate_structuring_event_keys(edges, *, time_window_sec, amt1_min, amt2_max)` | All-seeds sweep — returns the set of `event_key`s participating in any motif. Build-time helper for `compute_find_motif_structuring` |
333
+
334
+ ### Edge Dimensions YAML Parser (`hypertopos.builder.mapping`)
335
+
336
+ | Symbol | Description |
337
+ |--------|-------------|
338
+ | `EdgeDimensionsConfig(dims: dict[str, dict])` | Parsed `edge_dimensions:` block — frozen dataclass attached to `PatternMapping.edge_dimensions` |
339
+ | `parse_edge_dimensions(raw_list, *, pattern_type)` | Parse + validate the YAML list of dim entries (bare strings or single-key dicts). Raises `ValueError` on anchor pattern, `min_position < 3`, `cv_threshold` outside `(0, 1]`, `min_count < 2`, non-positive `amt1_min` / `amt2_max` / `time_window_hours`, negative `burst_seconds`, duplicate or unknown dim names, malformed entries |
340
+
341
+ ### Calibration History (GDSReader)
342
+
343
+ | Method | Description |
344
+ |--------|-------------|
345
+ | `read_calibration_fit(pattern_id, version=None)` | Load one calibration epoch as a frozen `CalibrationFit` dataclass. `version=None` resolves to the pattern's current `calibration_epoch` from sphere.json. Raises `CalibrationNotFoundError` if the requested version does not exist on disk (trimmed by GC, or schema bump wiped history). For 2.3 spheres, `version=None` and `version=1` both reconstruct a `CalibrationFit` from the inline sphere.json fields; any `version >= 2` raises `CalibrationNotFoundError`. |
346
+ | `list_calibration_versions(pattern_id)` | Return all available calibration epochs for a pattern, ascending. On a 2.3 sphere returns `[1]`. On a 2.4 sphere returns the integers N present in `_gds_meta/calibration_history/{pattern_id}/v={N}.json`. |
347
+ | `read_calibration_history_policy()` | Read the `calibration_history_policy` from sphere.json. Defaults to `{"last_k": 5}` if absent. Raises `ValueError` if `last_k < 1`. |
348
+
349
+ ### `CalibrationFit` (dataclass, frozen)
350
+
351
+ Fields: `pattern_id`, `calibration_epoch`, `schema_version`, `schema_hash`,
352
+ `mu`, `sigma_diag`, `theta`, `population_size`, `dimension_weights`,
353
+ `dimension_kinds`, `dim_percentiles`, `group_stats`, `gmm_components`,
354
+ `edge_max`, `computed_at`, `last_calibrated_at`.
355
+
356
+ ### `CalibrationNotFoundError` (exception)
357
+
358
+ Extends `GDSError`. Raised by `read_calibration_fit` when the requested epoch
359
+ is not on disk.
360
+
361
+ ### Calibration drift
362
+
363
+ #### `GDSNavigator.compare_calibrations(pattern_id, v_from=None, v_to=None, top_n=10, verbose=False) -> CalibrationDriftReport`
364
+
365
+ Per-dimension μ/σ/θ drift between two calibration epochs of the same pattern.
366
+ Auto-resolves: both `None` → second-to-last vs last; only `v_to=None` →
367
+ explicit `v_from` vs latest. Returns a `CalibrationDriftReport` with an
368
+ aggregate `overall_drift_rms` (RMS in σ units), ranked `top_drifted`
369
+ list, and optional full `per_dimension` breakdown when `verbose=True`.
370
+
371
+ Raises `ValueError` on `v_from == v_to`, single-epoch auto-resolve, or
372
+ schema_hash mismatch (cross-schema mu vectors are not dimensionally
373
+ comparable). `CalibrationNotFoundError` bubbles from missing versions.
374
+
375
+ #### `CalibrationDriftReport` (dataclass, frozen)
376
+
377
+ Fields: `pattern_id`, `v_from`, `v_to`, `schema_hash`,
378
+ `population_size_from`, `population_size_to`, `overall_drift_rms`,
379
+ `top_drifted: list[DimensionDrift]`, `per_dimension: list[DimensionDrift] | None`.
380
+
381
+ #### `DimensionDrift` (dataclass, frozen)
382
+
383
+ Fields: `dim_index`, `dim_kind`, `mu_from`, `mu_to`, `mu_delta`,
384
+ `mu_delta_normalized` (z-score: `(mu_to - mu_from) / sigma_from` with
385
+ `sigma_safe` guard for degenerate dims), `sigma_from`, `sigma_to`,
386
+ `sigma_delta`, `theta_from`, `theta_to`, `theta_delta`.
387
+
388
+ ### Drift decomposition
389
+
390
+ #### `GDSNavigator.decompose_drift(entity_key, pattern_id, v_from=None, v_to=None, timestamp_from=None, timestamp_to=None, top_n=10, verbose=False) -> IntrinsicExtrinsicReport`
391
+
392
+ Decompose an entity's drift between two temporal slices into intrinsic
393
+ (entity-driven, σ_v1-normalised shape change) and extrinsic (population-
394
+ recalibration-driven, residual) components, viewed across two calibration
395
+ epochs. Auto-resolves: both version args `None` → oldest retained vs current;
396
+ both timestamp args `None` → first vs last temporal slice. Returns an
397
+ `IntrinsicExtrinsicReport` with aggregate L2 displacements, sum-of-squares
398
+ `intrinsic_fraction` in [0, 1], ranked `top_dimensions`, and optional full
399
+ `per_dimension` breakdown when `verbose=True`.
400
+
401
+ Raises `ValueError` on `<2` retained epochs, `v_from == v_to`, schema_hash
402
+ mismatch, `<2` slices in window, or event pattern. `CalibrationNotFoundError`
403
+ bubbles up from missing versions.
404
+
405
+ #### `IntrinsicExtrinsicReport` (dataclass, frozen)
406
+
407
+ Fields: `pattern_id`, `entity_key`, `v_from`, `v_to`, `schema_hash`,
408
+ `timestamp_from`, `timestamp_to`, `intrinsic_displacement`,
409
+ `extrinsic_displacement`, `total_displacement`, `intrinsic_fraction`,
410
+ `top_dimensions: list[DimensionDecomposition]`, `per_dimension: list[DimensionDecomposition] | None`.
411
+
412
+ #### `DimensionDecomposition` (dataclass, frozen)
413
+
414
+ Fields: `dim_index`, `dim_kind`, `dim_label`, `total` (delta_b - delta_a),
415
+ `intrinsic` ((s_b - s_a) / σ_v1), `extrinsic` (residual), `intrinsic_fraction`
416
+ (per-dim sum-of-squares ratio in [0, 1]).
417
+
418
+ ### Influence analysis
419
+
420
+ #### `GDSNavigator.find_calibration_influencers(pattern_id, top_n=10, classify="hidden", high_threshold_pct=90.0, sample_size=None, verbose=False) -> InfluenceReport`
421
+
422
+ Detect entities with high influence on coordinate system calibration. Classifies
423
+ into the 4-cell influence × anomaly matrix (hidden / distorter /
424
+ standard_anomaly / normal); default `classify="hidden"` returns top_n entities
425
+ with high `total_impact` but low anomaly score (the patent's primary detection
426
+ cell — entities defining what 'normal' means without being detected as anomalous).
427
+
428
+ Math: exact leave-one-out via rolling Σs/Σs². For each entity E:
429
+ - `μ_without[i] = (Σs[i] - s_E[i]) / (N-1)`
430
+ - `σ²_without[i] = (Σs²[i] - s_E[i]²) / (N-1) - μ_without[i]²`
431
+ - `mu_impact = ‖(μ_full - μ_without) / σ_full_safe‖`
432
+ - `sigma_impact = ‖(σ_full - σ_without) / σ_full_safe‖`
433
+ - `total_impact = sqrt(mu_impact² + sigma_impact²)`
434
+
435
+ Classification: `high_impact = total_impact ≥ percentile(total_impact, high_threshold_pct)`;
436
+ `high_anomaly = ‖δ(E)‖ ≥ θ_norm`.
437
+
438
+ `verbose=True` attaches per-entry `cascading_flip_count` — count of
439
+ OTHER entities flipping `is_anomaly` after this entity's removal.
440
+
441
+ Raises `ValueError` on event pattern, `N<2`, `high_threshold_pct ∉ (0, 100)`,
442
+ invalid `classify`, or `top_n ∉ [1, 50]`.
443
+
444
+ #### `GDSNavigator.find_group_influence(pattern_id, groups) -> list[GroupInfluenceReport]`
445
+
446
+ Caller-supplied per-group leave-set-out impact. For each input
447
+ group, computes the set's collective μ/σ shift plus
448
+ `reinforcing_factor = total_impact_set / Σ_individuals`. Reinforcing > 1
449
+ indicates members pull together (coordinated injection or duplicate-record
450
+ contamination); < 1 indicates canceling (members offset each other).
451
+
452
+ Returns `list[GroupInfluenceReport]` (input order preserved).
453
+
454
+ Raises `ValueError` on event pattern, `N<3`, empty groups list, group with
455
+ `<2` members, group `≥ N`, missing entity_key, duplicate entity in group, or
456
+ undefined reinforcing factor (sum of individual impacts = 0).
457
+
458
+ #### Additive surface on `find_anomalies` MCP tool
459
+
460
+ Each per-entity polygon dict gains 2 scalar fields: `total_impact` (M4
461
+ leave-one-out scalar) and `classification` (`"hidden"` / `"distorter"` /
462
+ `"standard_anomaly"` / `"normal"`). Resolves to `null` per-entry when pattern
463
+ is event-type, `N<2`, or storage backend lacks shape-reconstruction
464
+ prerequisites — keeps batch response intact.
465
+
466
+ #### `InfluenceReport` (dataclass, frozen)
467
+
468
+ Fields: `pattern_id`, `pattern_version`, `population_size`, `high_threshold_pct`,
469
+ `total_impact_threshold`, `theta_norm`, `classify_filter`,
470
+ `cell_counts: dict[str, int]`, `entries: list[InfluenceEntry]`.
471
+
472
+ #### `InfluenceEntry` (dataclass, frozen)
473
+
474
+ Fields: `entity_key`, `mu_impact`, `sigma_impact`, `total_impact`, `delta_norm`,
475
+ `classification`, `top_dim_contributions: list[DimensionContribution]`,
476
+ `cascading_flip_count: int | None`.
477
+
478
+ #### `GroupInfluenceReport` (dataclass, frozen)
479
+
480
+ Fields: `pattern_id`, `pattern_version`, `group_index`, `member_count`,
481
+ `members: list[str]`, `mu_impact_set`, `sigma_impact_set`, `total_impact_set`,
482
+ `sum_individual_impacts`, `reinforcing_factor`,
483
+ `top_dim_contributions: list[DimensionContribution]`.
484
+
485
+ #### `DimensionContribution` (dataclass, frozen)
486
+
487
+ Fields: `dim_index`, `dim_kind`, `dim_label`, `mu_shift`, `sigma_shift`,
488
+ `contribution` (sqrt(mu_shift² + sigma_shift²)).
489
+
490
+ ### Cross-pattern temporal lead-lag
491
+
492
+ #### `Navigator.find_lead_lag`
493
+
494
+ ```python
495
+ nav.find_lead_lag(
496
+ pattern_a: str,
497
+ pattern_b: str,
498
+ *,
499
+ timestamp_from: str | None = None,
500
+ timestamp_to: str | None = None,
501
+ cohort: Literal["fixed", "all"] = "fixed",
502
+ min_epochs: int = 8,
503
+ max_lag: int | None = None,
504
+ fdr_alpha: float = 0.05,
505
+ fdr_method: Literal["bh", "storey"] = "storey",
506
+ verbose: bool = False,
507
+ entity_key: str | None = None,
508
+ ) -> LeadLagReport
509
+ ```
510
+
511
+ Cross-pattern temporal lead-lag in population-relative coordinates. Both
512
+ patterns must be `pattern_type="anchor"` and (effectively) over the same
513
+ entity space — `cohort="fixed"` raises empty-cohort otherwise. Time alignment
514
+ uses the intersection of pattern timestamp sets; `min_epochs` is a hard
515
+ floor. Default `max_lag = (N - 1) // 4`.
516
+
517
+ Three nested answer levels:
518
+
519
+ 1. **Population scalar.** `lag` and `correlation` from the cross-correlation
520
+ of differenced population centroid drift series. Bonferroni-adjusted peak
521
+ threshold (`max_corr_threshold` field) is the cut-off used by
522
+ `is_significant`.
523
+ 2. **Per-dim D_A × D_B matrix.** `top_dim_pairs` (top 10 by ascending
524
+ q-value, ties broken by descending |corr|) with full sorted matrix in
525
+ `per_dim_pairs` when `verbose=True`. BH or Storey FDR applied to
526
+ Bonferroni-over-lag-adjusted p-values across all `D_A * D_B` pairs.
527
+ 3. **Per-entity drill-down.** Pass `entity_key` to replace the population
528
+ centroid by that entity's own delta trajectory.
529
+
530
+ #### `LeadLagReport` (dataclass, frozen)
531
+
532
+ Fields: `pattern_a`, `pattern_b`, `entity_key: str | None`, `n_epochs_used`,
533
+ `n_dropped_a`, `n_dropped_b`, `cohort_size`, `cohort_dropped: int | None`,
534
+ `timestamp_from: datetime`, `timestamp_to: datetime`, `schema_hash_a`,
535
+ `schema_hash_b`, `lag`, `correlation`,
536
+ `centroid_drift_series_a/b: list[float]`, `lag_volatility`,
537
+ `correlation_volatility`, `volatility_series_a/b: list[float]`, `agreement`
538
+ (`"strong"` / `"weak"` / `"divergent"`), `bartlett_ci_95`,
539
+ `max_corr_threshold`, `is_significant`, `fdr_alpha`, `fdr_method`,
540
+ `n_dim_pairs`, `n_significant_pairs`, `top_dim_pairs: list[DimPairLeadLag]`,
541
+ `per_dim_pairs: list[DimPairLeadLag] | None`, `reliability`
542
+ (`"high"` / `"medium"` / `"low"`), `max_lag`,
543
+ `correlation_by_lag: list[float]`, `coverage_warning`, `degenerate_signal`.
544
+
545
+ #### `DimPairLeadLag` (dataclass, frozen)
546
+
547
+ Fields: `dim_index_a`, `dim_index_b`, `dim_label_a: str | None`,
548
+ `dim_label_b: str | None`, `lag`, `correlation`, `p_value`, `q_value`,
549
+ `is_significant`.
550
+
257
551
  ---
258
552
 
259
553
  ## Model Objects
@@ -113,7 +113,7 @@ For a denser top-down view, this stack is a useful shorthand:
113
113
 
114
114
  ```text
115
115
  ┌─────────────────────────────────────┐
116
- │ MCP Server (hypertopos-mcp) │ 67 tools, smart detection mode
116
+ │ MCP Server (hypertopos-mcp) │ 80 tools, smart detection mode
117
117
  ├─────────────────────────────────────┤
118
118
  │ PassiveScanner │ multi-source batch screening
119
119
  ├─────────────────────────────────────┤
@@ -175,7 +175,7 @@ graph LR
175
175
  ST --> E["Engine\ngeometry, delta,\nclustering, DTW"]
176
176
  E --> N["Navigator\nπ1–π12, detect_*"]
177
177
  N --> PS["PassiveScanner\nmulti-source\nbatch screening"]
178
- N --> MCP["MCP Server\n67 tools\nstdio transport"]
178
+ N --> MCP["MCP Server\n72 tools\nstdio transport"]
179
179
  MCP --> Agent["AI Agent"]
180
180
  CT["CalibrationTracker\nonline drift\nWelford stats"] -.-> E
181
181
  M["Manifest\nMVCC version pin"] -.-> ST