hypertopos 0.7.1__tar.gz → 0.7.2__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 (328) hide show
  1. {hypertopos-0.7.1 → hypertopos-0.7.2}/CHANGELOG.md +16 -0
  2. {hypertopos-0.7.1 → hypertopos-0.7.2}/LICENSE.md +1 -1
  3. {hypertopos-0.7.1 → hypertopos-0.7.2}/PKG-INFO +1 -1
  4. {hypertopos-0.7.1 → hypertopos-0.7.2}/README.md +2 -1
  5. {hypertopos-0.7.1 → hypertopos-0.7.2}/SECURITY.md +1 -1
  6. hypertopos-0.7.2/docs/patent-implementation-map.md +123 -0
  7. hypertopos-0.7.2/docs/refutations.md +71 -0
  8. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/__init__.py +1 -1
  9. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/chains.py +60 -1
  10. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/counterfactual.py +44 -0
  11. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/geometry.py +42 -2
  12. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/model/objects.py +7 -0
  13. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/navigation/navigator.py +558 -13
  14. {hypertopos-0.7.1 → hypertopos-0.7.2}/pyproject.toml +1 -1
  15. hypertopos-0.7.2/tests/test_audit_label_alignment.py +245 -0
  16. hypertopos-0.7.2/tests/test_chain_edge_potentials.py +156 -0
  17. hypertopos-0.7.2/tests/test_counterfactual_frozen_trajectory.py +278 -0
  18. hypertopos-0.7.2/tests/test_kind_tag_mismatch_warning.py +277 -0
  19. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_navigation.py +9 -3
  20. hypertopos-0.7.2/tests/test_signed_confidence_ranking.py +401 -0
  21. {hypertopos-0.7.1 → hypertopos-0.7.2}/.github/workflows/publish-pypi.yml +0 -0
  22. {hypertopos-0.7.1 → hypertopos-0.7.2}/.gitignore +0 -0
  23. {hypertopos-0.7.1 → hypertopos-0.7.2}/ROADMAP.md +0 -0
  24. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/README.md +0 -0
  25. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/api-reference.md +0 -0
  26. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/architecture.md +0 -0
  27. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/LICENSE +0 -0
  28. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/README.md +0 -0
  29. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/README.md +0 -0
  30. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/SUMMARY.md +0 -0
  31. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-a-report.md +0 -0
  32. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-a-scoring.md +0 -0
  33. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-b-report.md +0 -0
  34. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-b-scoring.md +0 -0
  35. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-settings.md +0 -0
  36. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-a-report.md +0 -0
  37. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-a-scoring.md +0 -0
  38. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-b-report.md +0 -0
  39. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-b-scoring.md +0 -0
  40. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-settings.md +0 -0
  41. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-a-report.md +0 -0
  42. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-a-scoring.md +0 -0
  43. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-b-report.md +0 -0
  44. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-b-scoring.md +0 -0
  45. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-settings.md +0 -0
  46. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-a-report.md +0 -0
  47. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-a-scoring.md +0 -0
  48. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-b-report.md +0 -0
  49. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-b-scoring.md +0 -0
  50. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-settings.md +0 -0
  51. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-a-report.md +0 -0
  52. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-a-scoring.md +0 -0
  53. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-b-report.md +0 -0
  54. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-b-scoring.md +0 -0
  55. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-settings.md +0 -0
  56. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-a-report.md +0 -0
  57. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-a-scoring.md +0 -0
  58. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-b-report.md +0 -0
  59. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-b-scoring.md +0 -0
  60. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-settings.md +0 -0
  61. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/sphere/prepare_accounts.py +0 -0
  62. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/sphere/prepare_cpty_banks.py +0 -0
  63. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/sphere/prepare_districts.py +0 -0
  64. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/sphere/prepare_loan_accounts.py +0 -0
  65. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/sphere/prepare_operations.py +0 -0
  66. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/sphere/prepare_orders.py +0 -0
  67. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/sphere/prepare_transactions.py +0 -0
  68. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/sphere/prepare_tx_types.py +0 -0
  69. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/berka/sphere/sphere.yaml +0 -0
  70. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/ibm-aml/README.md +0 -0
  71. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/ibm-aml/_layer2_cross_eval.py +0 -0
  72. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/ibm-aml/_layer3_cross_eval.py +0 -0
  73. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_accounts.py +0 -0
  74. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_currencies.py +0 -0
  75. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_payment_formats.py +0 -0
  76. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_transactions.py +0 -0
  77. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/ibm-aml/hi_small_sphere/sphere.yaml +0 -0
  78. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/ibm-aml/layer1_passive_scan.py +0 -0
  79. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/ibm-aml/layer2_xgboost_sweep.py +0 -0
  80. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/ibm-aml/layer3_typology_filter.py +0 -0
  81. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/ibm-aml/li_small_sphere/prepare_accounts.py +0 -0
  82. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/ibm-aml/li_small_sphere/prepare_currencies.py +0 -0
  83. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/ibm-aml/li_small_sphere/prepare_payment_formats.py +0 -0
  84. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/ibm-aml/li_small_sphere/prepare_transactions.py +0 -0
  85. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/ibm-aml/li_small_sphere/sphere.yaml +0 -0
  86. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/ibm-aml/passive-scan-calibration.md +0 -0
  87. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/nyc-taxi/README.md +0 -0
  88. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/nyc-taxi/results/0.1.0/SUMMARY.md +0 -0
  89. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-a-report.md +0 -0
  90. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-a-scoring.md +0 -0
  91. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-b-report.md +0 -0
  92. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-b-scoring.md +0 -0
  93. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-c-report.md +0 -0
  94. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-settings.md +0 -0
  95. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-a-report.md +0 -0
  96. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-a-scoring.md +0 -0
  97. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-b-report.md +0 -0
  98. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-b-scoring.md +0 -0
  99. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-c-report.md +0 -0
  100. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-settings.md +0 -0
  101. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-a-report.md +0 -0
  102. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-a-scoring.md +0 -0
  103. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-b-report.md +0 -0
  104. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-b-scoring.md +0 -0
  105. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-c-report.md +0 -0
  106. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-settings.md +0 -0
  107. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/nyc-taxi/sphere/prepare_trips.py +0 -0
  108. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmark/nyc-taxi/sphere/sphere.yaml +0 -0
  109. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/benchmarks.md +0 -0
  110. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/concepts.md +0 -0
  111. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/configuration.md +0 -0
  112. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/data-format.md +0 -0
  113. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/external-chains-as-anchor-line.md +0 -0
  114. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/images/build-pipeline.svg +0 -0
  115. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/images/core-objects.svg +0 -0
  116. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/images/cutting-plane.svg +0 -0
  117. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/images/directory-layout.svg +0 -0
  118. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/images/hypertopos-overview.svg +0 -0
  119. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/images/model-lifecycle.svg +0 -0
  120. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/images/navigation-primitives.svg +0 -0
  121. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/images/three-scales.svg +0 -0
  122. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/introduction.md +0 -0
  123. {hypertopos-0.7.1 → hypertopos-0.7.2}/docs/quickstart.md +0 -0
  124. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/builder/__init__.py +0 -0
  125. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/builder/_bootstrap.py +0 -0
  126. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/builder/_bregman.py +0 -0
  127. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/builder/_scatter.py +0 -0
  128. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/builder/_stats.py +0 -0
  129. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/builder/_theta_sensitivity.py +0 -0
  130. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/builder/_writer.py +0 -0
  131. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/builder/builder.py +0 -0
  132. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/builder/conformance.py +0 -0
  133. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/builder/conformance_mapping.py +0 -0
  134. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/builder/derived.py +0 -0
  135. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/builder/dim_blocks.py +0 -0
  136. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/builder/mapping.py +0 -0
  137. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/builder/temporal_bucket.py +0 -0
  138. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/cli/__init__.py +0 -0
  139. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/cli/__main__.py +0 -0
  140. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/cli/build.py +0 -0
  141. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/cli/info.py +0 -0
  142. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/cli/main.py +0 -0
  143. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/cli/schema.py +0 -0
  144. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/cli/sources.py +0 -0
  145. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/__init__.py +0 -0
  146. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/adjacency.py +0 -0
  147. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/aggregation.py +0 -0
  148. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/calibration.py +0 -0
  149. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/calibration_label_aware.py +0 -0
  150. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/composition.py +0 -0
  151. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/conformance.py +0 -0
  152. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/density_gaps.py +0 -0
  153. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/diagnostics.py +0 -0
  154. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/dim_audit.py +0 -0
  155. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/edge_features.py +0 -0
  156. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/explanation.py +0 -0
  157. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/fdr.py +0 -0
  158. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/forecast.py +0 -0
  159. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/graph_algorithms.py +0 -0
  160. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/heredity.py +0 -0
  161. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/hop_predicate.py +0 -0
  162. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/invalidation.py +0 -0
  163. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/investigation.py +0 -0
  164. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/lance_sql_agg.py +0 -0
  165. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/p_value_calibration.py +0 -0
  166. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/selection.py +0 -0
  167. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/structuring.py +0 -0
  168. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/engine/topology.py +0 -0
  169. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/model/__init__.py +0 -0
  170. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/model/manifest.py +0 -0
  171. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/model/sphere.py +0 -0
  172. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/navigation/__init__.py +0 -0
  173. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/navigation/scanner.py +0 -0
  174. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/sphere.py +0 -0
  175. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/storage/__init__.py +0 -0
  176. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/storage/_lance_versions.py +0 -0
  177. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/storage/_schemas.py +0 -0
  178. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/storage/_tag_timestamps.py +0 -0
  179. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/storage/cache.py +0 -0
  180. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/storage/calibration_history.py +0 -0
  181. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/storage/exceptions.py +0 -0
  182. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/storage/reader.py +0 -0
  183. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/storage/topology_cache.py +0 -0
  184. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/storage/writer.py +0 -0
  185. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/utils/__init__.py +0 -0
  186. {hypertopos-0.7.1 → hypertopos-0.7.2}/hypertopos/utils/arrow.py +0 -0
  187. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/.gitkeep +0 -0
  188. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/conftest.py +0 -0
  189. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/fixtures/generate_fixtures.py +0 -0
  190. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/fixtures/synthetic_chain_sphere.py +0 -0
  191. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_adjacency.py +0 -0
  192. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_adjacency_vectorized.py +0 -0
  193. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_aggregation_engine.py +0 -0
  194. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_anomaly_propagation_in_chain.py +0 -0
  195. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_arrow_utils.py +0 -0
  196. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_bootstrap.py +0 -0
  197. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_bregman.py +0 -0
  198. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_builder.py +0 -0
  199. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_builder_alias.py +0 -0
  200. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_builder_chunking.py +0 -0
  201. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_builder_contagion.py +0 -0
  202. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_builder_derived.py +0 -0
  203. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_builder_edges.py +0 -0
  204. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_builder_fdr_hierarchy.py +0 -0
  205. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_builder_fdr_hierarchy_inject.py +0 -0
  206. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_builder_fdr_yaml_end_to_end.py +0 -0
  207. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_builder_rolling_z.py +0 -0
  208. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_builder_scatter.py +0 -0
  209. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_builder_temporal_bucket.py +0 -0
  210. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_builder_temporal_perf.py +0 -0
  211. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_cache.py +0 -0
  212. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_calibration.py +0 -0
  213. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_calibration_drift.py +0 -0
  214. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_calibration_history.py +0 -0
  215. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_calibration_history_builder.py +0 -0
  216. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_calibration_history_reader.py +0 -0
  217. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_calibration_label_aware.py +0 -0
  218. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_chain_coherent_anomaly.py +0 -0
  219. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_chain_coherent_synthetic.py +0 -0
  220. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_chain_drift_trajectory.py +0 -0
  221. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_chain_investigation_summary.py +0 -0
  222. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_chain_typology_and_extend.py +0 -0
  223. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_chain_witness_intersection.py +0 -0
  224. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_chains.py +0 -0
  225. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_cli.py +0 -0
  226. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_cli_alias.py +0 -0
  227. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_cli_label_aware_flag.py +0 -0
  228. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_cli_sources.py +0 -0
  229. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_compaction.py +0 -0
  230. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_composition.py +0 -0
  231. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_conformance.py +0 -0
  232. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_conformance_mapping.py +0 -0
  233. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_conftest_magicmock_restore.py +0 -0
  234. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_continuous_event_dims.py +0 -0
  235. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_counterfactual.py +0 -0
  236. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_data_quality_prop.py +0 -0
  237. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_decompose_drift.py +0 -0
  238. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_delta_dim_aggregation_consumers.py +0 -0
  239. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_delta_norm_signed.py +0 -0
  240. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_density_gaps_engine.py +0 -0
  241. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_detection.py +0 -0
  242. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_diagnostics.py +0 -0
  243. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_dim_audit.py +0 -0
  244. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_dim_blocks.py +0 -0
  245. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_dim_quality_warnings.py +0 -0
  246. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_distinct_neighbor_count.py +0 -0
  247. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_edge_dim_aggregations.py +0 -0
  248. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_edge_dim_aggregations_builder.py +0 -0
  249. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_edge_dim_aggregations_chain.py +0 -0
  250. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_edge_dim_aggregations_engine.py +0 -0
  251. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_edge_dim_aggregations_roundtrip.py +0 -0
  252. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_edge_dimensions_builder.py +0 -0
  253. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_edge_dimensions_yaml.py +0 -0
  254. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_edge_features.py +0 -0
  255. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_edge_features_reader.py +0 -0
  256. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_edge_potential.py +0 -0
  257. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_edge_table.py +0 -0
  258. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_engine.py +0 -0
  259. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_fdr.py +0 -0
  260. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_fdr_multi_resolution.py +0 -0
  261. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_fdr_per_dim.py +0 -0
  262. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_find_calibration_influencers.py +0 -0
  263. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_find_density_gaps.py +0 -0
  264. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_find_diverse_explanations.py +0 -0
  265. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_find_lead_lag.py +0 -0
  266. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_find_lead_lag_significance.py +0 -0
  267. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_find_motif_by_hops.py +0 -0
  268. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_forecast.py +0 -0
  269. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_fts.py +0 -0
  270. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_generate_sar_rationale.py +0 -0
  271. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_geometric_path.py +0 -0
  272. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_geometry_stats.py +0 -0
  273. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_gradient_alignment.py +0 -0
  274. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_graph_algorithms.py +0 -0
  275. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_graph_geometry_tension.py +0 -0
  276. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_heredity.py +0 -0
  277. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_hop_predicate_engine.py +0 -0
  278. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_hub_safety_caps.py +0 -0
  279. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_hybrid_search.py +0 -0
  280. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_incremental.py +0 -0
  281. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_invalidation.py +0 -0
  282. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_investigate_chain.py +0 -0
  283. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_investigate_entity.py +0 -0
  284. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_investigation.py +0 -0
  285. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_kind_detect.py +0 -0
  286. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_label_audit_schema.py +0 -0
  287. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_lance_sql_agg.py +0 -0
  288. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_m1_1_builder_wiring.py +0 -0
  289. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_mapping.py +0 -0
  290. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_model.py +0 -0
  291. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_motif.py +0 -0
  292. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_mvcc_session.py +0 -0
  293. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_native_mvcc_geometry.py +0 -0
  294. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_navigator_alerts.py +0 -0
  295. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_navigator_fdr_resolution.py +0 -0
  296. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_navigator_fdr_select.py +0 -0
  297. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_neighbors_window.py +0 -0
  298. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_observability.py +0 -0
  299. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_p_value_calibration.py +0 -0
  300. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_pattern_fdr_hierarchy.py +0 -0
  301. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_precision_stack.py +0 -0
  302. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_profile_motif_perf_smoke.py +0 -0
  303. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_reliability_flags.py +0 -0
  304. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_reputation.py +0 -0
  305. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_root_cause_trace.py +0 -0
  306. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_row_id_cache.py +0 -0
  307. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_scanner.py +0 -0
  308. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_schema_chain_eda_validation.py +0 -0
  309. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_selection.py +0 -0
  310. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_simulate_dimension_change.py +0 -0
  311. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_sphere.py +0 -0
  312. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_sphere_format_version.py +0 -0
  313. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_sphere_overview_perf.py +0 -0
  314. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_sphere_validation_cheap_tier.py +0 -0
  315. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_stats_guard.py +0 -0
  316. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_storage.py +0 -0
  317. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_streaming_reads.py +0 -0
  318. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_structuring_engine.py +0 -0
  319. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_tag_timestamps.py +0 -0
  320. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_temporal_centroids.py +0 -0
  321. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_theta_sensitivity.py +0 -0
  322. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_topology.py +0 -0
  323. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_topology_anomalies.py +0 -0
  324. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_topology_cache.py +0 -0
  325. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_topology_trajectory_anomalies.py +0 -0
  326. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_trajectory_ann.py +0 -0
  327. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_witness_cohort.py +0 -0
  328. {hypertopos-0.7.1 → hypertopos-0.7.2}/tests/test_witness_set.py +0 -0
