osmp 2.0.0__py3-none-any.whl

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.
osmp/__init__.py ADDED
@@ -0,0 +1,146 @@
1
+ """
2
+ OSMP — Octid Semantic Mesh Protocol
3
+ Tier 1 API: Two functions. Zero setup.
4
+
5
+ from osmp import encode, decode
6
+
7
+ sal = encode(["H:HR@NODE1>120", "H:CASREP", "M:EVA@*"])
8
+ print(sal) # "H:HR@NODE1>120;H:CASREP;M:EVA@*"
9
+
10
+ text = decode("H:HR@NODE1>120;H:CASREP;M:EVA@*")
11
+ print(text) # "heart_rate at NODE1 priority 120; casualty_report; evacuation at broadcast"
12
+
13
+ Patent: OSMP-001-UTIL (pending) — inventor Clay Holberg
14
+ License: Apache 2.0
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ # Lazy-loaded singleton. The protocol module is heavy (~2700 lines).
20
+ # We don't import it until the first call, and we cache the instances.
21
+ _asd = None
22
+ _encoder = None
23
+ _decoder = None
24
+
25
+
26
+ def _init():
27
+ """Initialize singleton ASD, encoder, and decoder on first use."""
28
+ global _asd, _encoder, _decoder
29
+ if _asd is not None:
30
+ return
31
+
32
+ # Import from wherever the protocol internals live.
33
+ # This supports both the current osmp_mcp.osmp layout and
34
+ # the future osmp.protocol layout.
35
+ try:
36
+ from osmp.protocol import AdaptiveSharedDictionary, SALEncoder, SALDecoder
37
+ except ImportError:
38
+ try:
39
+ from osmp_mcp.osmp import AdaptiveSharedDictionary, SALEncoder, SALDecoder
40
+ except ImportError:
41
+ # Fallback: same package, different module name
42
+ from .osmp import AdaptiveSharedDictionary, SALEncoder, SALDecoder
43
+
44
+ _asd = AdaptiveSharedDictionary()
45
+ _encoder = SALEncoder(_asd)
46
+ _decoder = SALDecoder(_asd)
47
+
48
+
49
+ def encode(input_data) -> str:
50
+ """Encode to SAL.
51
+
52
+ Accepts:
53
+ list[str] — opcode strings, joined with ; (sequence operator)
54
+ str — if it looks like SAL (contains :), validates and returns as-is
55
+ if it looks like natural language, returns as NL_PASSTHROUGH
56
+
57
+ Returns:
58
+ SAL string ready for transmission.
59
+
60
+ Examples:
61
+ encode(["H:HR@NODE1>120", "H:CASREP", "M:EVA@*"])
62
+ → "H:HR@NODE1>120;H:CASREP;M:EVA@*"
63
+
64
+ encode("H:HR@NODE1>120;H:CASREP;M:EVA@*")
65
+ → "H:HR@NODE1>120;H:CASREP;M:EVA@*" (passthrough)
66
+ """
67
+ _init()
68
+
69
+ if isinstance(input_data, list):
70
+ return _encoder.encode_sequence(input_data)
71
+
72
+ if isinstance(input_data, str):
73
+ # If it contains namespace:opcode patterns, treat as pre-formatted SAL
74
+ if ":" in input_data and any(
75
+ c in input_data for c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
76
+ ):
77
+ # Looks like SAL already — return as-is
78
+ return input_data
79
+ # Natural language — return as NL passthrough
80
+ # (Future: NL-to-SAL conversion when composition engine is available)
81
+ return input_data
82
+
83
+ raise TypeError(
84
+ f"encode() accepts str or list[str], got {type(input_data).__name__}. "
85
+ f"Example: encode(['H:HR@NODE1>120', 'H:CASREP', 'M:EVA@*'])"
86
+ )
87
+
88
+
89
+ def decode(sal: str) -> str:
90
+ """Decode SAL to natural language description.
91
+
92
+ Accepts:
93
+ str — a SAL instruction string (single frame or ;-separated sequence)
94
+
95
+ Returns:
96
+ Human-readable natural language expansion of the SAL instruction,
97
+ resolved by ASD dictionary lookup. Zero inference.
98
+
99
+ Examples:
100
+ decode("H:HR@NODE1>120;H:CASREP;M:EVA@*")
101
+ → "H:heart_rate →NODE1>120; H:casualty_report; M:evacuation →*"
102
+ """
103
+ _init()
104
+ frames = [f.strip() for f in sal.split(";") if f.strip()]
105
+ if len(frames) <= 1:
106
+ return _decoder.decode_natural_language(sal)
107
+ return "; ".join(_decoder.decode_natural_language(f) for f in frames)
108
+
109
+
110
+ def validate(sal: str, nl: str = "", dependency_rules=None):
111
+ """Validate a composed SAL instruction against all eight rules.
112
+
113
+ Returns a CompositionResult with .valid (bool) and .issues (list).
114
+ """
115
+ _init()
116
+ try:
117
+ from osmp.protocol import validate_composition
118
+ except ImportError:
119
+ try:
120
+ from osmp_mcp.osmp import validate_composition
121
+ except ImportError:
122
+ from .osmp import validate_composition
123
+
124
+ return validate_composition(sal, nl, _asd, dependency_rules=dependency_rules)
125
+
126
+
127
+ def lookup(namespace_opcode: str) -> str | None:
128
+ """Look up an opcode definition in the ASD.
129
+
130
+ Accepts: "H:HR" or namespace="H", opcode="HR"
131
+ Returns: definition string or None if not found.
132
+ """
133
+ _init()
134
+ if ":" in namespace_opcode:
135
+ parts = namespace_opcode.split(":", 1)
136
+ return _asd.lookup(parts[0], parts[1])
137
+ return None
138
+
139
+
140
+ def byte_size(sal: str) -> int:
141
+ """Return UTF-8 byte count of a SAL string."""
142
+ return len(sal.encode("utf-8"))
143
+
144
+
145
+ # Version
146
+ __version__ = "2.0.0"
osmp/core.py ADDED
@@ -0,0 +1,124 @@
1
+ """
2
+ OSMP -- Octid Semantic Mesh Protocol
3
+ Tier 2 API: Class-based interface for advanced use.
4
+
5
+ from osmp.core import OSMP
6
+
7
+ o = OSMP()
8
+ sal = o.encode(["H:HR@NODE1>120", "H:CASREP", "M:EVA@*"])
9
+ text = o.decode(sal)
10
+ result = o.validate(sal)
11
+
12
+ For the two-function API, use Tier 1 instead:
13
+
14
+ from osmp import encode, decode
15
+
16
+ Patent: OSMP-001-UTIL (pending) -- inventor Clay Holberg
17
+ License: Apache 2.0
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from pathlib import Path
23
+ from typing import TYPE_CHECKING
24
+
25
+ if TYPE_CHECKING:
26
+ from .protocol import (
27
+ AdaptiveSharedDictionary,
28
+ CompositionResult,
29
+ DependencyRule,
30
+ SALDecoder,
31
+ SALEncoder,
32
+ )
33
+
34
+
35
+ class OSMP:
36
+ """Full-featured OSMP codec with configurable ASD and dependency rules.
37
+
38
+ For zero-setup usage, prefer the module-level functions in ``osmp``.
39
+ This class exposes configuration points (custom ASD floor version,
40
+ pre-loaded dependency rules, direct ASD access) that the Tier 1
41
+ functions intentionally hide.
42
+ """
43
+
44
+ def __init__(
45
+ self,
46
+ floor_version: str | None = None,
47
+ dependency_rules: list[DependencyRule] | None = None,
48
+ ):
49
+ from .protocol import AdaptiveSharedDictionary, SALDecoder, SALEncoder
50
+
51
+ if floor_version is not None:
52
+ self._asd: AdaptiveSharedDictionary = AdaptiveSharedDictionary(floor_version)
53
+ else:
54
+ self._asd = AdaptiveSharedDictionary()
55
+
56
+ self._encoder: SALEncoder = SALEncoder(self._asd)
57
+ self._decoder: SALDecoder = SALDecoder(self._asd)
58
+ self._dependency_rules: list[DependencyRule] | None = dependency_rules
59
+
60
+ # -- Encoding --------------------------------------------------------
61
+
62
+ def encode(self, instructions: list[str]) -> str:
63
+ """Encode a list of opcode strings into a SAL instruction chain."""
64
+ return self._encoder.encode_sequence(instructions)
65
+
66
+ def encode_frame(
67
+ self,
68
+ namespace: str,
69
+ opcode: str,
70
+ target: str | None = None,
71
+ query_slot: str | None = None,
72
+ consequence_class: str | None = None,
73
+ ) -> str:
74
+ """Encode a single SAL frame from structured fields."""
75
+ return self._encoder.encode_frame(
76
+ namespace, opcode, target, query_slot,
77
+ consequence_class=consequence_class,
78
+ )
79
+
80
+ # -- Decoding --------------------------------------------------------
81
+
82
+ def decode(self, sal: str) -> str:
83
+ """Decode a SAL string to natural language. Handles sequences."""
84
+ frames = [f.strip() for f in sal.split(";") if f.strip()]
85
+ if len(frames) <= 1:
86
+ return self._decoder.decode_natural_language(sal)
87
+ return "; ".join(self._decoder.decode_natural_language(f) for f in frames)
88
+
89
+ def decode_frame(self, sal: str):
90
+ """Decode a single SAL frame to a DecodedInstruction."""
91
+ return self._decoder.decode_frame(sal)
92
+
93
+ # -- Validation ------------------------------------------------------
94
+
95
+ def validate(self, sal: str, nl: str = ""):
96
+ """Validate a SAL instruction against all eight composition rules."""
97
+ from .protocol import validate_composition
98
+ return validate_composition(
99
+ sal, nl, self._asd,
100
+ dependency_rules=self._dependency_rules,
101
+ )
102
+
103
+ # -- Lookup ----------------------------------------------------------
104
+
105
+ def lookup(self, namespace: str, opcode: str) -> str | None:
106
+ """Look up an opcode definition in the ASD."""
107
+ return self._asd.lookup(namespace, opcode)
108
+
109
+ # -- Direct access ---------------------------------------------------
110
+
111
+ @property
112
+ def asd(self):
113
+ """Direct access to the AdaptiveSharedDictionary instance."""
114
+ return self._asd
115
+
116
+ @property
117
+ def encoder(self):
118
+ """Direct access to the SALEncoder instance."""
119
+ return self._encoder
120
+
121
+ @property
122
+ def decoder(self):
123
+ """Direct access to the SALDecoder instance."""
124
+ return self._decoder