margin 0.9.26__tar.gz → 0.9.28__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 (94) hide show
  1. {margin-0.9.26/margin.egg-info → margin-0.9.28}/PKG-INFO +20 -20
  2. {margin-0.9.26 → margin-0.9.28}/README.md +14 -14
  3. {margin-0.9.26 → margin-0.9.28}/margin/__init__.py +5 -0
  4. margin-0.9.28/margin/flow.py +390 -0
  5. {margin-0.9.26 → margin-0.9.28/margin.egg-info}/PKG-INFO +20 -20
  6. {margin-0.9.26 → margin-0.9.28}/margin.egg-info/SOURCES.txt +2 -0
  7. {margin-0.9.26 → margin-0.9.28}/pyproject.toml +6 -6
  8. margin-0.9.28/tests/test_flow.py +418 -0
  9. {margin-0.9.26 → margin-0.9.28}/LICENSE +0 -0
  10. {margin-0.9.26 → margin-0.9.28}/NOTICE +0 -0
  11. {margin-0.9.26 → margin-0.9.28}/margin/__main__.py +0 -0
  12. {margin-0.9.26 → margin-0.9.28}/margin/algebra.py +0 -0
  13. {margin-0.9.26 → margin-0.9.28}/margin/anomaly.py +0 -0
  14. {margin-0.9.26 → margin-0.9.28}/margin/bridge.py +0 -0
  15. {margin-0.9.26 → margin-0.9.28}/margin/calibrate.py +0 -0
  16. {margin-0.9.26 → margin-0.9.28}/margin/causal.py +0 -0
  17. {margin-0.9.26 → margin-0.9.28}/margin/composite.py +0 -0
  18. {margin-0.9.26 → margin-0.9.28}/margin/confidence.py +0 -0
  19. {margin-0.9.26 → margin-0.9.28}/margin/config.py +0 -0
  20. {margin-0.9.26 → margin-0.9.28}/margin/contract.py +0 -0
  21. {margin-0.9.26 → margin-0.9.28}/margin/controller.py +0 -0
  22. {margin-0.9.26 → margin-0.9.28}/margin/correlate.py +0 -0
  23. {margin-0.9.26 → margin-0.9.28}/margin/diff.py +0 -0
  24. {margin-0.9.26 → margin-0.9.28}/margin/drift.py +0 -0
  25. {margin-0.9.26 → margin-0.9.28}/margin/events.py +0 -0
  26. {margin-0.9.26 → margin-0.9.28}/margin/fingerprint.py +0 -0
  27. {margin-0.9.26 → margin-0.9.28}/margin/forecast.py +0 -0
  28. {margin-0.9.26 → margin-0.9.28}/margin/health.py +0 -0
  29. {margin-0.9.26 → margin-0.9.28}/margin/intent.py +0 -0
  30. {margin-0.9.26 → margin-0.9.28}/margin/ledger.py +0 -0
  31. {margin-0.9.26 → margin-0.9.28}/margin/loop.py +0 -0
  32. {margin-0.9.26 → margin-0.9.28}/margin/observation.py +0 -0
  33. {margin-0.9.26 → margin-0.9.28}/margin/persist.py +0 -0
  34. {margin-0.9.26 → margin-0.9.28}/margin/policy/__init__.py +0 -0
  35. {margin-0.9.26 → margin-0.9.28}/margin/policy/compose.py +0 -0
  36. {margin-0.9.26 → margin-0.9.28}/margin/policy/core.py +0 -0
  37. {margin-0.9.26 → margin-0.9.28}/margin/policy/temporal.py +0 -0
  38. {margin-0.9.26 → margin-0.9.28}/margin/policy/trace.py +0 -0
  39. {margin-0.9.26 → margin-0.9.28}/margin/policy/tuning.py +0 -0
  40. {margin-0.9.26 → margin-0.9.28}/margin/policy/validate.py +0 -0
  41. {margin-0.9.26 → margin-0.9.28}/margin/predicates.py +0 -0
  42. {margin-0.9.26 → margin-0.9.28}/margin/provenance.py +0 -0
  43. {margin-0.9.26 → margin-0.9.28}/margin/streaming.py +0 -0
  44. {margin-0.9.26 → margin-0.9.28}/margin/transitions.py +0 -0
  45. {margin-0.9.26 → margin-0.9.28}/margin/uncertain.py +0 -0
  46. {margin-0.9.26 → margin-0.9.28}/margin/validity.py +0 -0
  47. {margin-0.9.26 → margin-0.9.28}/margin.egg-info/dependency_links.txt +0 -0
  48. {margin-0.9.26 → margin-0.9.28}/margin.egg-info/top_level.txt +0 -0
  49. {margin-0.9.26 → margin-0.9.28}/setup.cfg +0 -0
  50. {margin-0.9.26 → margin-0.9.28}/tests/test_absence.py +0 -0
  51. {margin-0.9.26 → margin-0.9.28}/tests/test_algebra.py +0 -0
  52. {margin-0.9.26 → margin-0.9.28}/tests/test_anomaly.py +0 -0
  53. {margin-0.9.26 → margin-0.9.28}/tests/test_bridge.py +0 -0
  54. {margin-0.9.26 → margin-0.9.28}/tests/test_calibrate.py +0 -0
  55. {margin-0.9.26 → margin-0.9.28}/tests/test_causal.py +0 -0
  56. {margin-0.9.26 → margin-0.9.28}/tests/test_composite.py +0 -0
  57. {margin-0.9.26 → margin-0.9.28}/tests/test_confidence.py +0 -0
  58. {margin-0.9.26 → margin-0.9.28}/tests/test_config.py +0 -0
  59. {margin-0.9.26 → margin-0.9.28}/tests/test_contract.py +0 -0
  60. {margin-0.9.26 → margin-0.9.28}/tests/test_controller.py +0 -0
  61. {margin-0.9.26 → margin-0.9.28}/tests/test_correlate.py +0 -0
  62. {margin-0.9.26 → margin-0.9.28}/tests/test_diff.py +0 -0
  63. {margin-0.9.26 → margin-0.9.28}/tests/test_drift.py +0 -0
  64. {margin-0.9.26 → margin-0.9.28}/tests/test_events.py +0 -0
  65. {margin-0.9.26 → margin-0.9.28}/tests/test_fingerprint_ext.py +0 -0
  66. {margin-0.9.26 → margin-0.9.28}/tests/test_float_tolerance.py +0 -0
  67. {margin-0.9.26 → margin-0.9.28}/tests/test_forecast.py +0 -0
  68. {margin-0.9.26 → margin-0.9.28}/tests/test_health.py +0 -0
  69. {margin-0.9.26 → margin-0.9.28}/tests/test_healthcare.py +0 -0
  70. {margin-0.9.26 → margin-0.9.28}/tests/test_intent.py +0 -0
  71. {margin-0.9.26 → margin-0.9.28}/tests/test_ledger.py +0 -0
  72. {margin-0.9.26 → margin-0.9.28}/tests/test_loop.py +0 -0
  73. {margin-0.9.26 → margin-0.9.28}/tests/test_neuro.py +0 -0
  74. {margin-0.9.26 → margin-0.9.28}/tests/test_observation.py +0 -0
  75. {margin-0.9.26 → margin-0.9.28}/tests/test_persist.py +0 -0
  76. {margin-0.9.26 → margin-0.9.28}/tests/test_pipeline_features.py +0 -0
  77. {margin-0.9.26 → margin-0.9.28}/tests/test_policy.py +0 -0
  78. {margin-0.9.26 → margin-0.9.28}/tests/test_policy_compose.py +0 -0
  79. {margin-0.9.26 → margin-0.9.28}/tests/test_policy_temporal.py +0 -0
  80. {margin-0.9.26 → margin-0.9.28}/tests/test_policy_trace.py +0 -0
  81. {margin-0.9.26 → margin-0.9.28}/tests/test_policy_tuning.py +0 -0
  82. {margin-0.9.26 → margin-0.9.28}/tests/test_policy_validate.py +0 -0
  83. {margin-0.9.26 → margin-0.9.28}/tests/test_predicates.py +0 -0
  84. {margin-0.9.26 → margin-0.9.28}/tests/test_printer3d.py +0 -0
  85. {margin-0.9.26 → margin-0.9.28}/tests/test_provenance.py +0 -0
  86. {margin-0.9.26 → margin-0.9.28}/tests/test_reverse_bridge.py +0 -0
  87. {margin-0.9.26 → margin-0.9.28}/tests/test_ros2.py +0 -0
  88. {margin-0.9.26 → margin-0.9.28}/tests/test_staleness.py +0 -0
  89. {margin-0.9.26 → margin-0.9.28}/tests/test_streaming.py +0 -0
  90. {margin-0.9.26 → margin-0.9.28}/tests/test_transformer.py +0 -0
  91. {margin-0.9.26 → margin-0.9.28}/tests/test_transitions.py +0 -0
  92. {margin-0.9.26 → margin-0.9.28}/tests/test_uncertain.py +0 -0
  93. {margin-0.9.26 → margin-0.9.28}/tests/test_validity.py +0 -0
  94. {margin-0.9.26 → margin-0.9.28}/tests/test_windowing.py +0 -0
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: margin
3
- Version: 0.9.26
3
+ Version: 0.9.28
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
7
- Project-URL: Homepage, https://github.com/sethc5/margin
8
- Project-URL: Repository, https://github.com/sethc5/margin
9
- Project-URL: Issues, https://github.com/sethc5/margin/issues
10
- Project-URL: Changelog, https://github.com/sethc5/margin/blob/main/CHANGELOG.md
11
- Project-URL: Documentation, https://github.com/sethc5/margin#readme
7
+ Project-URL: Homepage, https://copelabs.dev
8
+ Project-URL: Repository, https://github.com/Cope-Labs/margin
9
+ Project-URL: Issues, https://github.com/Cope-Labs/margin/issues
10
+ Project-URL: Changelog, https://github.com/Cope-Labs/margin/blob/main/CHANGELOG.md
11
+ Project-URL: Documentation, https://github.com/Cope-Labs/margin#readme
12
12
  Keywords: health,monitoring,uncertainty,observability,typed,classification,threshold,polarity,correction,audit,ledger,policy