@@ -7,6 +7,22 @@ Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.7.2] — 2026-05-21
11
+
12
+ ### Added
13
+ - `GDSNavigator.π5_attract_anomaly` / `find_anomalies` accept `rank_by="signed_confidence"` — composes `delta_norm_signed`, the Fisher LDA alignment, and the reliability flags into one confidence-weighted score: `score = delta_norm_signed × |lda_alignment| × (1 − reliability_penalty)` where `reliability_penalty = 0.5 × single_dim_driven + 0.5 × low_confidence_bucket`. Survivors carry `signed_confidence_score`, `lda_alignment`, `reliability_penalty` per polygon. Pattern without `label_aware_calibration` raises `GDSNavigationError` — no silent fallback to `delta_norm` ranking.
14
+ - New per-dim `kind_mismatch` warning class on `sphere_overview.dim_quality_warnings`. Fires when a `kind='gaussian'` dim shows `|direction_component| < 0.05` (LDA assigns near-zero weight) AND `cohens_d_pos_neg >= 0.3` (raw class stats separate) — the dim's variance is captured by another dim's Fisher axis, suggesting kind re-declaration or split. Composes with label-aware calibration; suppressed for dims already flagged `negative_space`. Pattern-level prerequisite: `Pattern.label_aware_calibration` must be non-None.
15
+ - `GDSNavigator.audit_label_alignment(pattern_id, *, top_n=10)` — reports Fisher LDA direction's discrimination power as AUROC of `delta_norm_signed` against the binary label column declared in `sphere.yaml`'s `label_audit:` block. Returns `{pattern_id, auroc, n_pos, n_neg, top_dims}` where `top_dims` carries the `top_n` dims sorted by `|direction_component|` desc (most label-discriminating axes first). Returns fallback shape with `auroc: null` and `label_aware_available: False` on patterns built without `label_audit:`.
16
+ - `Chain.to_dict()` carries an additional `edge_potentials: list[float | null]` field — one Euclidean distance `||delta(keys[i]) - delta(keys[i+1])||` per consecutive-pair hop, computed against a caller-supplied anchor pattern delta lookup. `null` on hops with missing polygon, mismatched delta shapes, or non-finite distance (NaN / inf strict-JSON sanitised). Length equals `len(keys) - 1` (empty list for single-entity chains). Surfaces high-distance behavioural jumps inside multi-hop chains for downstream chain investigation primitives.
17
+ - `GDSNavigator.π3_dive_solid` accepts `counterfactual_frozen_population: bool = False` — when `True`, each `SolidSlice` on the returned solid carries an additional `delta_norm_frozen_pop` field reporting the per-slice L2 norm recomputed against the FIRST slice's raw shape as the entity-relative reference epoch (sigma stays at the current pattern's diagonal). Answers "is this entity's apparent normalization a real shift, or just population drift around a stationary entity?" — a stationary entity yields `delta_norm_frozen_pop = 0` across all slices. Default `False` preserves the existing return shape (`delta_norm_frozen_pop = None`).
18
+ - `engine.counterfactual.recompute_delta_norm_against_frozen(shape, mu_frozen, sigma)` — pure-NumPy helper used by the frozen-trajectory path. Sigma-dead dims contribute zero (same convention as the per-edge counterfactual primitives).
19
+ - `SolidSlice.delta_norm_frozen_pop: float | None` — new optional field on the model dataclass populated by the frozen-trajectory path; `None` for the default build path.
20
+ - `docs/refutations.md` — record of tested-and-closed hypotheses: pattern-level σ-estimator swap, per-dim heuristic σ-swap, lazy-chain sampled calibration, Bregman-based `is_anomaly` flag, plus a methodology refutation on cycle-compression. Each entry states the hypothesis, dataset, numeric verdict, root cause, and closure rule.
21
+ - `docs/patent-implementation-map.md` — descriptive map of nine architectural-claim clusters (per-entity geometric position, counterfactual closure, multi-resolution FDR, persistent-homology cycle persistence, declarative compliance, HMP detector composition, chain-coherent investigation, reliability triage, build-time dim quality auditing) to their shipped surfaces. No internal tracker IDs.
22
+
23
+ ### Changed
24
+ - README adds CI test workflow badge linking to the public-mirror Actions run.
25
+
10
26
  ## [0.7.1] — 2026-05-20
