margin 0.9.17__tar.gz → 0.9.18__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.

Potentially problematic release.


This version of margin might be problematic. Click here for more details.

Files changed (88) hide show
  1. {margin-0.9.17/margin.egg-info → margin-0.9.18}/PKG-INFO +1 -1
  2. {margin-0.9.17 → margin-0.9.18}/margin/fingerprint.py +16 -8
  3. {margin-0.9.17 → margin-0.9.18/margin.egg-info}/PKG-INFO +1 -1
  4. {margin-0.9.17 → margin-0.9.18}/pyproject.toml +1 -1
  5. {margin-0.9.17 → margin-0.9.18}/tests/test_controller.py +16 -2
  6. {margin-0.9.17 → margin-0.9.18}/LICENSE +0 -0
  7. {margin-0.9.17 → margin-0.9.18}/NOTICE +0 -0
  8. {margin-0.9.17 → margin-0.9.18}/README.md +0 -0
  9. {margin-0.9.17 → margin-0.9.18}/margin/__init__.py +0 -0
  10. {margin-0.9.17 → margin-0.9.18}/margin/__main__.py +0 -0
  11. {margin-0.9.17 → margin-0.9.18}/margin/algebra.py +0 -0
  12. {margin-0.9.17 → margin-0.9.18}/margin/anomaly.py +0 -0
  13. {margin-0.9.17 → margin-0.9.18}/margin/bridge.py +0 -0
  14. {margin-0.9.17 → margin-0.9.18}/margin/calibrate.py +0 -0
  15. {margin-0.9.17 → margin-0.9.18}/margin/causal.py +0 -0
  16. {margin-0.9.17 → margin-0.9.18}/margin/composite.py +0 -0
  17. {margin-0.9.17 → margin-0.9.18}/margin/confidence.py +0 -0
  18. {margin-0.9.17 → margin-0.9.18}/margin/config.py +0 -0
  19. {margin-0.9.17 → margin-0.9.18}/margin/contract.py +0 -0
  20. {margin-0.9.17 → margin-0.9.18}/margin/controller.py +0 -0
  21. {margin-0.9.17 → margin-0.9.18}/margin/correlate.py +0 -0
  22. {margin-0.9.17 → margin-0.9.18}/margin/diff.py +0 -0
  23. {margin-0.9.17 → margin-0.9.18}/margin/drift.py +0 -0
  24. {margin-0.9.17 → margin-0.9.18}/margin/events.py +0 -0
  25. {margin-0.9.17 → margin-0.9.18}/margin/forecast.py +0 -0
  26. {margin-0.9.17 → margin-0.9.18}/margin/health.py +0 -0
  27. {margin-0.9.17 → margin-0.9.18}/margin/intent.py +0 -0
  28. {margin-0.9.17 → margin-0.9.18}/margin/ledger.py +0 -0
  29. {margin-0.9.17 → margin-0.9.18}/margin/loop.py +0 -0
  30. {margin-0.9.17 → margin-0.9.18}/margin/observation.py +0 -0
  31. {margin-0.9.17 → margin-0.9.18}/margin/persist.py +0 -0
  32. {margin-0.9.17 → margin-0.9.18}/margin/policy/__init__.py +0 -0
  33. {margin-0.9.17 → margin-0.9.18}/margin/policy/compose.py +0 -0
  34. {margin-0.9.17 → margin-0.9.18}/margin/policy/core.py +0 -0
  35. {margin-0.9.17 → margin-0.9.18}/margin/policy/temporal.py +0 -0
  36. {margin-0.9.17 → margin-0.9.18}/margin/policy/trace.py +0 -0
  37. {margin-0.9.17 → margin-0.9.18}/margin/policy/tuning.py +0 -0
  38. {margin-0.9.17 → margin-0.9.18}/margin/policy/validate.py +0 -0
  39. {margin-0.9.17 → margin-0.9.18}/margin/predicates.py +0 -0
  40. {margin-0.9.17 → margin-0.9.18}/margin/provenance.py +0 -0
  41. {margin-0.9.17 → margin-0.9.18}/margin/streaming.py +0 -0
  42. {margin-0.9.17 → margin-0.9.18}/margin/transitions.py +0 -0
  43. {margin-0.9.17 → margin-0.9.18}/margin/uncertain.py +0 -0
  44. {margin-0.9.17 → margin-0.9.18}/margin/validity.py +0 -0
  45. {margin-0.9.17 → margin-0.9.18}/margin.egg-info/SOURCES.txt +0 -0
  46. {margin-0.9.17 → margin-0.9.18}/margin.egg-info/dependency_links.txt +0 -0
  47. {margin-0.9.17 → margin-0.9.18}/margin.egg-info/top_level.txt +0 -0
  48. {margin-0.9.17 → margin-0.9.18}/setup.cfg +0 -0
  49. {margin-0.9.17 → margin-0.9.18}/tests/test_algebra.py +0 -0
  50. {margin-0.9.17 → margin-0.9.18}/tests/test_anomaly.py +0 -0
  51. {margin-0.9.17 → margin-0.9.18}/tests/test_bridge.py +0 -0
  52. {margin-0.9.17 → margin-0.9.18}/tests/test_calibrate.py +0 -0
  53. {margin-0.9.17 → margin-0.9.18}/tests/test_causal.py +0 -0
  54. {margin-0.9.17 → margin-0.9.18}/tests/test_composite.py +0 -0
  55. {margin-0.9.17 → margin-0.9.18}/tests/test_confidence.py +0 -0
  56. {margin-0.9.17 → margin-0.9.18}/tests/test_config.py +0 -0
  57. {margin-0.9.17 → margin-0.9.18}/tests/test_contract.py +0 -0
  58. {margin-0.9.17 → margin-0.9.18}/tests/test_correlate.py +0 -0
  59. {margin-0.9.17 → margin-0.9.18}/tests/test_diff.py +0 -0
  60. {margin-0.9.17 → margin-0.9.18}/tests/test_drift.py +0 -0
  61. {margin-0.9.17 → margin-0.9.18}/tests/test_events.py +0 -0
  62. {margin-0.9.17 → margin-0.9.18}/tests/test_forecast.py +0 -0
  63. {margin-0.9.17 → margin-0.9.18}/tests/test_health.py +0 -0
  64. {margin-0.9.17 → margin-0.9.18}/tests/test_healthcare.py +0 -0
  65. {margin-0.9.17 → margin-0.9.18}/tests/test_intent.py +0 -0
  66. {margin-0.9.17 → margin-0.9.18}/tests/test_ledger.py +0 -0
  67. {margin-0.9.17 → margin-0.9.18}/tests/test_loop.py +0 -0
  68. {margin-0.9.17 → margin-0.9.18}/tests/test_neuro.py +0 -0
  69. {margin-0.9.17 → margin-0.9.18}/tests/test_observation.py +0 -0
  70. {margin-0.9.17 → margin-0.9.18}/tests/test_persist.py +0 -0
  71. {margin-0.9.17 → margin-0.9.18}/tests/test_policy.py +0 -0
  72. {margin-0.9.17 → margin-0.9.18}/tests/test_policy_compose.py +0 -0
  73. {margin-0.9.17 → margin-0.9.18}/tests/test_policy_temporal.py +0 -0
  74. {margin-0.9.17 → margin-0.9.18}/tests/test_policy_trace.py +0 -0
  75. {margin-0.9.17 → margin-0.9.18}/tests/test_policy_tuning.py +0 -0
  76. {margin-0.9.17 → margin-0.9.18}/tests/test_policy_validate.py +0 -0
  77. {margin-0.9.17 → margin-0.9.18}/tests/test_predicates.py +0 -0
  78. {margin-0.9.17 → margin-0.9.18}/tests/test_printer3d.py +0 -0
  79. {margin-0.9.17 → margin-0.9.18}/tests/test_provenance.py +0 -0
  80. {margin-0.9.17 → margin-0.9.18}/tests/test_reverse_bridge.py +0 -0
  81. {margin-0.9.17 → margin-0.9.18}/tests/test_ros2.py +0 -0
  82. {margin-0.9.17 → margin-0.9.18}/tests/test_staleness.py +0 -0
  83. {margin-0.9.17 → margin-0.9.18}/tests/test_streaming.py +0 -0
  84. {margin-0.9.17 → margin-0.9.18}/tests/test_transformer.py +0 -0
  85. {margin-0.9.17 → margin-0.9.18}/tests/test_transitions.py +0 -0
  86. {margin-0.9.17 → margin-0.9.18}/tests/test_uncertain.py +0 -0
  87. {margin-0.9.17 → margin-0.9.18}/tests/test_validity.py +0 -0
  88. {margin-0.9.17 → margin-0.9.18}/tests/test_windowing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: margin
