zeromodel-observation 1.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,128 @@
1
+ Metadata-Version: 2.4
2
+ Name: zeromodel-observation
3
+ Version: 1.1.0
4
+ Summary: ZeroModel observation package
5
+ Author-email: Ernan Hughes <ernanhughes@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/ernanhughes/zeromodel
8
+ Project-URL: Repository, https://github.com/ernanhughes/zeromodel
9
+ Project-URL: Documentation, https://ernanhughes.github.io/zeromodel/
10
+ Keywords: visual-policy-map,vpm,observation-contracts,provider-protocols
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ Requires-Dist: numpy>=1.23
25
+ Requires-Dist: zeromodel==1.1.0
26
+
27
+ # zeromodel-observation
28
+
29
+ `zeromodel-observation` defines provider-neutral observation contracts for
30
+ ZeroModel. It is the boundary between raw observations and deterministic policy
31
+ addressing, exposed through `zeromodel.observation`.
32
+
33
+ The package depends on the validated core distribution, `zeromodel==1.0.13`, and
34
+ uses NumPy for immutable image-array contracts. It does not implement perception,
35
+ visual recognition, video policy behavior, model inference, or persistence.
36
+
37
+ ## Install
38
+
39
+ ```powershell
40
+ python -m pip install zeromodel==1.0.13 zeromodel-observation==1.0.13
41
+ ```
42
+
43
+ ## Provider-Neutral Contracts
44
+
45
+ `ImageObservation` stores an owned, read-only `uint8` image array plus source,
46
+ timestamp, and metadata. It accepts `HxW`, `HxWx3`, and `HxWx4` arrays and
47
+ derives a deterministic raw digest from the canonical shape and bytes.
48
+
49
+ `VisualAddressContract` describes what a provider promises before inference:
50
+ provider identity, score polarity, replay semantics, representation identity,
51
+ calibration identity, policy identity, and source scope.
52
+
53
+ `VisualAddressDecision` records an accepted address or evidence-bearing
54
+ rejection. It links the observation, representation, provider, address artifact,
55
+ calibration artifact, and policy artifact without depending on any concrete
56
+ vision implementation.
57
+
58
+ `VisualAddressManifest` and `PrototypeBinding` bind representation rows to
59
+ policy row IDs and link those rows to a content-addressed matrix blob. The
60
+ manifest is an address contract artifact, not a visual reader.
61
+
62
+ `DeploymentBinding` records an approved pairing of policy, address artifact,
63
+ calibration, source scope, and optional encoder identity. It is not a
64
+ cryptographic signature or authorization system.
65
+
66
+ ## Minimal Provider
67
+
68
+ ```python
69
+ import numpy as np
70
+
71
+ from zeromodel.observation import (
72
+ ImageObservation,
73
+ VisualAddressContract,
74
+ VisualAddressDecision,
75
+ VisualAddressProvider,
76
+ )
77
+
78
+
79
+ class TableProvider:
80
+ def contract(self) -> VisualAddressContract:
81
+ return VisualAddressContract(
82
+ provider_kind="table-provider",
83
+ provider_version="v1",
84
+ score_semantics="distance",
85
+ observation_spec_digest="obs-spec",
86
+ representation_spec_digest="rep-spec",
87
+ address_artifact_id="address-1",
88
+ calibration_artifact_id="calibration-1",
89
+ policy_artifact_id="policy-1",
90
+ source_scope="fixture:camera",
91
+ replay_contract="exact_decision",
92
+ )
93
+
94
+ def read(self, observation: ImageObservation) -> VisualAddressDecision:
95
+ return VisualAddressDecision(
96
+ accepted=True,
97
+ reason="accepted",
98
+ observation_digest=observation.raw_digest,
99
+ representation_digest="sha256:representation",
100
+ provider_kind="table-provider",
101
+ provider_version="v1",
102
+ score_semantics="distance",
103
+ address_artifact_id="address-1",
104
+ calibration_artifact_id="calibration-1",
105
+ policy_artifact_id="policy-1",
106
+ nearest_row_id="policy-row-1",
107
+ nearest_score=0.0,
108
+ second_row_id=None,
109
+ second_score=None,
110
+ ambiguity_measure=None,
111
+ matched_row_id="policy-row-1",
112
+ exact_match=True,
113
+ accepted_by=("fixture",),
114
+ )
115
+
116
+
117
+ provider: VisualAddressProvider = TableProvider()
118
+ decision = provider.read(ImageObservation(np.zeros((2, 2), dtype=np.uint8)))
119
+ ```
120
+
121
+ ## Exclusions
122
+
123
+ Concrete deterministic visual addressing belongs to `zeromodel-vision`.
124
+ Temporal policy and video-domain behavior belong to `zeromodel-video`.
125
+ Durable SQL persistence belongs to `zeromodel-sqlalchemy`.
126
+
127
+ This package does not import or require `zeromodel-analysis`, `zeromodel-vision`,
128
+ `zeromodel-video`, SQLAlchemy, Torch, TorchVision, Transformers, or Pillow.
@@ -0,0 +1,102 @@
1
+ # zeromodel-observation
2
+
3
+ `zeromodel-observation` defines provider-neutral observation contracts for
4
+ ZeroModel. It is the boundary between raw observations and deterministic policy
5
+ addressing, exposed through `zeromodel.observation`.
6
+
7
+ The package depends on the validated core distribution, `zeromodel==1.0.13`, and
8
+ uses NumPy for immutable image-array contracts. It does not implement perception,
9
+ visual recognition, video policy behavior, model inference, or persistence.
10
+
11
+ ## Install
12
+
13
+ ```powershell
14
+ python -m pip install zeromodel==1.0.13 zeromodel-observation==1.0.13
15
+ ```
16
+
17
+ ## Provider-Neutral Contracts
18
+
19
+ `ImageObservation` stores an owned, read-only `uint8` image array plus source,
20
+ timestamp, and metadata. It accepts `HxW`, `HxWx3`, and `HxWx4` arrays and
21
+ derives a deterministic raw digest from the canonical shape and bytes.
22
+
23
+ `VisualAddressContract` describes what a provider promises before inference:
24
+ provider identity, score polarity, replay semantics, representation identity,
25
+ calibration identity, policy identity, and source scope.
26
+
27
+ `VisualAddressDecision` records an accepted address or evidence-bearing
28
+ rejection. It links the observation, representation, provider, address artifact,
29
+ calibration artifact, and policy artifact without depending on any concrete
30
+ vision implementation.
31
+
32
+ `VisualAddressManifest` and `PrototypeBinding` bind representation rows to
33
+ policy row IDs and link those rows to a content-addressed matrix blob. The
34
+ manifest is an address contract artifact, not a visual reader.
35
+
36
+ `DeploymentBinding` records an approved pairing of policy, address artifact,
37
+ calibration, source scope, and optional encoder identity. It is not a
38
+ cryptographic signature or authorization system.
39
+
40
+ ## Minimal Provider
41
+
42
+ ```python
43
+ import numpy as np
44
+
45
+ from zeromodel.observation import (
46
+ ImageObservation,
47
+ VisualAddressContract,
48
+ VisualAddressDecision,
49
+ VisualAddressProvider,
50
+ )
51
+
52
+
53
+ class TableProvider:
54
+ def contract(self) -> VisualAddressContract:
55
+ return VisualAddressContract(
56
+ provider_kind="table-provider",
57
+ provider_version="v1",
58
+ score_semantics="distance",
59
+ observation_spec_digest="obs-spec",
60
+ representation_spec_digest="rep-spec",
61
+ address_artifact_id="address-1",
62
+ calibration_artifact_id="calibration-1",
63
+ policy_artifact_id="policy-1",
64
+ source_scope="fixture:camera",
65
+ replay_contract="exact_decision",
66
+ )
67
+
68
+ def read(self, observation: ImageObservation) -> VisualAddressDecision:
69
+ return VisualAddressDecision(
70
+ accepted=True,
71
+ reason="accepted",
72
+ observation_digest=observation.raw_digest,
73
+ representation_digest="sha256:representation",
74
+ provider_kind="table-provider",
75
+ provider_version="v1",
76
+ score_semantics="distance",
77
+ address_artifact_id="address-1",
78
+ calibration_artifact_id="calibration-1",
79
+ policy_artifact_id="policy-1",
80
+ nearest_row_id="policy-row-1",
81
+ nearest_score=0.0,
82
+ second_row_id=None,
83
+ second_score=None,
84
+ ambiguity_measure=None,
85
+ matched_row_id="policy-row-1",
86
+ exact_match=True,
87
+ accepted_by=("fixture",),
88
+ )
89
+
90
+
91
+ provider: VisualAddressProvider = TableProvider()
92
+ decision = provider.read(ImageObservation(np.zeros((2, 2), dtype=np.uint8)))
93
+ ```
94
+
95
+ ## Exclusions
96
+
97
+ Concrete deterministic visual addressing belongs to `zeromodel-vision`.
98
+ Temporal policy and video-domain behavior belong to `zeromodel-video`.
99
+ Durable SQL persistence belongs to `zeromodel-sqlalchemy`.
100
+
101
+ This package does not import or require `zeromodel-analysis`, `zeromodel-vision`,
102
+ `zeromodel-video`, SQLAlchemy, Torch, TorchVision, Transformers, or Pillow.
@@ -0,0 +1,44 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "zeromodel-observation"
7
+ version = "1.1.0"
8
+ description = "ZeroModel observation package"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = {text = "MIT"}
12
+ authors = [{name = "Ernan Hughes", email = "ernanhughes@gmail.com"}]
13
+ keywords = [
14
+ "visual-policy-map",
15
+ "vpm",
16
+ "observation-contracts",
17
+ "provider-protocols",
18
+ ]
19
+ classifiers = [
20
+ "Development Status :: 5 - Production/Stable",
21
+ "Intended Audience :: Developers",
22
+ "Intended Audience :: Science/Research",
23
+ "License :: OSI Approved :: MIT License",
24
+ "Operating System :: OS Independent",
25
+ "Programming Language :: Python :: 3",
26
+ "Programming Language :: Python :: 3.10",
27
+ "Programming Language :: Python :: 3.11",
28
+ "Programming Language :: Python :: 3.12",
29
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
30
+ "Topic :: Software Development :: Libraries :: Python Modules",
31
+ ]
32
+ dependencies = [
33
+ "numpy>=1.23",
34
+ "zeromodel==1.1.0",
35
+ ]
36
+
37
+ [project.urls]
38
+ Homepage = "https://github.com/ernanhughes/zeromodel"
39
+ Repository = "https://github.com/ernanhughes/zeromodel"
40
+ Documentation = "https://ernanhughes.github.io/zeromodel/"
41
+
42
+ [tool.setuptools.packages.find]
43
+ where = ["src"]
44
+ include = ["zeromodel.observation*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,37 @@
1
+ """ZeroModel observation public API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .deployment_binding import (
6
+ DEPLOYMENT_BINDING_VERSION,
7
+ DeploymentBinding,
8
+ )
9
+ from .visual_address import (
10
+ IMAGE_OBSERVATION_VERSION,
11
+ ImageObservation,
12
+ VISUAL_ADDRESS_CONTRACT_VERSION,
13
+ VISUAL_ADDRESS_DECISION_VERSION,
14
+ VISUAL_ADDRESS_MANIFEST_VERSION,
15
+ VisualAddressContract,
16
+ VisualAddressDecision,
17
+ VisualAddressProvider,
18
+ )
19
+ from .visual_address_manifest import (
20
+ PrototypeBinding,
21
+ VisualAddressManifest,
22
+ )
23
+
24
+ __all__ = [
25
+ "DEPLOYMENT_BINDING_VERSION",
26
+ "DeploymentBinding",
27
+ "IMAGE_OBSERVATION_VERSION",
28
+ "ImageObservation",
29
+ "PrototypeBinding",
30
+ "VISUAL_ADDRESS_CONTRACT_VERSION",
31
+ "VISUAL_ADDRESS_DECISION_VERSION",
32
+ "VISUAL_ADDRESS_MANIFEST_VERSION",
33
+ "VisualAddressContract",
34
+ "VisualAddressDecision",
35
+ "VisualAddressManifest",
36
+ "VisualAddressProvider",
37
+ ]
@@ -0,0 +1,203 @@
1
+ """Approved binding between a visual address contract and a VPM policy."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ import hashlib
7
+ import json
8
+ from typing import Any, Dict, Mapping, Optional
9
+
10
+ from zeromodel.core.artifact import VPMValidationError
11
+ from zeromodel.observation.visual_address import VisualAddressContract
12
+
13
+
14
+ DEPLOYMENT_BINDING_VERSION = "zeromodel-deployment-binding/v1"
15
+ _DEPLOYMENT_STATUSES = {"validated", "research"}
16
+
17
+
18
+ def _canonical_json_bytes(value: Any) -> bytes:
19
+ try:
20
+ return json.dumps(
21
+ value,
22
+ sort_keys=True,
23
+ separators=(",", ":"),
24
+ ensure_ascii=False,
25
+ allow_nan=False,
26
+ ).encode("utf-8")
27
+ except (TypeError, ValueError) as exc:
28
+ raise VPMValidationError(
29
+ "deployment binding must be JSON-serializable"
30
+ ) from exc
31
+
32
+
33
+ @dataclass(frozen=True)
34
+ class DeploymentBinding:
35
+ """Identity-bearing approval of one perception-policy pairing.
36
+
37
+ A binding is not a cryptographic signature. It records exactly which
38
+ address, calibration, encoder, policy, and source scope were approved
39
+ together so the runtime can reject accidental cross-pairing.
40
+ """
41
+
42
+ policy_artifact_id: str
43
+ address_artifact_id: str
44
+ calibration_artifact_id: str
45
+ source_scope: str
46
+ encoder_manifest_id: Optional[str] = None
47
+ deployment_status: str = "validated"
48
+ version: str = DEPLOYMENT_BINDING_VERSION
49
+ binding_id: str = ""
50
+
51
+ def __init__(
52
+ self,
53
+ *,
54
+ policy_artifact_id: str,
55
+ address_artifact_id: str,
56
+ calibration_artifact_id: str,
57
+ source_scope: str,
58
+ encoder_manifest_id: Optional[str] = None,
59
+ deployment_status: str = "validated",
60
+ version: str = DEPLOYMENT_BINDING_VERSION,
61
+ binding_id: Optional[str] = None,
62
+ ) -> None:
63
+ object.__setattr__(self, "policy_artifact_id", str(policy_artifact_id))
64
+ object.__setattr__(self, "address_artifact_id", str(address_artifact_id))
65
+ object.__setattr__(
66
+ self,
67
+ "calibration_artifact_id",
68
+ str(calibration_artifact_id),
69
+ )
70
+ object.__setattr__(self, "source_scope", str(source_scope))
71
+ object.__setattr__(
72
+ self,
73
+ "encoder_manifest_id",
74
+ None if encoder_manifest_id is None else str(encoder_manifest_id),
75
+ )
76
+ object.__setattr__(self, "deployment_status", str(deployment_status))
77
+ object.__setattr__(self, "version", str(version))
78
+ computed = self.compute_binding_id()
79
+ object.__setattr__(self, "binding_id", str(binding_id or computed))
80
+ self.validate()
81
+
82
+ @property
83
+ def deployment_permitted(self) -> bool:
84
+ return self.deployment_status == "validated"
85
+
86
+ def validate(self) -> None:
87
+ if self.version != DEPLOYMENT_BINDING_VERSION:
88
+ raise VPMValidationError(
89
+ "unsupported deployment binding version: %r" % self.version
90
+ )
91
+ for name, value in (
92
+ ("policy_artifact_id", self.policy_artifact_id),
93
+ ("address_artifact_id", self.address_artifact_id),
94
+ ("calibration_artifact_id", self.calibration_artifact_id),
95
+ ("source_scope", self.source_scope),
96
+ ):
97
+ if not value:
98
+ raise VPMValidationError("%s cannot be empty" % name)
99
+ if self.deployment_status not in _DEPLOYMENT_STATUSES:
100
+ raise VPMValidationError(
101
+ "deployment_status must be 'validated' or 'research'"
102
+ )
103
+ expected = self.compute_binding_id()
104
+ if self.binding_id != expected:
105
+ raise VPMValidationError(
106
+ "deployment binding id mismatch: expected %s, got %s"
107
+ % (expected, self.binding_id)
108
+ )
109
+
110
+ def identity_payload(self) -> Dict[str, Any]:
111
+ return {
112
+ "version": self.version,
113
+ "policy_artifact_id": self.policy_artifact_id,
114
+ "address_artifact_id": self.address_artifact_id,
115
+ "calibration_artifact_id": self.calibration_artifact_id,
116
+ "encoder_manifest_id": self.encoder_manifest_id,
117
+ "source_scope": self.source_scope,
118
+ "deployment_status": self.deployment_status,
119
+ }
120
+
121
+ def compute_binding_id(self) -> str:
122
+ return hashlib.sha256(
123
+ b"zeromodel.deployment-binding.identity.v1\0"
124
+ + _canonical_json_bytes(self.identity_payload())
125
+ ).hexdigest()
126
+
127
+ def to_dict(self) -> Dict[str, Any]:
128
+ payload = self.identity_payload()
129
+ payload["binding_id"] = self.binding_id
130
+ payload["deployment_permitted"] = self.deployment_permitted
131
+ return payload
132
+
133
+ @classmethod
134
+ def from_dict(cls, data: Mapping[str, Any]) -> "DeploymentBinding":
135
+ return cls(
136
+ policy_artifact_id=str(data["policy_artifact_id"]),
137
+ address_artifact_id=str(data["address_artifact_id"]),
138
+ calibration_artifact_id=str(data["calibration_artifact_id"]),
139
+ encoder_manifest_id=data.get("encoder_manifest_id"),
140
+ source_scope=str(data["source_scope"]),
141
+ deployment_status=str(data.get("deployment_status", "validated")),
142
+ version=str(data.get("version", DEPLOYMENT_BINDING_VERSION)),
143
+ binding_id=data.get("binding_id"),
144
+ )
145
+
146
+ @classmethod
147
+ def from_contract(
148
+ cls,
149
+ contract: VisualAddressContract,
150
+ *,
151
+ deployment_status: str = "validated",
152
+ encoder_manifest_id: Optional[str] = None,
153
+ ) -> "DeploymentBinding":
154
+ if contract.source_scope is None or not contract.source_scope:
155
+ raise VPMValidationError(
156
+ "deployment binding requires contract source_scope"
157
+ )
158
+ return cls(
159
+ policy_artifact_id=contract.policy_artifact_id,
160
+ address_artifact_id=contract.address_artifact_id,
161
+ calibration_artifact_id=contract.calibration_artifact_id,
162
+ encoder_manifest_id=encoder_manifest_id,
163
+ source_scope=contract.source_scope,
164
+ deployment_status=deployment_status,
165
+ )
166
+
167
+ def verify_contract(
168
+ self,
169
+ contract: VisualAddressContract,
170
+ *,
171
+ allow_research: bool = False,
172
+ ) -> None:
173
+ contract.validate()
174
+ mismatches = []
175
+ for name, expected, actual in (
176
+ (
177
+ "policy_artifact_id",
178
+ self.policy_artifact_id,
179
+ contract.policy_artifact_id,
180
+ ),
181
+ (
182
+ "address_artifact_id",
183
+ self.address_artifact_id,
184
+ contract.address_artifact_id,
185
+ ),
186
+ (
187
+ "calibration_artifact_id",
188
+ self.calibration_artifact_id,
189
+ contract.calibration_artifact_id,
190
+ ),
191
+ ("source_scope", self.source_scope, contract.source_scope),
192
+ ):
193
+ if expected != actual:
194
+ mismatches.append("%s=%r (expected %r)" % (name, actual, expected))
195
+ if mismatches:
196
+ raise VPMValidationError(
197
+ "deployment binding does not match visual address contract: %s"
198
+ % "; ".join(mismatches)
199
+ )
200
+ if not self.deployment_permitted and not allow_research:
201
+ raise VPMValidationError(
202
+ "research deployment binding is not permitted in validated runtime"
203
+ )