11
27
 
12
28
  ### 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-05-20
29
+ **Change Date:** 2029-05-21
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.7.1
3
+ Version: 0.7.2
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
@@ -2,13 +2,14 @@
2
2
 
3
3
  > A behavioral feature layer for graph and temporal data — turning behavior into coordinates, trajectories, and explanations.
4
4
 
5
+ [![Test](https://github.com/hypertopos/hypertopos-py/actions/workflows/test.yml/badge.svg)](https://github.com/hypertopos/hypertopos-py/actions/workflows/test.yml)
5
6
  [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
6
7
  [![License: BSL 1.1](https://img.shields.io/badge/license-BSL%201.1-orange.svg)](LICENSE.md)
7
8
  [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.19482069.svg)](https://doi.org/10.5281/zenodo.19482069)
8
9
  [![PyArrow](https://img.shields.io/badge/format-PyArrow-red.svg)](https://arrow.apache.org/docs/python/)
9
10
  [![Lance](https://img.shields.io/badge/storage-Lance-blueviolet.svg)](https://github.com/lance-format/lance)
10
11
  [![MCP](https://img.shields.io/badge/MCP-compatible-green.svg)](https://modelcontextprotocol.io)
11
- [![Version](https://img.shields.io/badge/version-0.7.1-%235500FF.svg)](pyproject.toml)
12
+ [![Version](https://img.shields.io/badge/version-0.7.2-%235500FF.svg)](pyproject.toml)
12
13
 
13
14
  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
15
 
@@ -1,6 +1,6 @@
1
1
  # Security
2
2
 
3
- ## Current version: 0.7.1
3
+ ## Current version: 0.7.2
4
4
 
5
5
  hypertopos runs locally. No network services, no auth layer, no multi-tenancy.
6
6
 
@@ -0,0 +1,123 @@
1
+ # Patent implementation map
2
+
3
+ This document maps the architectural claims behind hypertopos to the shipped implementation surfaces in the current release.
4
+
5
+ Each cluster names a load-bearing idea, the navigator or MCP surface that ships it, and the method one-line. The point is to make it easy for a reviewer or external auditor to walk from "this is what the project claims to do" to "this is the function that does it and the test that verifies it".
6
+
7
+ There are no claim numbers (`IC-XX`, `DC-XX.Y`) in this document — those are internal tracker IDs. Each cluster is named by its descriptive function.
8
+
9
+ ---
10
+
11
+ ## Per-entity geometric position derived from a population
12
+
13
+ **What it is.** Every entity carries a position in a multi-dimensional space whose axes are not raw column values but population-relative statistics — z-scores against the population mean, ratios against population percentiles, signed projections onto label-discriminating axes. The position is computed once at build time and stored as a Lance column.
14
+
15
+ **Surfaces.** `Pattern.mu_diag`, `Pattern.sigma_diag`, `Pattern.theta_diag` (population-level statistics persisted on every pattern). `Pattern.label_aware_calibration` (per-class moments + Fisher LDA direction, opt-in via `sphere.yaml`'s `label_audit:` block). `delta_norm` and `delta_norm_signed` Lance columns on every geometry table.
16
+
17
+ **Method.** Welford one-pass for mu / sigma. Quantile sketches for theta. Linear discriminant analysis with diagonal-covariance approximation for label-aware direction. All computations are deterministic and reproducible from the source data plus `sphere.yaml`.
18
+
19
+ **Tests.** `tests/test_calibration.py`, `tests/test_calibration_label_aware.py`, `tests/test_m1_1_builder_wiring.py`.
20
+
21
+ ---
22
+
23
+ ## Counterfactual closure — what changes if X is removed
24
+
25
+ **What it is.** For an anomalous entity, ask "would it still be anomalous if this edge, this counterparty, or this dim were removed?" — and answer with a closed-form recomputation of `delta_norm` against the same population statistics, no model retraining.
26
+
27
+ **Surfaces.** `GDSNavigator.simulate_edge_removal`, `simulate_counterparty_removal`, `select_minimal_joint_edge_removal`, `simulate_dimension_change`. Each surfaces as an MCP tool with the same name.
28
+
29
+ **Method.** Closed-form exp-family delta recomputation. For each candidate removal, recompute the entity's polygon contribution to `delta_norm` as `mu_diag - new_centroid`, take the ratio against `sigma_diag`, project against `theta_diag` percentile — same calibration, different inputs. Greedy selection for the minimal joint set.
30
+
31
+ **Tests.** `tests/test_counterfactual_*.py`.
32
+
33
+ ---
34
+
35
+ ## Multi-resolution false-discovery-rate control
36
+
37
+ **What it is.** Standard Benjamini-Hochberg FDR on a per-entity anomaly score answers one question — "is this entity anomalous?" — but ignores that entities live in nested cells (region × time × kind). Multi-resolution FDR aggregates per-cell p-values up a hierarchy with Tippett min-p and reports `q`-values at every level, surviving the more conservative join.
38
+
39
+ **Surfaces.** `Pattern.fdr_hierarchy` and `Pattern.fdr_temporal_hierarchy` (declarative schema in `sphere.yaml`). `find_anomalies(fdr_resolution, fdr_temporal_resolution)` MCP tool returns `cell_q_spatial` and `cell_q_temporal` per survivor. `engine.fdr.fdr_multi_resolution` (pure-math primitive).
40
+
41
+ **Method.** Per-cell Fisher exact 2×2 on an anomaly indicator vs background. BH or Storey correction within each cell. Tippett min-p aggregation up each declared level. Independent per-axis adjustment for spatial vs temporal hierarchies.
42
+
43
+ **Tests.** `tests/test_fdr_*.py`.
44
+
45
+ ---
46
+
47
+ ## Persistent-homology cycle persistence at the entity level
48
+
49
+ **What it is.** Treat each entity's k-nearest-neighbour ball in the polygon space as a small point cloud and ask whether that cloud carries persistent topological cycles. An entity with high `h1_max_persistence` sits inside a non-trivial topological structure — a ring, a hub, a cluster boundary — that single-entity z-scores cannot see.
50
+
51
+ **Surfaces.** `find_topological_anomalies` MCP tool. `engine.topology` module (Vietoris-Rips persistent homology backed by `ripser` when the optional `[topology]` extra is installed). `storage.topology_cache` for memoised per-entity persistence values.
52
+
53
+ **Method.** Per-entity k-NN ball (k ≈ 50 by default), PCA reduction to 10 dims, Vietoris-Rips H_1 persistence computed with `ripser`. Score = max `h1` death-birth interval. Cached because the computation is expensive; invalidated when calibration epochs bump.
54
+
55
+ **Tests.** `tests/test_topology_*.py`.
56
+
57
+ ---
58
+
59
+ ## Declarative compliance rules as a first-class build-time predicate language
60
+
61
+ **What it is.** Compliance officers write rules in YAML using a small declarative AST (`and / or / not` over `==, !=, <, <=, >, >=, in`); the builder evaluates them at build time and materialises a violations sidecar Lance dataset; a runtime MCP tool reads the sidecar with rule-id and severity pushdown. Separate from `delta_norm` anomaly flags — an entity can be one, the other, or both.
62
+
63
+ **Surfaces.** `Pattern.conformance_rules` (model field). `builder.conformance_mapping.parse_conformance_rules` (YAML loader with column-existence validation). `find_conformance_violations` MCP tool.
64
+
65
+ **Method.** Vectorised PyArrow expression evaluation. Severity literals `low < medium < high < critical`. Rule-id stamping. Sidecar Lance dataset alongside the geometry table.
66
+
67
+ **Tests.** `tests/test_conformance_mapping.py`, `tests/test_find_conformance_violations_mcp.py`.
68
+
69
+ ---
70
+
71
+ ## Detector composition via harmonic-mean p-value
72
+
73
+ **What it is.** Five independent detectors — `delta_norm`, `neighbor_contamination`, `segment_shift`, `trajectory_continuous`, `density_gap` — score each entity. A correct composition treats these as multiple hypothesis tests on the same null and combines them via the Wilson harmonic-mean p-value (HMP), not via score averaging. The combined `hmp` is rank-stable across detector subsets and exact under the null.
74
+
75
+ **Surfaces.** `combine_anomaly_pvalues`, `classify_detector_consensus`, `composite_risk`, `composite_risk_batch` MCP tools. `engine.composition.harmonic_mean_p` (pure-math primitive). `engine.p_value_calibration` (per-detector score-to-p-value adapters).
76
+
77
+ **Method.** Per-detector calibrated p-values. Wilson HMP combiner. Null-simulation lookup for the `L`-test threshold at level `alpha`. Per-detector weighting optional.
78
+
79
+ **Tests.** `tests/test_composition_*.py`, `tests/test_detector_consensus_*.py`.
80
+
81
+ ---
82
+
83
+ ## Chain-coherent investigation primitives
84
+
85
+ **What it is.** A chain is a multi-hop path through an event graph (`from_account → to_account` over time). Investigating a chain means asking whether the chain itself carries anomaly signal (`investigate_chain`), whether its members move together in regime space (`chain_drift_trajectory`), whether their explanatory witness sets intersect (`chain_witness_intersection`), and whether the chain's typology matches a known pattern (`classify_chain_typology`). The chain becomes a first-class investigation entity, not just a list of vertices.
86
+
87
+ **Surfaces.** `chain_lines:` YAML block. `extract_chains` build pass. `chain_pattern` derived geometry. `find_chains_with_coherent_anomaly`, `chain_witness_intersection`, `chain_drift_trajectory`, `classify_chain_typology`, `extend_chain`, `investigate_chain`, `generate_sar_rationale` MCP tools.
88
+
89
+ **Method.** Seeded BFS over the event graph with multi-criteria seed selection (`fan_out`, `cross_bank`, `multi_currency`, `pass_through`). Chain feature extraction at build time (`hop_count`, `is_cyclic`, `time_span_hours`, `amount_decay`, etc.). Same pattern-level calibration applied to chains as to entities, so `delta_norm` carries the same population-relative semantics.
90
+
91
+ **Tests.** `tests/test_chain_*.py`.
92
+
93
+ ---
94
+
95
+ ## Reliability triage on every flagged polygon
96
+
97
+ **What it is.** A polygon flagged anomalous can be flagged because one dim dominates the score (an artefact of that dim's calibration, not a real anomaly) or because the bootstrap confidence on `is_anomaly` is fragile (the flag would flip if the population were resampled). Both surfaces are reported as boolean flags alongside the score, so an investigator can corroborate before opening a case.
98
+
99
+ **Surfaces.** `reliability_flags` field on `find_anomalies`, `explain_anomaly`, `composite_risk`, `combine_anomaly_pvalues`, `investigate_entity`. Shape `{single_dim_driven, dominant_dim, dominant_dim_share, low_confidence_bucket, confidence, flags}`.
100
+
101
+ **Method.** `single_dim_driven` fires when one dim's attribution share exceeds 70 %. `low_confidence_bucket` fires when bootstrap-resampled `anomaly_confidence` falls below 0.5. Both computed at flag time, no extra MCP roundtrip.
102
+
103
+ **Tests.** `tests/test_reliability_flags.py`.
104
+
105
+ ---
106
+
107
+ ## Build-time dim quality auditing — silent calibration failure modes surfaced
108
+
109
+ **What it is.** Five build-time auditors catch the silent failure modes that break z-score / `delta_norm` semantics: dead dims (zero variance), sparse dims (mostly zero), dominant-dim mass (one dim accounts for ≥70 % of tail variance), negative-space dims (gaussian-declared but empirically zero-modal), non-normal dims (gaussian-declared but Shapiro-Wilk / KS `p < 0.01`), and per-pattern heteroscedasticity (Brown-Forsythe Levene `p < 0.01` on the grouping variable).
110
+
111
+ **Surfaces.** `sphere_overview.dim_quality_warnings[]` block. `engine.diagnostics` and `engine.dim_audit` modules.
112
+
113
+ **Method.** Build-time statistical tests on the calibrated geometry table. Each auditor returns a structured warning with `dim_label`, `reason`, `advice`, `evidence_value`, `threshold` — enough for a downstream consumer to either raise a calibration ticket or apply the recommended transform.
114
+
115
+ **Tests.** `tests/test_dim_quality_warnings.py`, `tests/test_diagnostics.py`.
116
+
117
+ ---
118
+
119
+ ## Notes
120
+
121
+ This document is a living artefact. As new architectural surfaces ship, they are added here. As surfaces are deprecated or refactored, the entries update with the cycle they shipped in.
122
+
123
+ Internal tracker IDs (`IC-XX`, `DC-XX.Y`, `OPF-XX`, `IDEA-XXX`) are kept out of this document by project rule — they are private bookkeeping and would look like dead hyperlinks to anyone reading from the public mirror. Each cluster is named by what it does, not by which ticket tracks it.
@@ -0,0 +1,71 @@
1
+ # Refutations
2
+
3
+ This document records hypotheses that were tested, found wrong, and closed. Each entry follows the discipline: state the hypothesis, name the dataset, give the numeric evidence, and link the commit or PR that closed the line of work.
4
+
5
+ The point is not to celebrate failures. It is to leave a record so the same idea is not re-investigated six months later under a different name, and so a reviewer can see that the project takes negative results seriously.
6
+
7
+ ---
8
+
9
+ ## Pattern-level σ-estimator swap (refuted 0.6.6, PR #435)
10
+
11
+ **Hypothesis.** Replacing the population standard deviation σ used in `delta_norm` calibration with a robust estimator (MAD, IQR, Qn) would lift downstream anomaly detection by being less sensitive to outlier leak.
12
+
13
+ **What was tested.** Four σ estimators (`std`, `mad`, `iqr`, `qn`) plugged into `Pattern.sigma_diag` calibration. Evaluation on a money-laundering account-level pattern with binary `is_laundering` labels.
14
+
15
+ **Verdict.** AUROC and top-K precision differences across the four estimators sat inside the bootstrap noise band (±0.005 on AUROC). MAD and IQR did not deliver any robustness lift on this distribution. The "robust σ" line of work was closed.
16
+
17
+ **Why it failed.** Account-level features are already log-transformed or rank-normalised upstream of calibration. Outliers no longer drive σ when the input is approximately normal post-transform. The σ-swap intervention had no surface to act on.
18
+
19
+ **Citation.** PR #435 retrospective + `MEMORY.md` entry `project_population_robust_sigma_refuted`.
20
+
21
+ ---
22
+
23
+ ## Per-dim heuristic σ-estimator swap (refuted 0.6.6, PR #436)
24
+
25
+ **Hypothesis.** σ-swap might work if applied per-dim rather than population-wide — pick the estimator per column based on a heuristic (skewness, kurtosis, sparsity).
26
+
27
+ **What was tested.** Per-dim heuristic dispatcher selecting `std` / `mad` / `iqr` based on column statistics. Same evaluation setup as PR #435.
28
+
29
+ **Verdict.** Same outcome — ΔAUROC < 0.005, inside bootstrap noise. The heuristic dispatcher added complexity without measurable lift.
30
+
31
+ **Why it failed.** Same root cause as PR #435 — when upstream features are normalised, σ-estimator choice does not materially change the calibration. The dispatcher was solving a problem that did not exist on this data shape.
32
+
33
+ **Closure rule.** The entire "calibration robustness via σ-estimator swap" theme is closed for the project unless a new dataset surfaces with un-normalised, heavy-tailed inputs where the assumption is testable from scratch. The closure rule is enforced via the session memory `project_population_robust_sigma_refuted` — re-opening requires a different dataset or methodology.
34
+
35
+ ---
36
+
37
+ ## Lazy-chain geometry sampled calibration (refuted 0.5.x, retired 2026-04-19)
38
+
39
+ **Hypothesis.** Computing chain-pattern calibration on a sampled subset of chains rather than the full population would scale linearly with sphere size and preserve detection quality.
40
+
41
+ **What was tested.** Sampled-calibration variant against full-calibration baseline on the money-laundering chain pattern. Compared `delta_norm` rankings and downstream chain-level anomaly recall.
42
+
43
+ **Verdict.** Sampled calibration produced a sub-null `delta_norm` distribution — calibration estimates were biased low because the sampled tail underrepresented the true variance. Bias roughly 24 % on the most affected dims. Chain recall on labelled bad chains dropped materially.
44
+
45
+ **Why it failed.** Chains are a long-tail distribution by structure. Uniform sampling under-weights long chains, which carry the discriminating signal. A weighted sampler would have helped in theory but the engineering cost outweighed the expected gain.
46
+
47
+ **Closure.** The lazy-chain-geometry document carries an in-doc retired banner dated 2026-04-19. The work was superseded by treating chains as first-class geometric entities (the `chain_lines:` YAML block and the chain investigation primitives shipped in 0.6.x and 0.7.0).
48
+
49
+ ---
50
+
51
+ ## Bregman-based `is_anomaly` flag (refuted 2026-04-16)
52
+
53
+ **Hypothesis.** Replacing the Mahalanobis-distance-based anomaly flag with a Bregman-divergence-based flag would lift top-K precision on money-laundering account-level detection by giving exp-family-correct distance semantics for non-gaussian dims (counts, ratios).
54
+
55
+ **What was tested.** Bregman-flag variant evaluated against Mahalanobis baseline at K ∈ {50, 100, 200, 500}, top-K precision and recall on labelled laundering accounts.
56
+
57
+ **Verdict.** ΔF1 ≤ +0.005 across all K values — inside bootstrap noise. The Bregman variant did not deliver measurable lift on this data shape.
58
+
59
+ **Why it failed.** The dimension-kind tags in production are mostly `gaussian` or `mahalanobis` with a few `bernoulli` flags. Bregman's correctness advantage only matters when a substantial fraction of dims are explicitly `poisson` or `bernoulli` and carry top-of-rank load. On the tested pattern, no such dim dominated.
60
+
61
+ **Closure.** The `is_anomaly` flag stays Mahalanobis-based. Bregman remains a per-dim choice via `dimension_kinds: poisson` / `bernoulli` declarations but is not promoted to the global flag computation. Tracked as a closed direction in session memory.
62
+
63
+ ---
64
+
65
+ ## Process refutation — emergent cycle-compression (0.7.0 retrospective)
66
+
67
+ This one is not algorithmic. The 0.7.0 cycle landed five PRs in a 24-hour window, four of which had been originally planned as 0.7.1 milestones. The result was a tag that shipped on the planned date but with a release-prep state that drifted across packages.
68
+
69
+ **Lesson.** Items that emerge during a cycle and feel ready to ship should not skip cycle boundaries unless the truth-in-CHANGELOG cost of leaving them is concrete. The hard cycle-window rule is enforced going forward: feature PRs land outside the seven-day pre-tag window; the release-prep PR is the literal last PR before the tag; emerged-during-execution items defer to the next cycle by default.
70
+
71
+ **Why this entry exists.** Methodology refutations are as load-bearing as algorithmic ones — they keep the cycle cadence honest. The 0.7.1 cycle deliberately sized smaller to test the rule under real conditions.
@@ -44,4 +44,4 @@ __all__ = [
44
44
  "IntrinsicExtrinsicReport",
45
45
  "LeadLagReport",
46
46
  ]
47
- __version__ = "0.7.1"
47
+ __version__ = "0.7.2"
@@ -10,6 +10,7 @@ Used for: money laundering chain detection, supply chain path analysis.
10
10
  from __future__ import annotations
11
11
 
12
12
  import contextlib
13
+ import math
13
14
  from collections import defaultdict
14
15
  from dataclasses import dataclass
15
16
  from datetime import datetime
@@ -173,7 +174,23 @@ class Chain:
173
174
  for i in range(len(self.amounts) - 1)
174
175
  )
175
176
 
176
- def to_dict(self) -> dict:
177
+ def to_dict(
178
+ self,
179
+ *,
180
+ delta_by_key: dict[str, np.ndarray] | None = None,
181
+ ) -> dict:
182
+ """Serialise the chain to a JSON-safe dict.
183
+
184
+ ``delta_by_key`` is an optional pre-fetched mapping from entity key
185
+ to that entity's polygon delta vector against the chain's anchor
186
+ pattern. When supplied, the output gains an ``edge_potentials``
187
+ field — one Euclidean distance ``||delta(keys[i]) - delta(keys[i+1])||``
188
+ per consecutive-pair hop. Element is ``None`` when either endpoint
189
+ lacks a delta entry, the vectors have mismatched or zero length,
190
+ or the resulting distance is non-finite (NaN / inf — strict-JSON
191
+ sanitised). When ``delta_by_key`` is None, ``edge_potentials`` is
192
+ a list of ``None`` of the appropriate length.
193
+ """
177
194
  amt_std = float(np.std(self.amounts)) if self.amounts else 0.0
178
195
  amt_mean = float(np.mean(self.amounts)) if self.amounts else 0.0
179
196
  return {
@@ -196,9 +213,51 @@ class Chain:
196
213
  "amount_monotone_decreasing": (
197
214
  self.amount_monotone_decreasing()
198
215
  ),
216
+ "edge_potentials": _compute_edge_potentials(
217
+ self.keys, delta_by_key,
218
+ ),
199
219
  }
200
220
 
201
221
 
222
+ def _compute_edge_potentials(
223
+ keys: list[str],
224
+ delta_by_key: dict[str, np.ndarray] | None,
225
+ ) -> list[float | None]:
226
+ """Euclidean distance ``||delta(keys[i]) - delta(keys[i+1])||`` per hop.
227
+
228
+ Length is ``max(0, len(keys) - 1)``. Element is ``None`` when:
229
+ - ``delta_by_key`` is None (the caller did not pre-fetch deltas);
230
+ - either endpoint's key is missing from ``delta_by_key``;
231
+ - the two delta vectors have mismatched or zero length;
232
+ - the computed distance is not finite (NaN / inf).
233
+
234
+ NaN / inf protection keeps the result strict-JSON-safe: any non-finite
235
+ distance is replaced by ``None`` before serialisation.
236
+ """
237
+ n_hops = max(0, len(keys) - 1)
238
+ if n_hops == 0:
239
+ return []
240
+ if delta_by_key is None:
241
+ return [None] * n_hops
242
+
243
+ out: list[float | None] = []
244
+ for i in range(n_hops):
245
+ a = delta_by_key.get(keys[i])
246
+ b = delta_by_key.get(keys[i + 1])
247
+ if a is None or b is None:
248
+ out.append(None)
249
+ continue
250
+ if a.shape != b.shape or a.size == 0:
251
+ out.append(None)
252
+ continue
253
+ d = float(np.linalg.norm(a - b))
254
+ if not math.isfinite(d):
255
+ out.append(None)
256
+ continue
257
+ out.append(d)
258
+ return out
259
+
260
+
202
261
  def extract_chains(
203
262
  from_keys: list[str],
204
263
  to_keys: list[str],
@@ -26,6 +26,7 @@ __all__ = [
26
26
  "compute_per_edge_source_value_pvalues",
27
27
  "simulate_joint_edge_removal",
28
28
  "select_minimal_joint_removal",
29
+ "recompute_delta_norm_against_frozen",
29
30
  ]
30
31
 
31
32
 
@@ -823,3 +824,46 @@ def select_minimal_joint_removal(
823
824
  "target_reached": target_reached,
824
825
  "k_max_reached": k_max_reached,
825
826
  }
827
+
828
+
829
+ def recompute_delta_norm_against_frozen(
830
+ *,
831
+ shape: np.ndarray,
832
+ mu_frozen: np.ndarray,
833
+ sigma: np.ndarray,
834
+ ) -> float:
835
+ """L2 norm of ``shape`` normalised against a frozen reference ``mu_frozen``.
836
+
837
+ Frozen-population trajectory primitive used by
838
+ ``GDSEngine.build_solid(counterfactual_frozen_population=True)``: for each
839
+ solid slice, recompute ``delta_norm`` using the FIRST slice's raw shape
840
+ (``mu_frozen``) as the population reference and the current pattern's
841
+ diagonal sigma as the scale. Sigma-dead dims (``sigma < 1e-10``) contribute
842
+ zero — same convention as the per-edge counterfactual primitives.
843
+
844
+ Args:
845
+ shape: per-slice raw shape vector, shape (D,).
846
+ mu_frozen: frozen reference mean (e.g. slice-0's raw shape), shape (D,).
847
+ sigma: current pattern's diagonal sigma, shape (D,).
848
+
849
+ Returns:
850
+ Float L2 norm. Returns ``0.0`` when ``shape == mu_frozen`` (stationary
851
+ entity in a drifting population) — the canonical signal that the
852
+ entity's apparent normalisation came from population drift, not
853
+ entity movement.
854
+ """
855
+ if shape.shape != mu_frozen.shape or shape.shape != sigma.shape:
856
+ raise ValueError(
857
+ f"shape / mu_frozen / sigma must have matching length; got "
858
+ f"{shape.shape} / {mu_frozen.shape} / {sigma.shape}",
859
+ )
860
+ shape = shape.astype(np.float64, copy=False)
861
+ mu_frozen = mu_frozen.astype(np.float64, copy=False)
862
+ sigma = sigma.astype(np.float64, copy=False)
863
+ sigma_dead_mask = sigma < _SIGMA_ZERO_FLOOR
864
+ sigma_safe = np.where(sigma_dead_mask, 1.0, sigma)
865
+ delta = np.where(sigma_dead_mask, 0.0, (shape - mu_frozen) / sigma_safe)
866
+ val = float(np.linalg.norm(delta))
867
+ if not np.isfinite(val):
868
+ return 0.0
869
+ return val
@@ -201,6 +201,7 @@ class GDSEngine:
201
201
  manifest: Manifest,
202
202
  filters: dict[str, str | list[str]] | None = None,
203
203
  timestamp: datetime | None = None,
204
+ counterfactual_frozen_population: bool = False,
204
205
  ) -> Solid:
205
206
  base = self.build_polygon(primary_key, pattern_id, manifest)
206
207
  table = self._storage.read_temporal(
@@ -216,6 +217,11 @@ class GDSEngine:
216
217
  pattern = sphere.patterns[pattern_id]
217
218
  sigma = np.maximum(pattern.sigma_diag, self.SIGMA_EPSILON)
218
219
  slices: list[SolidSlice] = []
220
+ # When counterfactual_frozen_population is requested, capture each
221
+ # slice's raw shape_snapshot so the frozen-trajectory recompute can
222
+ # run after the loop using the chronologically-first slice's shape
223
+ # as the entity-relative reference.
224
+ raw_shapes: list[np.ndarray] = []
219
225
  for i in range(table.num_rows):
220
226
  row = {col: table[col][i].as_py() for col in table.schema.names}
221
227
  shape = np.array(row["shape_snapshot"], dtype=np.float32)
@@ -241,11 +247,45 @@ class GDSEngine:
241
247
  changed_line_id=row.get("changed_line_id"),
242
248
  added_edge=None,
243
249
  ))
244
- slices.sort(key=lambda s: s.timestamp)
250
+ if counterfactual_frozen_population:
251
+ raw_shapes.append(shape)
252
+ # Pair raw_shapes with their slice and sort jointly so the
253
+ # chronologically-first slice's raw shape becomes the frozen
254
+ # reference even when temporal rows arrive out of order.
255
+ if counterfactual_frozen_population and slices:
256
+ paired = sorted(
257
+ zip(slices, raw_shapes, strict=True),
258
+ key=lambda p: p[0].timestamp,
259
+ )
260
+ slices = [p[0] for p in paired]
261
+ raw_shapes = [p[1] for p in paired]
262
+ else:
263
+ slices.sort(key=lambda s: s.timestamp)
245
264
  if timestamp is not None:
246
265
  if timestamp.tzinfo is None:
247
266
  timestamp = timestamp.replace(tzinfo=UTC)
248
- slices = [s for s in slices if s.timestamp <= timestamp]
267
+ if counterfactual_frozen_population:
268
+ surviving = [
269
+ (s, r) for s, r in zip(slices, raw_shapes, strict=True)
270
+ if s.timestamp <= timestamp
271
+ ]
272
+ slices = [p[0] for p in surviving]
273
+ raw_shapes = [p[1] for p in surviving]
274
+ else:
275
+ slices = [s for s in slices if s.timestamp <= timestamp]
276
+ if counterfactual_frozen_population and slices:
277
+ from hypertopos.engine.counterfactual import (
278
+ recompute_delta_norm_against_frozen,
279
+ )
280
+ mu_frozen = raw_shapes[0]
281
+ _w = mu_frozen.shape[-1]
282
+ sigma_frozen = sigma[:_w]
283
+ for slc, raw in zip(slices, raw_shapes, strict=True):
284
+ slc.delta_norm_frozen_pop = recompute_delta_norm_against_frozen(
285
+ shape=raw,
286
+ mu_frozen=mu_frozen,
287
+ sigma=sigma_frozen,
288
+ )
249
289
  return Solid(primary_key=primary_key, pattern_id=pattern_id,
250
290
  base_polygon=base, slices=slices)
251
291
 
@@ -87,6 +87,13 @@ class SolidSlice:
87
87
  changed_property: str | None
88
88
  changed_line_id: str | None
89
89
  added_edge: Edge | None
90
+ # Counterfactual frozen-population delta_norm. Populated only when the
91
+ # owning solid was built with ``counterfactual_frozen_population=True`` —
92
+ # in that case each slice's value is the L2 norm of the per-slice shape
93
+ # normalised against the FIRST slice's raw shape (treated as the
94
+ # entity-relative reference epoch) instead of the current pattern mu.
95
+ # ``None`` for the default path preserves the existing serialised shape.
96
+ delta_norm_frozen_pop: float | None = None
90
97
 
91
98
  def prop_column_states(self, pattern: Pattern) -> dict[str, bool]:
92
99
  """Inverse z-score: recover boolean property states from delta_snapshot."""