3
- Version: 0.9.17
3
+ Version: 0.9.18
4
4
  Summary: Typed health classification, uncertainty algebra, and correction auditing for any system that measures things and needs to explain what happened.
5
5
  Author: Cope Labs LLC
6
6
  License: MIT
@@ -126,23 +126,31 @@ class Fingerprint(dict):
126
126
 
127
127
  def sigma(self, component: str, value: float) -> float:
128
128
  """
129
- Normalize ``value`` against the fingerprint's empirical mean.
129
+ Z-score of ``value`` against the fingerprint's empirical distribution.
130
130
 
131
- Returns ``(value − mean) / |mean|``.
131
+ Returns ``(value − mean) / std`` (standard z-score).
132
132
 
133
- Positive = above session baseline; negative = below. Used to
134
- convert a raw domain measurement into a cross-session comparable
135
- signal before passing it to ``Controller.step()``.
133
+ Using ``std`` as denominator is correct for cross-session normalization.
134
+ ``|mean|`` would only be appropriate when the coefficient of variation
135
+ (std/|mean|) is small; for high-CV components (e.g. ``std=0.498``,
136
+ ``mean=0.070``, CV=7.1) it amplifies by 7×, turning a normal ±0.07
137
+ swing into ±1.0 and saturating the controller.
136
138
 
137
- Returns ``value`` unchanged when the stored mean is zero
138
- (no finite normalization is possible).
139
+ Falls back to ``(value − mean) / |mean|`` when ``std`` is zero (constant
140
+ window or not yet stored), and returns ``value`` unchanged when both
141
+ ``mean`` and ``std`` are zero.
139
142
 
140
143
  Example::
141
144
 
142
145
  metric = fp.sigma("recovery_ratio", cq.recovery_ratio)
143
146
  alpha, reason = ctrl.step(alpha, metric)
144
147
  """
