margin 0.8.1__tar.gz → 0.8.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.
Potentially problematic release.
This version of margin might be problematic. Click here for more details.
- {margin-0.8.1/margin.egg-info → margin-0.8.2}/PKG-INFO +1 -1
- {margin-0.8.1 → margin-0.8.2}/margin/causal.py +25 -1
- {margin-0.8.1 → margin-0.8.2}/margin/correlate.py +1 -0
- {margin-0.8.1 → margin-0.8.2}/margin/ledger.py +8 -0
- {margin-0.8.1 → margin-0.8.2/margin.egg-info}/PKG-INFO +1 -1
- {margin-0.8.1 → margin-0.8.2}/pyproject.toml +1 -1
- {margin-0.8.1 → margin-0.8.2}/LICENSE +0 -0
- {margin-0.8.1 → margin-0.8.2}/NOTICE +0 -0
- {margin-0.8.1 → margin-0.8.2}/README.md +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/__init__.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/__main__.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/algebra.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/anomaly.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/bridge.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/calibrate.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/composite.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/confidence.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/config.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/contract.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/diff.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/drift.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/events.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/forecast.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/health.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/intent.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/loop.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/observation.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/persist.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/policy/__init__.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/policy/compose.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/policy/core.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/policy/temporal.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/policy/trace.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/policy/tuning.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/policy/validate.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/predicates.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/provenance.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/streaming.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/transitions.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/uncertain.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin/validity.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin.egg-info/SOURCES.txt +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin.egg-info/dependency_links.txt +0 -0
- {margin-0.8.1 → margin-0.8.2}/margin.egg-info/top_level.txt +0 -0
- {margin-0.8.1 → margin-0.8.2}/setup.cfg +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_algebra.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_anomaly.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_bridge.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_calibrate.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_causal.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_composite.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_confidence.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_config.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_contract.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_correlate.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_diff.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_drift.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_events.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_forecast.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_health.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_healthcare.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_intent.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_ledger.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_loop.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_neuro.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_observation.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_persist.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_policy.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_policy_compose.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_policy_temporal.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_policy_trace.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_policy_tuning.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_policy_validate.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_predicates.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_printer3d.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_provenance.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_reverse_bridge.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_ros2.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_staleness.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_streaming.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_transformer.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_transitions.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_uncertain.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_validity.py +0 -0
- {margin-0.8.1 → margin-0.8.2}/tests/test_windowing.py +0 -0
|
@@ -45,6 +45,10 @@ class CausalLink:
|
|
|
45
45
|
strength: 0.0-1.0, how strong the relationship is (1.0 = deterministic)
|
|
46
46
|
condition: optional — this link only applies when source is in this health state
|
|
47
47
|
evidence: free text — why we believe this link exists
|
|
48
|
+
origin: "asserted" (manually declared) or "discovered" (auto_causal_graph).
|
|
49
|
+
Asserted links are authoritative. Discovered links are correlational
|
|
50
|
+
heuristics — lag-based causal direction is plausible but not proven.
|
|
51
|
+
Never treat discovered links as ground truth in high-stakes domains.
|
|
48
52
|
"""
|
|
49
53
|
source: str
|
|
50
54
|
target: str
|
|
@@ -52,6 +56,15 @@ class CausalLink:
|
|
|
52
56
|
strength: float = 1.0
|
|
53
57
|
condition: Optional[Health] = None
|
|
54
58
|
evidence: str = ""
|
|
59
|
+
origin: str = "asserted" # "asserted" | "discovered"
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def is_asserted(self) -> bool:
|
|
63
|
+
return self.origin == "asserted"
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def is_discovered(self) -> bool:
|
|
67
|
+
return self.origin == "discovered"
|
|
55
68
|
|
|
56
69
|
def to_dict(self) -> dict:
|
|
57
70
|
d = {
|
|
@@ -59,6 +72,7 @@ class CausalLink:
|
|
|
59
72
|
"cause_type": self.cause_type.value,
|
|
60
73
|
"strength": round(self.strength, 4),
|
|
61
74
|
"evidence": self.evidence,
|
|
75
|
+
"origin": self.origin,
|
|
62
76
|
}
|
|
63
77
|
if self.condition:
|
|
64
78
|
d["condition"] = self.condition.value
|
|
@@ -72,11 +86,13 @@ class CausalLink:
|
|
|
72
86
|
strength=d.get("strength", 1.0),
|
|
73
87
|
condition=Health(d["condition"]) if "condition" in d else None,
|
|
74
88
|
evidence=d.get("evidence", ""),
|
|
89
|
+
origin=d.get("origin", "asserted"),
|
|
75
90
|
)
|
|
76
91
|
|
|
77
92
|
def __repr__(self) -> str:
|
|
78
93
|
cond = f" when {self.condition.value}" if self.condition else ""
|
|
79
|
-
|
|
94
|
+
tag = "[discovered]" if self.is_discovered else ""
|
|
95
|
+
return f"{self.source} --{self.cause_type.value}--> {self.target}{cond}{tag}"
|
|
80
96
|
|
|
81
97
|
|
|
82
98
|
# -----------------------------------------------------------------------
|
|
@@ -124,6 +140,14 @@ class CausalGraph:
|
|
|
124
140
|
"""All links where `component` is the source (what it affects)."""
|
|
125
141
|
return [l for l in self.links if l.source == component]
|
|
126
142
|
|
|
143
|
+
def asserted_links(self) -> list[CausalLink]:
|
|
144
|
+
"""Links that were manually declared (authoritative)."""
|
|
145
|
+
return [l for l in self.links if l.is_asserted]
|
|
146
|
+
|
|
147
|
+
def discovered_links(self) -> list[CausalLink]:
|
|
148
|
+
"""Links auto-discovered via correlation (heuristic, not proven)."""
|
|
149
|
+
return [l for l in self.links if l.is_discovered]
|
|
150
|
+
|
|
127
151
|
def upstream(self, component: str, depth: int = 10) -> list[str]:
|
|
128
152
|
"""All components upstream of `component` (transitive causes)."""
|
|
129
153
|
visited = set()
|
|
@@ -85,6 +85,7 @@ class Correlation:
|
|
|
85
85
|
cause_type=cause_type,
|
|
86
86
|
strength=self.strength,
|
|
87
87
|
evidence=f"auto-correlated: r={self.coefficient:.3f}, lag={self.lag}, n={self.n_samples}",
|
|
88
|
+
origin="discovered", # lag-based causal direction is a heuristic, not proven
|
|
88
89
|
)
|
|
89
90
|
|
|
90
91
|
def to_dict(self) -> dict:
|
|
@@ -109,12 +109,20 @@ class Ledger:
|
|
|
109
109
|
"""
|
|
110
110
|
Accumulates Records across a session.
|
|
111
111
|
Provides aggregate stats and renders the intervention history.
|
|
112
|
+
|
|
113
|
+
max_records: optional bound on records kept. When set, old records
|
|
114
|
+
are dropped (oldest first) on each append. Use this for
|
|
115
|
+
long-running production loops to prevent unbounded growth.
|
|
116
|
+
None (default) = unbounded, suitable for batch/replay use.
|
|
112
117
|
"""
|
|
113
118
|
label: str = ""
|
|
114
119
|
records: list[Record] = field(default_factory=list)
|
|
120
|
+
max_records: Optional[int] = None
|
|
115
121
|
|
|
116
122
|
def append(self, record: Record) -> None:
|
|
117
123
|
self.records.append(record)
|
|
124
|
+
if self.max_records is not None and len(self.records) > self.max_records:
|
|
125
|
+
self.records.pop(0)
|
|
118
126
|
|
|
119
127
|
def __len__(self) -> int:
|
|
120
128
|
return len(self.records)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "margin"
|
|
7
|
-
version = "0.8.
|
|
7
|
+
version = "0.8.2"
|
|
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"}
|
|
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
|
|
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
|