13
13
  Classifier: Development Status :: 4 - Beta
14
14
  Classifier: Intended Audience :: Developers
@@ -156,24 +156,24 @@ Ready-to-use threshold profiles for specific domains:
156
156
 
157
157
  | Adapter | What it monitors | Polarity |
158
158
  |---|---|---|
159
- | [**healthcare**](https://github.com/sethc5/margin/tree/main/adapters/healthcare/) | Vital signs (HR, BP, SpO2, temp, glucose) — WHO/AHA ranges, sepsis screening | bands |
159
+ | [**healthcare**](https://github.com/Cope-Labs/margin/tree/main/adapters/healthcare/) | Vital signs (HR, BP, SpO2, temp, glucose) — WHO/AHA ranges, sepsis screening | bands |
160
160
  | [**godot**](https://github.com/sethc5/margin-godot) | Game systems (food, morale, stress, stamina) — native GDScript | mixed |
161
- | [**homeassistant**](https://github.com/sethc5/margin/tree/main/adapters/homeassistant/) | Smart home sensors (temp, humidity, battery, solar, power) | mixed |
162
- | [**evcharging**](https://github.com/sethc5/margin/tree/main/adapters/evcharging/) | EV charging (SoC, grid draw, solar surplus, efficiency) | mixed |
163
- | [**infrastructure**](https://github.com/sethc5/margin/tree/main/adapters/infrastructure/) | Server monitoring (CPU, memory, disk, latency, error rate) | mixed |
164
- | [**aquarium**](https://github.com/sethc5/margin/tree/main/adapters/aquarium/) | Water chemistry (pH, ammonia, nitrite, temp, hardness) | bands |
165
- | [**greenhouse**](https://github.com/sethc5/margin/tree/main/adapters/greenhouse/) | Growing environment (soil moisture, CO₂, light, VPD) | bands |
166
- | [**fitness**](https://github.com/sethc5/margin/tree/main/adapters/fitness/) | Wearables (resting HR, HRV, sleep, steps, stress) | mixed |
167
- | [**transformer**](https://github.com/sethc5/margin/tree/main/adapters/transformer/) | ML circuit interpretability (pythia-6.9b) | higher |
168
- | [**fastapi**](https://github.com/sethc5/margin/tree/main/adapters/fastapi/) | Endpoint health (latency, error rate, throughput, queue depth) | mixed |
169
- | [**database**](https://github.com/sethc5/margin/tree/main/adapters/database/) | DB health (pool usage, query latency, replication lag, deadlocks) | mixed |
170
- | [**celery**](https://github.com/sethc5/margin/tree/main/adapters/celery/) | Task queue (queue depth, failure rate, worker utilization, retries) | mixed |
171
- | [**dataframe**](https://github.com/sethc5/margin/tree/main/adapters/dataframe/) | Data quality (completeness, null rate, drift, freshness, schema) | mixed |
172
- | [**pytest**](https://github.com/sethc5/margin/tree/main/adapters/pytest/) | Test suite health (pass rate, flake rate, coverage, duration) | mixed |
161
+ | [**homeassistant**](https://github.com/Cope-Labs/margin/tree/main/adapters/homeassistant/) | Smart home sensors (temp, humidity, battery, solar, power) | mixed |
162
+ | [**evcharging**](https://github.com/Cope-Labs/margin/tree/main/adapters/evcharging/) | EV charging (SoC, grid draw, solar surplus, efficiency) | mixed |
163
+ | [**infrastructure**](https://github.com/Cope-Labs/margin/tree/main/adapters/infrastructure/) | Server monitoring (CPU, memory, disk, latency, error rate) | mixed |
164
+ | [**aquarium**](https://github.com/Cope-Labs/margin/tree/main/adapters/aquarium/) | Water chemistry (pH, ammonia, nitrite, temp, hardness) | bands |
165
+ | [**greenhouse**](https://github.com/Cope-Labs/margin/tree/main/adapters/greenhouse/) | Growing environment (soil moisture, CO₂, light, VPD) | bands |
166
+ | [**fitness**](https://github.com/Cope-Labs/margin/tree/main/adapters/fitness/) | Wearables (resting HR, HRV, sleep, steps, stress) | mixed |
167
+ | [**transformer**](https://github.com/Cope-Labs/margin/tree/main/adapters/transformer/) | ML circuit interpretability (pythia-6.9b) | higher |
168
+ | [**fastapi**](https://github.com/Cope-Labs/margin/tree/main/adapters/fastapi/) | Endpoint health (latency, error rate, throughput, queue depth) | mixed |
169
+ | [**database**](https://github.com/Cope-Labs/margin/tree/main/adapters/database/) | DB health (pool usage, query latency, replication lag, deadlocks) | mixed |
170
+ | [**celery**](https://github.com/Cope-Labs/margin/tree/main/adapters/celery/) | Task queue (queue depth, failure rate, worker utilization, retries) | mixed |
171
+ | [**dataframe**](https://github.com/Cope-Labs/margin/tree/main/adapters/dataframe/) | Data quality (completeness, null rate, drift, freshness, schema) | mixed |
172
+ | [**pytest**](https://github.com/Cope-Labs/margin/tree/main/adapters/pytest/) | Test suite health (pass rate, flake rate, coverage, duration) | mixed |
173
173
 
174
174
  ## Docs
175
175
 
176
- Full specification: [margin-language.md](https://github.com/sethc5/margin/blob/main/margin/margin-language.md)
176
+ Full specification: [margin-language.md](https://github.com/Cope-Labs/margin/blob/main/margin/margin-language.md)
177
177
 
178
178
  ## License
179
179
 
@@ -127,24 +127,24 @@ Ready-to-use threshold profiles for specific domains:
127
127
 
128
128
  | Adapter | What it monitors | Polarity |
129
129
  |---|---|---|
130
- | [**healthcare**](https://github.com/sethc5/margin/tree/main/adapters/healthcare/) | Vital signs (HR, BP, SpO2, temp, glucose) — WHO/AHA ranges, sepsis screening | bands |
130
+ | [**healthcare**](https://github.com/Cope-Labs/margin/tree/main/adapters/healthcare/) | Vital signs (HR, BP, SpO2, temp, glucose) — WHO/AHA ranges, sepsis screening | bands |
131
131
  | [**godot**](https://github.com/sethc5/margin-godot) | Game systems (food, morale, stress, stamina) — native GDScript | mixed |
132
- | [**homeassistant**](https://github.com/sethc5/margin/tree/main/adapters/homeassistant/) | Smart home sensors (temp, humidity, battery, solar, power) | mixed |
133
- | [**evcharging**](https://github.com/sethc5/margin/tree/main/adapters/evcharging/) | EV charging (SoC, grid draw, solar surplus, efficiency) | mixed |
134
- | [**infrastructure**](https://github.com/sethc5/margin/tree/main/adapters/infrastructure/) | Server monitoring (CPU, memory, disk, latency, error rate) | mixed |
135
- | [**aquarium**](https://github.com/sethc5/margin/tree/main/adapters/aquarium/) | Water chemistry (pH, ammonia, nitrite, temp, hardness) | bands |
136
- | [**greenhouse**](https://github.com/sethc5/margin/tree/main/adapters/greenhouse/) | Growing environment (soil moisture, CO₂, light, VPD) | bands |
137
- | [**fitness**](https://github.com/sethc5/margin/tree/main/adapters/fitness/) | Wearables (resting HR, HRV, sleep, steps, stress) | mixed |
138
- | [**transformer**](https://github.com/sethc5/margin/tree/main/adapters/transformer/) | ML circuit interpretability (pythia-6.9b) | higher |
139
- | [**fastapi**](https://github.com/sethc5/margin/tree/main/adapters/fastapi/) | Endpoint health (latency, error rate, throughput, queue depth) | mixed |
140
- | [**database**](https://github.com/sethc5/margin/tree/main/adapters/database/) | DB health (pool usage, query latency, replication lag, deadlocks) | mixed |
141
- | [**celery**](https://github.com/sethc5/margin/tree/main/adapters/celery/) | Task queue (queue depth, failure rate, worker utilization, retries) | mixed |
142
- | [**dataframe**](https://github.com/sethc5/margin/tree/main/adapters/dataframe/) | Data quality (completeness, null rate, drift, freshness, schema) | mixed |
143
- | [**pytest**](https://github.com/sethc5/margin/tree/main/adapters/pytest/) | Test suite health (pass rate, flake rate, coverage, duration) | mixed |
132
+ | [**homeassistant**](https://github.com/Cope-Labs/margin/tree/main/adapters/homeassistant/) | Smart home sensors (temp, humidity, battery, solar, power) | mixed |
133
+ | [**evcharging**](https://github.com/Cope-Labs/margin/tree/main/adapters/evcharging/) | EV charging (SoC, grid draw, solar surplus, efficiency) | mixed |
134
+ | [**infrastructure**](https://github.com/Cope-Labs/margin/tree/main/adapters/infrastructure/) | Server monitoring (CPU, memory, disk, latency, error rate) | mixed |
135
+ | [**aquarium**](https://github.com/Cope-Labs/margin/tree/main/adapters/aquarium/) | Water chemistry (pH, ammonia, nitrite, temp, hardness) | bands |
136
+ | [**greenhouse**](https://github.com/Cope-Labs/margin/tree/main/adapters/greenhouse/) | Growing environment (soil moisture, CO₂, light, VPD) | bands |
137
+ | [**fitness**](https://github.com/Cope-Labs/margin/tree/main/adapters/fitness/) | Wearables (resting HR, HRV, sleep, steps, stress) | mixed |
138
+ | [**transformer**](https://github.com/Cope-Labs/margin/tree/main/adapters/transformer/) | ML circuit interpretability (pythia-6.9b) | higher |
139
+ | [**fastapi**](https://github.com/Cope-Labs/margin/tree/main/adapters/fastapi/) | Endpoint health (latency, error rate, throughput, queue depth) | mixed |
140
+ | [**database**](https://github.com/Cope-Labs/margin/tree/main/adapters/database/) | DB health (pool usage, query latency, replication lag, deadlocks) | mixed |
141
+ | [**celery**](https://github.com/Cope-Labs/margin/tree/main/adapters/celery/) | Task queue (queue depth, failure rate, worker utilization, retries) | mixed |
142
+ | [**dataframe**](https://github.com/Cope-Labs/margin/tree/main/adapters/dataframe/) | Data quality (completeness, null rate, drift, freshness, schema) | mixed |
143
+ | [**pytest**](https://github.com/Cope-Labs/margin/tree/main/adapters/pytest/) | Test suite health (pass rate, flake rate, coverage, duration) | mixed |
144
144
 
145
145
  ## Docs
146
146
 
147
- Full specification: [margin-language.md](https://github.com/sethc5/margin/blob/main/margin/margin-language.md)
147
+ Full specification: [margin-language.md](https://github.com/Cope-Labs/margin/blob/main/margin/margin-language.md)
148
148
 
149
149
  ## License
150
150
 
@@ -120,6 +120,9 @@ from .intent import (
120
120
  # Loop
121
121
  from .loop import StepResult, step, run, FullStepResult, full_step
122
122
 
123
+ # Flow
124
+ from .flow import Flow, Stage, Guidance, FlowResult, GUIDANCE_SEVERITY
125
+
123
126
  __all__ = [
124
127
  # Foundation
125
128
  "Confidence",
@@ -190,6 +193,8 @@ __all__ = [
190
193
  "save_monitor", "load_monitor", "replay", "replay_csv",
191
194
  # Loop
192
195
  "StepResult", "step", "run", "FullStepResult", "full_step",
196
+ # Flow
197
+ "Flow", "Stage", "Guidance", "FlowResult", "GUIDANCE_SEVERITY",
193
198
  # margin-poc compat aliases
194
199
  "EpistemicSource", "UncertaintyMode",
195
200
  "CallConfidence", "CircuitHealth", "GapThresholds", "MarginParser",
@@ -0,0 +1,390 @@
1
+ """
2
+ Flow: ordered sequence of stages with typed guidance.
3
+
4
+ A Flow is an ordered list of Stage objects, each with its own Parser and
5
+ per-stage Monitor. Observations are routed to the current stage; the Flow
6
+ emits typed Guidance (ADVANCE / HOLD / RETRY / ROLLBACK / ESCALATE /
7
+ COMPLETE) based on stage health and user-supplied advance/retry/escalate
8
+ predicates.
9
+
10
+ Minimal use::
11
+
12
+ from margin import Flow, Stage, Parser, Thresholds, Guidance
13
+
14
+ ferment = Flow(stages=[
15
+ Stage(
16
+ name="inoculation",
17
+ parser=Parser(baselines={"pH": 6.5, "DO": 90}, thresholds=Thresholds(...)),
18
+ advance_when=lambda expr: expr.health_of("DO") == Health.INTACT,
19
+ ),
20
+ Stage(
21
+ name="lag_phase",
22
+ parser=Parser(baselines={"pH": 6.3, "biomass": 0.1}, thresholds=Thresholds(...)),
23
+ advance_when=lambda expr: (expr.health_of("biomass") == Health.INTACT
24
+ and expr.observations[0].sigma > 0.5),
25
+ ),
26
+ # ...
27
+ ], label="fermentation")
28
+
29
+ result = ferment.observe({"pH": 6.5, "DO": 88})
30
+ # → FlowResult(stage="inoculation", guidance=HOLD, reason=...)
31
+
32
+ # When ready:
33
+ result = ferment.observe({"pH": 6.4, "DO": 92})
34
+ # → FlowResult(stage="inoculation", guidance=ADVANCE, ...)
35
+ ferment.advance()
36
+
37
+ # Compact rendering:
38
+ result.to_atom()
39
+ # → "flow:fermentation:inoculation[1/5] → ADVANCE"
40
+
41
+ Contracts can be used in predicates::
42
+
43
+ from margin import Contract, HealthTarget, Health, Ledger
44
+ contract = Contract(terms=[HealthTarget(component="biomass", target=Health.INTACT)])
45
+ ledger = Ledger()
46
+ stage = Stage(
47
+ name="stationary",
48
+ parser=parser,
49
+ advance_when=lambda expr: contract.evaluate(ledger, expr).passed,
50
+ )
51
+
52
+ This keeps Flow lightweight (no mandatory Ledger) while letting advanced
53
+ users plug in the full Contract machinery when they need it.
54
+ """
55
+
56
+ from __future__ import annotations
57
+
58
+ from dataclasses import dataclass, field
59
+ from datetime import datetime
60
+ from enum import Enum
61
+ from typing import Callable, Dict, List, Optional
62
+
63
+ from .health import Health
64
+ from .observation import Expression, Parser
65
+ from .streaming import Monitor
66
+
67
+
68
+ # -----------------------------------------------------------------------
69
+ # Guidance
70
+ # -----------------------------------------------------------------------
71
+
72
+ class Guidance(Enum):
73
+ """
74
+ Recommendation for the current flow state.
75
+
76
+ ADVANCE: exit criteria satisfied — ready to move to the next stage
77
+ HOLD: stage running healthy, exit criteria not yet met
78
+ RETRY: stage degraded but recoverable; continue observing
79
+ ROLLBACK: this stage cannot proceed; return to previous stage
80
+ ESCALATE: cannot proceed automatically — needs human intervention
81
+ COMPLETE: all stages are done
82
+ """
83
+ ADVANCE = "ADVANCE"
84
+ HOLD = "HOLD"
85
+ RETRY = "RETRY"
86
+ ROLLBACK = "ROLLBACK"
87
+ ESCALATE = "ESCALATE"
88
+ COMPLETE = "COMPLETE"
89
+
90
+
91
+ GUIDANCE_SEVERITY: Dict[Guidance, int] = {
92
+ Guidance.COMPLETE: 0,
93
+ Guidance.ADVANCE: 0,
94
+ Guidance.HOLD: 1,
95
+ Guidance.RETRY: 2,
96
+ Guidance.ROLLBACK: 3,
97
+ Guidance.ESCALATE: 4,
98
+ }
99
+
100
+
101
+ # -----------------------------------------------------------------------
102
+ # Stage
103
+ # -----------------------------------------------------------------------
104
+
105
+ StagePredicate = Callable[[Expression], bool]
106
+
107
+
108
+ @dataclass
109
+ class Stage:
110
+ """
111
+ One stage in a process flow.
112
+
113
+ name: human-readable stage identifier
114
+ parser: Parser for measurements observed during this stage
115
+ advance_when: predicate(expr) → True means exit criteria met
116
+ retry_when: predicate(expr) → True means stage is degraded-but-recoverable
117
+ escalate_when: predicate(expr) → True means stage cannot proceed
118
+ rollback_when: predicate(expr) → True means return to the previous stage
119
+ window: observation window size for the stage's Monitor (default 20)
120
+
121
+ All predicates are optional. When none fire, Guidance defaults to HOLD.
122
+ If no explicit predicates are given, the default classifier uses
123
+ observation health (any ABLATED → ESCALATE, any DEGRADED → RETRY, else HOLD).
124
+ """
125
+
126
+ name: str
127
+ parser: Parser
128
+ advance_when: Optional[StagePredicate] = None
129
+ retry_when: Optional[StagePredicate] = None
130
+ escalate_when: Optional[StagePredicate] = None
131
+ rollback_when: Optional[StagePredicate] = None
132
+ window: int = 20
133
+
134
+ def create_monitor(self) -> Monitor:
135
+ """Fresh Monitor bound to this stage's parser and window."""
136
+ return Monitor(self.parser, window=self.window)
137
+
138
+
139
+ # -----------------------------------------------------------------------
140
+ # FlowResult
141
+ # -----------------------------------------------------------------------
142
+
143
+ @dataclass
144
+ class FlowResult:
145
+ """Returned from Flow.observe() and flow advancement helpers."""
146
+ stage: str
147
+ stage_index: int
148
+ total_stages: int
149
+ expression: Optional[Expression]
150
+ guidance: Guidance
151
+ reason: str
152
+
153
+ def to_atom(self) -> str:
154
+ """
155
+ Compact flow notation.
156
+
157
+ "flow:fermentation:lag_phase[2/5] → ADVANCE"
158
+ "flow:fermentation:COMPLETE[5/5]"
159
+ """
160
+ if self.guidance == Guidance.COMPLETE:
161
+ return f"flow:{self.stage}[{self.total_stages}/{self.total_stages}]"
162
+ return (
163
+ f"flow:{self.stage}[{self.stage_index + 1}/{self.total_stages}]"
164
+ f" → {self.guidance.value}"
165
+ )
166
+
167
+ def to_dict(self) -> dict:
168
+ return {
169
+ "stage": self.stage,
170
+ "stage_index": self.stage_index,
171
+ "total_stages": self.total_stages,
172
+ "guidance": self.guidance.value,
173
+ "reason": self.reason,
174
+ "expression": self.expression.to_dict() if self.expression else None,
175
+ }
176
+
177
+ def __repr__(self) -> str:
178
+ return f"FlowResult({self.to_atom()} — {self.reason})"
179
+
180
+
181
+ # -----------------------------------------------------------------------
182
+ # Flow
183
+ # -----------------------------------------------------------------------
184
+
185
+ class Flow:
186
+ """
187
+ An ordered sequence of stages with typed per-step guidance.
188
+
189
+ Each stage has its own Monitor, created fresh when the flow enters the
190
+ stage. Observations are routed to the current stage only; earlier-stage
191
+ state is preserved in the transition log.
192
+
193
+ Flow does not auto-advance. ``observe()`` returns guidance; the caller
194
+ (or a policy layer above) decides whether to call ``advance()``. This
195
+ keeps the flow deterministic and side-effect free, which matters for
196
+ replay, backtesting, and safety-critical use cases.
197
+ """
198
+
199
+ def __init__(
200
+ self,
201
+ stages: List[Stage],
202
+ label: str = "",
203
+ ):
204
+ if not stages:
205
+ raise ValueError("Flow requires at least one stage")
206
+ names = [s.name for s in stages]
207
+ if len(set(names)) != len(names):
208
+ raise ValueError(f"Flow stages must have unique names, got {names}")
209
+ self.stages: List[Stage] = list(stages)
210
+ self.label = label
211
+ self._index = 0
212
+ self._monitors: Dict[str, Monitor] = {
213
+ stages[0].name: stages[0].create_monitor()
214
+ }
215
+ self._transitions: List[dict] = []
216
+ self._entered_at: datetime = datetime.now()
217
+
218
+ # ------------------------------------------------------------------
219
+ # Inspection
220
+ # ------------------------------------------------------------------
221
+
222
+ @property
223
+ def current(self) -> Optional[Stage]:
224
+ """Current stage, or None if the flow is complete."""
225
+ if self.is_complete:
226
+ return None
227
+ return self.stages[self._index]
228
+
229
+ @property
230
+ def current_monitor(self) -> Optional[Monitor]:
231
+ """Monitor for the current stage, or None if complete."""
232
+ if self.is_complete:
233
+ return None
234
+ return self._monitors[self.current.name]
235
+
236
+ @property
237
+ def is_complete(self) -> bool:
238
+ return self._index >= len(self.stages)
239
+
240
+ @property
241
+ def stage_index(self) -> int:
242
+ return self._index
243
+
244
+ @property
245
+ def transitions(self) -> List[dict]:
246
+ """History of stage transitions."""
247
+ return list(self._transitions)
248
+
249
+ # ------------------------------------------------------------------
250
+ # Observation
251
+ # ------------------------------------------------------------------
252
+
253
+ def observe(self, values: Dict[str, float], **kw) -> FlowResult:
254
+ """
255
+ Route an observation to the current stage and return guidance.
256
+
257
+ ``values`` and any kwargs are forwarded to ``Monitor.update()``, so
258
+ confidences, absences, and provenance all pass through.
259
+ """
260
+ if self.is_complete:
261
+ return FlowResult(
262
+ stage=self.label or "flow",
263
+ stage_index=len(self.stages),
264
+ total_stages=len(self.stages),
265
+ expression=None,
266
+ guidance=Guidance.COMPLETE,
267
+ reason="all stages complete",
268
+ )
269
+
270
+ stage = self.current
271
+ monitor = self._monitors[stage.name]
272
+ expr = monitor.update(values, **kw)
273
+ guidance, reason = self._classify(stage, expr)
274
+
275
+ return FlowResult(
276
+ stage=stage.name,
277
+ stage_index=self._index,
278
+ total_stages=len(self.stages),
279
+ expression=expr,
280
+ guidance=guidance,
281
+ reason=reason,
282
+ )
283
+
284
+ def _classify(self, stage: Stage, expr: Expression) -> tuple[Guidance, str]:
285
+ """Evaluate stage predicates in severity order."""
286
+ # Highest severity first — a true escalate dominates an advance
287
+ if stage.escalate_when is not None and stage.escalate_when(expr):
288
+ return Guidance.ESCALATE, "escalate_when fired"
289
+ if stage.rollback_when is not None and stage.rollback_when(expr):
290
+ return Guidance.ROLLBACK, "rollback_when fired"
291
+ if stage.advance_when is not None and stage.advance_when(expr):
292
+ return Guidance.ADVANCE, "advance_when fired"
293
+ if stage.retry_when is not None and stage.retry_when(expr):
294
+ return Guidance.RETRY, "retry_when fired"
295
+
296
+ # Fallback classification using observation health alone
297
+ if any(o.health == Health.ABLATED for o in expr.observations):
298
+ return Guidance.ESCALATE, "component ABLATED"
299
+ if any(o.health == Health.DEGRADED for o in expr.observations):
300
+ return Guidance.RETRY, "component DEGRADED"
301
+ return Guidance.HOLD, "healthy, exit criteria not met"
302
+
303
+ # ------------------------------------------------------------------
304
+ # Stage transitions
305
+ # ------------------------------------------------------------------
306
+
307
+ def advance(self) -> FlowResult:
308
+ """
309
+ Move to the next stage. Creates a fresh Monitor for that stage.
310
+ Raises if the flow is already complete.
311
+ """
312
+ if self.is_complete:
313
+ raise ValueError("flow is already complete")
314
+ prev = self.stages[self._index].name
315
+ self._index += 1
316
+ self._transitions.append({
317
+ "from": prev,
318
+ "to": self.stages[self._index].name if not self.is_complete else "(complete)",
319
+ "kind": "advance",
320
+ "at": datetime.now().isoformat(),
321
+ })
322
+ if not self.is_complete:
323
+ self._monitors[self.current.name] = self.current.create_monitor()
324
+ return self._status_result("advanced")
325
+
326
+ def rollback(self) -> FlowResult:
327
+ """
328
+ Return to the previous stage. Creates a fresh Monitor for that stage
329
+ (prior observations during the rollback target are discarded).
330
+ Raises if already at the first stage.
331
+ """
332
+ if self._index == 0:
333
+ raise ValueError("cannot rollback from first stage")
334
+ prev = self.current.name if not self.is_complete else "(complete)"
335
+ self._index -= 1
336
+ self._monitors[self.current.name] = self.current.create_monitor()
337
+ self._transitions.append({
338
+ "from": prev,
339
+ "to": self.current.name,
340
+ "kind": "rollback",
341
+ "at": datetime.now().isoformat(),
342
+ })
343
+ return self._status_result("rolled back")
344
+
345
+ def reset(self) -> None:
346
+ """Reset to the first stage. Clears all monitors and transitions."""
347
+ self._index = 0
348
+ self._monitors = {self.stages[0].name: self.stages[0].create_monitor()}
349
+ self._transitions = []
350
+ self._entered_at = datetime.now()
351
+
352
+ def _status_result(self, reason: str) -> FlowResult:
353
+ if self.is_complete:
354
+ return FlowResult(
355
+ stage=self.label or "flow",
356
+ stage_index=len(self.stages),
357
+ total_stages=len(self.stages),
358
+ expression=None,
359
+ guidance=Guidance.COMPLETE,
360
+ reason=reason,
361
+ )
362
+ return FlowResult(
363
+ stage=self.current.name,
364
+ stage_index=self._index,
365
+ total_stages=len(self.stages),
366
+ expression=None,
367
+ guidance=Guidance.HOLD,
368
+ reason=reason,
369
+ )
370
+
371
+ # ------------------------------------------------------------------
372
+ # Serialization
373
+ # ------------------------------------------------------------------
374
+
375
+ def to_dict(self) -> dict:
376
+ """Serialize the flow's current position and transition log."""
377
+ return {
378
+ "label": self.label,
379
+ "stages": [s.name for s in self.stages],
380
+ "index": self._index,
381
+ "transitions": list(self._transitions),
382
+ "entered_at": self._entered_at.isoformat(),
383
+ }
384
+
385
+ def __repr__(self) -> str:
386
+ if self.is_complete:
387
+ pos = f"COMPLETE[{len(self.stages)}/{len(self.stages)}]"
388
+ else:
389
+ pos = f"{self.current.name}[{self._index + 1}/{len(self.stages)}]"
390
+ return f"Flow(label={self.label!r}, {pos})"
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: margin
3
- Version: 0.9.26
3
+ Version: 0.9.28
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
7
- Project-URL: Homepage, https://github.com/sethc5/margin
8
- Project-URL: Repository, https://github.com/sethc5/margin
9
- Project-URL: Issues, https://github.com/sethc5/margin/issues
10
- Project-URL: Changelog, https://github.com/sethc5/margin/blob/main/CHANGELOG.md
11
- Project-URL: Documentation, https://github.com/sethc5/margin#readme
7
+ Project-URL: Homepage, https://copelabs.dev
8
+ Project-URL: Repository, https://github.com/Cope-Labs/margin
9
+ Project-URL: Issues, https://github.com/Cope-Labs/margin/issues
10
+ Project-URL: Changelog, https://github.com/Cope-Labs/margin/blob/main/CHANGELOG.md
11
+ Project-URL: Documentation, https://github.com/Cope-Labs/margin#readme
12
12
  Keywords: health,monitoring,uncertainty,observability,typed,classification,threshold,polarity,correction,audit,ledger,policy
13
13
  Classifier: Development Status :: 4 - Beta
14
14
  Classifier: Intended Audience :: Developers
@@ -156,24 +156,24 @@ Ready-to-use threshold profiles for specific domains:
156
156
 
157
157
  | Adapter | What it monitors | Polarity |
158
158
  |---|---|---|
159
- | [**healthcare**](https://github.com/sethc5/margin/tree/main/adapters/healthcare/) | Vital signs (HR, BP, SpO2, temp, glucose) — WHO/AHA ranges, sepsis screening | bands |
159
+ | [**healthcare**](https://github.com/Cope-Labs/margin/tree/main/adapters/healthcare/) | Vital signs (HR, BP, SpO2, temp, glucose) — WHO/AHA ranges, sepsis screening | bands |
160
160
  | [**godot**](https://github.com/sethc5/margin-godot) | Game systems (food, morale, stress, stamina) — native GDScript | mixed |
161
- | [**homeassistant**](https://github.com/sethc5/margin/tree/main/adapters/homeassistant/) | Smart home sensors (temp, humidity, battery, solar, power) | mixed |
162
- | [**evcharging**](https://github.com/sethc5/margin/tree/main/adapters/evcharging/) | EV charging (SoC, grid draw, solar surplus, efficiency) | mixed |
163
- | [**infrastructure**](https://github.com/sethc5/margin/tree/main/adapters/infrastructure/) | Server monitoring (CPU, memory, disk, latency, error rate) | mixed |
164
- | [**aquarium**](https://github.com/sethc5/margin/tree/main/adapters/aquarium/) | Water chemistry (pH, ammonia, nitrite, temp, hardness) | bands |
165
- | [**greenhouse**](https://github.com/sethc5/margin/tree/main/adapters/greenhouse/) | Growing environment (soil moisture, CO₂, light, VPD) | bands |
166
- | [**fitness**](https://github.com/sethc5/margin/tree/main/adapters/fitness/) | Wearables (resting HR, HRV, sleep, steps, stress) | mixed |
167
- | [**transformer**](https://github.com/sethc5/margin/tree/main/adapters/transformer/) | ML circuit interpretability (pythia-6.9b) | higher |
168
- | [**fastapi**](https://github.com/sethc5/margin/tree/main/adapters/fastapi/) | Endpoint health (latency, error rate, throughput, queue depth) | mixed |
169
- | [**database**](https://github.com/sethc5/margin/tree/main/adapters/database/) | DB health (pool usage, query latency, replication lag, deadlocks) | mixed |
170
- | [**celery**](https://github.com/sethc5/margin/tree/main/adapters/celery/) | Task queue (queue depth, failure rate, worker utilization, retries) | mixed |
171
- | [**dataframe**](https://github.com/sethc5/margin/tree/main/adapters/dataframe/) | Data quality (completeness, null rate, drift, freshness, schema) | mixed |
172
- | [**pytest**](https://github.com/sethc5/margin/tree/main/adapters/pytest/) | Test suite health (pass rate, flake rate, coverage, duration) | mixed |
161
+ | [**homeassistant**](https://github.com/Cope-Labs/margin/tree/main/adapters/homeassistant/) | Smart home sensors (temp, humidity, battery, solar, power) | mixed |
162
+ | [**evcharging**](https://github.com/Cope-Labs/margin/tree/main/adapters/evcharging/) | EV charging (SoC, grid draw, solar surplus, efficiency) | mixed |
163
+ | [**infrastructure**](https://github.com/Cope-Labs/margin/tree/main/adapters/infrastructure/) | Server monitoring (CPU, memory, disk, latency, error rate) | mixed |
164
+ | [**aquarium**](https://github.com/Cope-Labs/margin/tree/main/adapters/aquarium/) | Water chemistry (pH, ammonia, nitrite, temp, hardness) | bands |
165
+ | [**greenhouse**](https://github.com/Cope-Labs/margin/tree/main/adapters/greenhouse/) | Growing environment (soil moisture, CO₂, light, VPD) | bands |
166
+ | [**fitness**](https://github.com/Cope-Labs/margin/tree/main/adapters/fitness/) | Wearables (resting HR, HRV, sleep, steps, stress) | mixed |
167
+ | [**transformer**](https://github.com/Cope-Labs/margin/tree/main/adapters/transformer/) | ML circuit interpretability (pythia-6.9b) | higher |
168
+ | [**fastapi**](https://github.com/Cope-Labs/margin/tree/main/adapters/fastapi/) | Endpoint health (latency, error rate, throughput, queue depth) | mixed |
169
+ | [**database**](https://github.com/Cope-Labs/margin/tree/main/adapters/database/) | DB health (pool usage, query latency, replication lag, deadlocks) | mixed |
170
+ | [**celery**](https://github.com/Cope-Labs/margin/tree/main/adapters/celery/) | Task queue (queue depth, failure rate, worker utilization, retries) | mixed |
171
+ | [**dataframe**](https://github.com/Cope-Labs/margin/tree/main/adapters/dataframe/) | Data quality (completeness, null rate, drift, freshness, schema) | mixed |
172
+ | [**pytest**](https://github.com/Cope-Labs/margin/tree/main/adapters/pytest/) | Test suite health (pass rate, flake rate, coverage, duration) | mixed |
173
173
 
174
174
  ## Docs
175
175
 
176
- Full specification: [margin-language.md](https://github.com/sethc5/margin/blob/main/margin/margin-language.md)
176
+ Full specification: [margin-language.md](https://github.com/Cope-Labs/margin/blob/main/margin/margin-language.md)
177
177
 
178
178
  ## License
179
179
 
@@ -19,6 +19,7 @@ margin/diff.py
19
19
  margin/drift.py
20
20
  margin/events.py
21
21
  margin/fingerprint.py
22
+ margin/flow.py
22
23
  margin/forecast.py
23
24
  margin/health.py
24
25
  margin/intent.py
@@ -60,6 +61,7 @@ tests/test_drift.py
60
61
  tests/test_events.py
61
62
  tests/test_fingerprint_ext.py
62
63
  tests/test_float_tolerance.py
64
+ tests/test_flow.py
63
65
  tests/test_forecast.py
64
66
  tests/test_health.py
65
67
  tests/test_healthcare.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "margin"
7
- version = "0.9.26"
7
+ version = "0.9.28"
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"}
@@ -30,11 +30,11 @@ classifiers = [
30
30
  ]
31
31
 
32
32
  [project.urls]
33
- Homepage = "https://github.com/sethc5/margin"
34
- Repository = "https://github.com/sethc5/margin"
35
- Issues = "https://github.com/sethc5/margin/issues"
36
- Changelog = "https://github.com/sethc5/margin/blob/main/CHANGELOG.md"
37
- Documentation = "https://github.com/sethc5/margin#readme"
33
+ Homepage = "https://copelabs.dev"
34
+ Repository = "https://github.com/Cope-Labs/margin"
35
+ Issues = "https://github.com/Cope-Labs/margin/issues"
36
+ Changelog = "https://github.com/Cope-Labs/margin/blob/main/CHANGELOG.md"
37
+ Documentation = "https://github.com/Cope-Labs/margin#readme"
38
38
 
39
39
  [tool.setuptools.packages.find]
40
40
  include = ["margin*"]