145
- mean = self.get(component, {}).get("mean", 0.0)
148
+ stats = self.get(component, {})
149
+ mean = stats.get("mean", 0.0)
150
+ std = stats.get("std", 0.0)
151
+ if std != 0.0:
152
+ return (value - mean) / std
153
+ # std=0: constant distribution — fall back to relative deviation
146
154
  if mean == 0.0:
147
155
  return value
148
156
  return (value - mean) / abs(mean)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: margin
3
- Version: 0.9.17
3
+ Version: 0.9.18
4
4
  Summary: Typed health classification, uncertainty algebra, and correction auditing for any system that measures things and needs to explain what happened.
5
5
  Author: Cope Labs LLC
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "margin"
7
- version = "0.9.17"
7
+ version = "0.9.18"
8
8
  description = "Typed health classification, uncertainty algebra, and correction auditing for any system that measures things and needs to explain what happened."
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -552,14 +552,28 @@ class TestFingerprintSigma:
552
552
 
553
553
  def test_negative_mean(self):
554
554
  fp = Fingerprint(stats={"rr": {"mean": -2.0, "std": 0.5, "n": 10, "trend": "STABLE"}})
555
- # (value - mean) / |mean| = (-1.0 - (-2.0)) / 2.0 = 0.5
556
- assert fp.sigma("rr", -1.0) == pytest.approx(0.5)
555
+ # z-score: (value - mean) / std = (-1.0 - (-2.0)) / 0.5 = 2.0
556
+ assert fp.sigma("rr", -1.0) == pytest.approx(2.0)
557
557
 
558
558
  def test_missing_component_returns_value(self):
559
559
  fp = _make_fp([1.0] * 10)
560
560
  # missing → mean=0.0 → returns value unchanged
561
561
  assert fp.sigma("missing", 0.7) == pytest.approx(0.7)
562
562
 
563
+ def test_uses_std_not_mean_as_denominator(self):
564
+ # Regression: Build #29 failure — high-CV component (std=0.498, mean=0.070)
565
+ # Old formula: (value - mean) / |mean| = 0.07 / 0.07 = 1.0 (7× amplification)
566
+ # New formula: (value - mean) / std = 0.07 / 0.498 ≈ 0.141 (sensible z-score)
567
+ fp = Fingerprint(stats={"rr": {"mean": 0.070, "std": 0.498, "n": 98}})
568
+ result = fp.sigma("rr", 0.14)
569
+ assert result == pytest.approx((0.14 - 0.070) / 0.498, rel=1e-4) # z-score
570
+ assert abs(result) < 0.2 # not ±1.0 (the bang-bang range)
571
+
572
+ def test_std_zero_falls_back_to_mean_relative(self):
573
+ # Constant distribution: std=0 → fall back to (value-mean)/|mean|
574
+ fp = Fingerprint(stats={"rr": {"mean": 1.0, "std": 0.0, "n": 5}})
575
+ assert fp.sigma("rr", 1.5) == pytest.approx(0.5) # (1.5-1.0)/1.0
576
+
563
577
 
564
578
  class TestFingerprintPrecomputedPercentiles:
565
579
  """Fingerprints from Monitor.fingerprint() now include median/q25/q75.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes