strata-bridge 1.0.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,118 @@
1
+ Metadata-Version: 2.4
2
+ Name: strata_bridge
3
+ Version: 1.0.0
4
+ Summary: Strata BYOM SDK — StrataBridge and StrataModelAdapter for sponsor containers
5
+ Author-email: Intheris Health <intheris.health@gmail.com>
6
+ License-Expression: LicenseRef-Proprietary
7
+ Project-URL: Homepage, https://intheris.health
8
+ Project-URL: Documentation, https://github.com/intheris-health/strata-platform/blob/main/docs/user-guides/BYOM_SPONSOR_SDK.md
9
+ Project-URL: Bug Tracker, https://github.com/intheris-health/strata-platform/issues
10
+ Keywords: federated-learning,healthcare,BYOM,strata
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: pandas>=1.5
23
+ Requires-Dist: pyarrow>=12.0
24
+
25
+ # strata_bridge
26
+
27
+ **Strata BYOM SDK** — the bridge between your ML model and the [Strata federated healthcare platform](https://intheris.health) by Intheris Health.
28
+
29
+ ## Overview
30
+
31
+ `strata_bridge` is the Python package your Docker container uses when participating in federated training or inference across hospital sites. It provides:
32
+
33
+ - **`StrataBridge`** — file-based I/O helpers (`/data` → `/output`)
34
+ - **`StrataModelAdapter`** — the interface your model class must implement
35
+ - **`run.py`** — the container entrypoint dispatcher (`python -m strata_bridge.run`)
36
+
37
+ Core principle: **algorithms travel, data stays local.** Patient data never leaves the hospital; your container receives a privacy-safe DataFrame and returns only model weights or aggregate statistics.
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ pip install strata_bridge
43
+ ```
44
+
45
+ ## Quick Start
46
+
47
+ ```python
48
+ from strata_bridge import StrataModelAdapter, ModelSpec, TrainingConfig, LocalTrainResult, InferenceResult
49
+ import pandas as pd
50
+
51
+ class MyModel(StrataModelAdapter):
52
+
53
+ def get_model_spec(self) -> ModelSpec:
54
+ return ModelSpec(
55
+ model_id="my-model-v1",
56
+ name="My Federated Model",
57
+ version="1.0.0",
58
+ input_columns=["age", "length_of_stay", "drg_weight"],
59
+ output_names=["risk_score"],
60
+ min_samples=50,
61
+ )
62
+
63
+ def initialize(self, config: dict) -> None:
64
+ # load or build your model here
65
+ pass
66
+
67
+ def get_weights(self) -> bytes:
68
+ # serialize model weights
69
+ return b""
70
+
71
+ def set_weights(self, weights: bytes) -> None:
72
+ # deserialize and apply global weights
73
+ pass
74
+
75
+ def local_train(self, data: pd.DataFrame, round_number: int, config: TrainingConfig) -> LocalTrainResult:
76
+ # train on local data, return updated weights + metrics
77
+ return LocalTrainResult(weights=self.get_weights(), sample_count=len(data), loss=0.0)
78
+
79
+ def local_inference(self, data: pd.DataFrame) -> InferenceResult:
80
+ # run inference, return aggregate distribution only
81
+ return InferenceResult(prediction_distribution={"low": len(data)}, sample_count=len(data))
82
+ ```
83
+
84
+ Your `Dockerfile`:
85
+
86
+ ```dockerfile
87
+ FROM python:3.11-slim
88
+ WORKDIR /app
89
+ RUN pip install strata_bridge torch --index-url https://download.pytorch.org/whl/cpu
90
+ COPY model.py .
91
+ CMD ["python", "-m", "strata_bridge.run"]
92
+ ```
93
+
94
+ ## Data Schema
95
+
96
+ Your container receives local patient data at `/data/input.parquet` as a privacy-safe DataFrame. No PII is ever exposed.
97
+
98
+ | Column | Type | Description |
99
+ |--------|------|-------------|
100
+ | `patient_id` | str | Anonymized hash |
101
+ | `age` | int | Computed from DOB |
102
+ | `gender` | str | MALE / FEMALE / OTHER / UNKNOWN |
103
+ | `admission_type` | str | EMERGENCY / ELECTIVE / TRANSFER / AMBULATORY |
104
+ | `length_of_stay` | int | Days |
105
+ | `primary_diagnosis` | str | ICD-10 code |
106
+ | `drg_code` | str | SwissDRG code |
107
+ | `drg_weight` | float | DRG cost weight |
108
+ | `num_procedures` | int | Count |
109
+ | `num_medications` | int | Count |
110
+ | `num_lab_results` | int | Count |
111
+
112
+ ## Documentation
113
+
114
+ Full sponsor guide: [BYOM_SPONSOR_SDK.md](https://github.com/intheris-health/strata-platform/blob/main/docs/user-guides/BYOM_SPONSOR_SDK.md)
115
+
116
+ ## License
117
+
118
+ Proprietary — Intheris Health. Contact [intheris.health@gmail.com](mailto:intheris.health@gmail.com) for access.
@@ -0,0 +1,94 @@
1
+ # strata_bridge
2
+
3
+ **Strata BYOM SDK** — the bridge between your ML model and the [Strata federated healthcare platform](https://intheris.health) by Intheris Health.
4
+
5
+ ## Overview
6
+
7
+ `strata_bridge` is the Python package your Docker container uses when participating in federated training or inference across hospital sites. It provides:
8
+
9
+ - **`StrataBridge`** — file-based I/O helpers (`/data` → `/output`)
10
+ - **`StrataModelAdapter`** — the interface your model class must implement
11
+ - **`run.py`** — the container entrypoint dispatcher (`python -m strata_bridge.run`)
12
+
13
+ Core principle: **algorithms travel, data stays local.** Patient data never leaves the hospital; your container receives a privacy-safe DataFrame and returns only model weights or aggregate statistics.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pip install strata_bridge
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ```python
24
+ from strata_bridge import StrataModelAdapter, ModelSpec, TrainingConfig, LocalTrainResult, InferenceResult
25
+ import pandas as pd
26
+
27
+ class MyModel(StrataModelAdapter):
28
+
29
+ def get_model_spec(self) -> ModelSpec:
30
+ return ModelSpec(
31
+ model_id="my-model-v1",
32
+ name="My Federated Model",
33
+ version="1.0.0",
34
+ input_columns=["age", "length_of_stay", "drg_weight"],
35
+ output_names=["risk_score"],
36
+ min_samples=50,
37
+ )
38
+
39
+ def initialize(self, config: dict) -> None:
40
+ # load or build your model here
41
+ pass
42
+
43
+ def get_weights(self) -> bytes:
44
+ # serialize model weights
45
+ return b""
46
+
47
+ def set_weights(self, weights: bytes) -> None:
48
+ # deserialize and apply global weights
49
+ pass
50
+
51
+ def local_train(self, data: pd.DataFrame, round_number: int, config: TrainingConfig) -> LocalTrainResult:
52
+ # train on local data, return updated weights + metrics
53
+ return LocalTrainResult(weights=self.get_weights(), sample_count=len(data), loss=0.0)
54
+
55
+ def local_inference(self, data: pd.DataFrame) -> InferenceResult:
56
+ # run inference, return aggregate distribution only
57
+ return InferenceResult(prediction_distribution={"low": len(data)}, sample_count=len(data))
58
+ ```
59
+
60
+ Your `Dockerfile`:
61
+
62
+ ```dockerfile
63
+ FROM python:3.11-slim
64
+ WORKDIR /app
65
+ RUN pip install strata_bridge torch --index-url https://download.pytorch.org/whl/cpu
66
+ COPY model.py .
67
+ CMD ["python", "-m", "strata_bridge.run"]
68
+ ```
69
+
70
+ ## Data Schema
71
+
72
+ Your container receives local patient data at `/data/input.parquet` as a privacy-safe DataFrame. No PII is ever exposed.
73
+
74
+ | Column | Type | Description |
75
+ |--------|------|-------------|
76
+ | `patient_id` | str | Anonymized hash |
77
+ | `age` | int | Computed from DOB |
78
+ | `gender` | str | MALE / FEMALE / OTHER / UNKNOWN |
79
+ | `admission_type` | str | EMERGENCY / ELECTIVE / TRANSFER / AMBULATORY |
80
+ | `length_of_stay` | int | Days |
81
+ | `primary_diagnosis` | str | ICD-10 code |
82
+ | `drg_code` | str | SwissDRG code |
83
+ | `drg_weight` | float | DRG cost weight |
84
+ | `num_procedures` | int | Count |
85
+ | `num_medications` | int | Count |
86
+ | `num_lab_results` | int | Count |
87
+
88
+ ## Documentation
89
+
90
+ Full sponsor guide: [BYOM_SPONSOR_SDK.md](https://github.com/intheris-health/strata-platform/blob/main/docs/user-guides/BYOM_SPONSOR_SDK.md)
91
+
92
+ ## License
93
+
94
+ Proprietary — Intheris Health. Contact [intheris.health@gmail.com](mailto:intheris.health@gmail.com) for access.
@@ -0,0 +1,39 @@
1
+ [project]
2
+ name = "strata_bridge"
3
+ version = "1.0.0"
4
+ description = "Strata BYOM SDK — StrataBridge and StrataModelAdapter for sponsor containers"
5
+ readme = "README.md"
6
+ license = "LicenseRef-Proprietary"
7
+ requires-python = ">=3.10"
8
+ authors = [
9
+ { name = "Intheris Health", email = "intheris.health@gmail.com" },
10
+ ]
11
+ keywords = ["federated-learning", "healthcare", "BYOM", "strata"]
12
+ classifiers = [
13
+ "Development Status :: 4 - Beta",
14
+ "Intended Audience :: Developers",
15
+ "Intended Audience :: Science/Research",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.10",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
21
+ "Topic :: Scientific/Engineering :: Medical Science Apps.",
22
+ ]
23
+ dependencies = [
24
+ "pandas>=1.5",
25
+ "pyarrow>=12.0",
26
+ ]
27
+
28
+ [project.urls]
29
+ Homepage = "https://intheris.health"
30
+ Documentation = "https://github.com/intheris-health/strata-platform/blob/main/docs/user-guides/BYOM_SPONSOR_SDK.md"
31
+ "Bug Tracker" = "https://github.com/intheris-health/strata-platform/issues"
32
+
33
+ [build-system]
34
+ requires = ["setuptools>=68", "wheel"]
35
+ build-backend = "setuptools.build_meta"
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["src"]
39
+ include = ["strata_bridge*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,28 @@
1
+ """
2
+ strata_bridge — Strata BYOM SDK for sponsor containers.
3
+
4
+ Quick start::
5
+
6
+ from strata_bridge import StrataBridge, StrataModelAdapter
7
+ from strata_bridge import ModelSpec, TrainingConfig, LocalTrainResult, InferenceResult
8
+ """
9
+
10
+ from .adapter import (
11
+ ContainerResourceRequirements,
12
+ InferenceResult,
13
+ LocalTrainResult,
14
+ ModelSpec,
15
+ StrataModelAdapter,
16
+ TrainingConfig,
17
+ )
18
+ from .bridge import StrataBridge
19
+
20
+ __all__ = [
21
+ "StrataBridge",
22
+ "StrataModelAdapter",
23
+ "ModelSpec",
24
+ "TrainingConfig",
25
+ "LocalTrainResult",
26
+ "InferenceResult",
27
+ "ContainerResourceRequirements",
28
+ ]
@@ -0,0 +1,231 @@
1
+ # strata_bridge/adapter.py
2
+ """
3
+ StrataModelAdapter — abstract base class that sponsors must implement.
4
+
5
+ Strata calls these methods during federated training and inference rounds.
6
+ All methods receive a pre-filtered, anonymised pandas DataFrame; no raw
7
+ patient data ever leaves the hospital.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from abc import ABC, abstractmethod
13
+ from typing import Any, Dict, List, Optional
14
+
15
+ import pandas as pd
16
+ from pydantic import BaseModel, Field
17
+
18
+
19
+ # ---------------------------------------------------------------------------
20
+ # Schemas passed to / returned from adapter methods
21
+ # ---------------------------------------------------------------------------
22
+
23
+
24
+ class ContainerResourceRequirements(BaseModel):
25
+ memory_mb: int = Field(default=8192, description="Memory limit in MB")
26
+ cpu_cores: float = Field(default=4.0, description="CPU core limit")
27
+ timeout_seconds: int = Field(default=3600, description="Max execution time")
28
+ gpu_enabled: bool = False
29
+
30
+
31
+ class ModelSpec(BaseModel):
32
+ """Metadata returned by StrataModelAdapter.get_model_spec()."""
33
+
34
+ model_id: str
35
+ name: str
36
+ version: str = "1.0.0"
37
+ description: Optional[str] = None
38
+
39
+ # Columns your model expects in the input DataFrame
40
+ input_columns: List[str]
41
+ input_dtypes: Dict[str, str] = Field(default_factory=dict)
42
+
43
+ # Names of output tensors / prediction columns
44
+ output_names: List[str] = Field(default=["prediction"])
45
+
46
+ # Minimum number of local samples required to run
47
+ min_samples: int = 50
48
+
49
+ resource_requirements: ContainerResourceRequirements = Field(
50
+ default_factory=ContainerResourceRequirements
51
+ )
52
+
53
+
54
+ class TrainingConfig(BaseModel):
55
+ """Hyperparameters for a single local training round."""
56
+
57
+ epochs: int = Field(default=1, ge=1, le=100)
58
+ batch_size: int = Field(default=32, ge=1, le=1024)
59
+ learning_rate: float = Field(default=0.001, ge=1e-6, le=1.0)
60
+ weight_decay: float = Field(default=0.0, ge=0.0)
61
+ dropout: float = Field(default=0.0, ge=0.0, le=0.9)
62
+
63
+ # Differential privacy (optional)
64
+ apply_differential_privacy: bool = False
65
+ dp_epsilon: float = Field(default=1.0, ge=0.1, le=10.0)
66
+ dp_delta: float = Field(default=1e-5)
67
+ dp_max_grad_norm: float = Field(default=1.0, ge=0.1)
68
+
69
+
70
+ class LocalTrainResult(BaseModel):
71
+ """
72
+ Result returned by StrataModelAdapter.local_train().
73
+
74
+ Only weights and aggregate metrics leave the site — never per-patient data.
75
+ """
76
+
77
+ weights: bytes = Field(..., description="Serialised model weights after local training")
78
+ sample_count: int = Field(..., ge=1, description="Number of local samples used")
79
+ loss: float = Field(..., description="Final training loss")
80
+ metrics: Dict[str, float] = Field(default_factory=dict, description="Extra metrics")
81
+ epochs_completed: int = 1
82
+ early_stopped: bool = False
83
+
84
+
85
+ class InferenceResult(BaseModel):
86
+ """
87
+ Result returned by StrataModelAdapter.local_inference().
88
+
89
+ Must contain only aggregated statistics — no per-patient predictions.
90
+ """
91
+
92
+ # Aggregate distribution of predictions (histogram, percentiles, class counts)
93
+ prediction_distribution: Dict[str, Any] = Field(default_factory=dict)
94
+
95
+ sample_count: int = 0
96
+ prediction_count: int = 0
97
+
98
+ # Optional privacy-safe binned result counts
99
+ binned_results: Optional[Dict[str, int]] = None
100
+ mean_confidence: Optional[float] = None
101
+
102
+
103
+ # ---------------------------------------------------------------------------
104
+ # Abstract adapter interface
105
+ # ---------------------------------------------------------------------------
106
+
107
+
108
+ class StrataModelAdapter(ABC):
109
+ """
110
+ Abstract interface that sponsors must implement in their Docker container.
111
+
112
+ Strata provides:
113
+ - Multi-site federation and round coordination
114
+ - Privacy protection (differential privacy, k-anonymity)
115
+ - Data governance and consent enforcement
116
+ - Regulatory compliance (LPD/GDPR)
117
+
118
+ Sponsors provide:
119
+ - Model architecture
120
+ - Training logic (local_train)
121
+ - Inference logic (local_inference)
122
+
123
+ Minimal implementation skeleton::
124
+
125
+ class MyModel(StrataModelAdapter):
126
+
127
+ def __init__(self):
128
+ self.model = None # your model object
129
+
130
+ def get_model_spec(self) -> ModelSpec:
131
+ return ModelSpec(
132
+ model_id="my-model-v1",
133
+ name="My Federated Model",
134
+ version="1.0.0",
135
+ input_columns=["age", "drg_weight", "length_of_stay"],
136
+ min_samples=100,
137
+ )
138
+
139
+ def initialize(self, config: dict) -> None:
140
+ # Build / reset model architecture
141
+ ...
142
+
143
+ def get_weights(self) -> bytes:
144
+ import pickle
145
+ return pickle.dumps(self.model.state_dict())
146
+
147
+ def set_weights(self, weights: bytes) -> None:
148
+ import pickle
149
+ self.model.load_state_dict(pickle.loads(weights))
150
+
151
+ def local_train(self, data, round_number, config) -> LocalTrainResult:
152
+ # Train on data, return weights + metrics
153
+ ...
154
+
155
+ def local_inference(self, data) -> InferenceResult:
156
+ # Run predictions, return aggregated distribution only
157
+ ...
158
+ """
159
+
160
+ @abstractmethod
161
+ def get_model_spec(self) -> ModelSpec:
162
+ """Return model metadata and resource requirements."""
163
+
164
+ @abstractmethod
165
+ def initialize(self, config: Dict[str, Any]) -> None:
166
+ """
167
+ Initialise model architecture.
168
+
169
+ Called once before any training or inference.
170
+
171
+ Args:
172
+ config: The full config dict from StrataBridge.get_config().
173
+ """
174
+
175
+ @abstractmethod
176
+ def get_weights(self) -> bytes:
177
+ """Serialise current model weights to bytes."""
178
+
179
+ @abstractmethod
180
+ def set_weights(self, weights: bytes) -> None:
181
+ """Load model weights from bytes (e.g. from the previous global round)."""
182
+
183
+ @abstractmethod
184
+ def local_train(
185
+ self,
186
+ data: pd.DataFrame,
187
+ round_number: int,
188
+ config: TrainingConfig,
189
+ ) -> LocalTrainResult:
190
+ """
191
+ Execute one local training round.
192
+
193
+ IMPORTANT: ``data`` is a read-only view of local patient records.
194
+ It must never be transmitted or persisted outside this function.
195
+ Only weights and aggregate metrics should be returned.
196
+
197
+ Args:
198
+ data: Local cohort DataFrame (14 anonymised columns).
199
+ round_number: Current federated round (0-based).
200
+ config: Training hyperparameters.
201
+
202
+ Returns:
203
+ LocalTrainResult with updated weights and aggregate metrics.
204
+ """
205
+
206
+ @abstractmethod
207
+ def local_inference(self, data: pd.DataFrame) -> InferenceResult:
208
+ """
209
+ Run inference on local data.
210
+
211
+ Returns aggregated prediction statistics only — no per-patient rows.
212
+
213
+ Args:
214
+ data: Local cohort DataFrame.
215
+
216
+ Returns:
217
+ InferenceResult with prediction distribution.
218
+ """
219
+
220
+ def local_evaluate(
221
+ self,
222
+ data: pd.DataFrame,
223
+ labels: pd.Series,
224
+ ) -> Dict[str, float]:
225
+ """
226
+ Optional: evaluate model on labelled local data.
227
+
228
+ Returns aggregate evaluation metrics (accuracy, AUC, etc.).
229
+ Not required for Phase 1 / Phase 2.
230
+ """
231
+ raise NotImplementedError("local_evaluate is optional and not implemented")
@@ -0,0 +1,147 @@
1
+ # strata_bridge/bridge.py
2
+ """
3
+ StrataBridge — file-based communication channel between the Strata host and
4
+ a sponsor's container.
5
+
6
+ The host mounts two directories into the container:
7
+ /data (read-only) — input.parquet, weights.bin, config.json
8
+ /output (read-write) — weights.bin (required), metrics.json (optional)
9
+
10
+ Sponsors use this class to read inputs and write outputs. There is no
11
+ network access inside the container; all I/O goes through these mounts.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import os
18
+ from typing import Any, Dict
19
+
20
+ import pandas as pd
21
+
22
+
23
+ class StrataBridge:
24
+ """
25
+ Static helpers for reading inputs from /data and writing outputs to /output.
26
+
27
+ All methods are static — import and call directly without instantiation.
28
+
29
+ Example::
30
+
31
+ from strata_bridge import StrataBridge
32
+
33
+ data = StrataBridge.get_data()
34
+ config = StrataBridge.get_config()
35
+ weights = StrataBridge.get_weights()
36
+
37
+ # ... train ...
38
+
39
+ StrataBridge.save_weights(updated_weights_bytes)
40
+ StrataBridge.save_metrics({"loss": 0.32, "samples": 420})
41
+ """
42
+
43
+ # Paths inside the container (configured by the host)
44
+ DATA_PATH: str = os.environ.get("STRATA_DATA_PATH", "/data/input.parquet")
45
+ WEIGHTS_IN_PATH: str = os.environ.get("STRATA_WEIGHTS_IN_PATH", "/data/weights.bin")
46
+ CONFIG_PATH: str = os.environ.get("STRATA_CONFIG_PATH", "/data/config.json")
47
+ WEIGHTS_OUT_PATH: str = os.environ.get("STRATA_WEIGHTS_OUT_PATH", "/output/weights.bin")
48
+ METRICS_PATH: str = os.environ.get("STRATA_METRICS_PATH", "/output/metrics.json")
49
+
50
+ # ---------------------------------------------------------------------------
51
+ # Inputs
52
+ # ---------------------------------------------------------------------------
53
+
54
+ @staticmethod
55
+ def get_data() -> pd.DataFrame:
56
+ """
57
+ Load the local patient data as a DataFrame.
58
+
59
+ The DataFrame is pre-filtered to the study cohort and contains only
60
+ privacy-safe, non-PII columns. It is always read-only from the
61
+ container's perspective.
62
+
63
+ Returns:
64
+ pandas DataFrame with 14 anonymised columns (see data schema docs).
65
+ """
66
+ return pd.read_parquet(StrataBridge.DATA_PATH)
67
+
68
+ @staticmethod
69
+ def get_weights() -> bytes:
70
+ """
71
+ Load current global model weights sent by the Control Plane.
72
+
73
+ For round 0 the file is still present but empty (b""), so always
74
+ check ``len(weights) > 0`` before loading.
75
+
76
+ Returns:
77
+ Raw bytes of the serialised weights, or b"" for round 0.
78
+ """
79
+ path = StrataBridge.WEIGHTS_IN_PATH
80
+ if not os.path.exists(path):
81
+ return b""
82
+ with open(path, "rb") as fh:
83
+ return fh.read()
84
+
85
+ @staticmethod
86
+ def get_config() -> Dict[str, Any]:
87
+ """
88
+ Load the training / inference configuration dict.
89
+
90
+ Standard keys provided by Strata:
91
+ mode "train" | "inference"
92
+ round_number int (0-based)
93
+ epochs int
94
+ batch_size int
95
+ learning_rate float
96
+ adapter_class str fully-qualified class to instantiate
97
+ site_id str
98
+
99
+ Returns:
100
+ dict with training hyperparameters and run metadata.
101
+ """
102
+ with open(StrataBridge.CONFIG_PATH, "r") as fh:
103
+ return json.load(fh)
104
+
105
+ # ---------------------------------------------------------------------------
106
+ # Outputs
107
+ # ---------------------------------------------------------------------------
108
+
109
+ @staticmethod
110
+ def save_weights(weights: bytes) -> None:
111
+ """
112
+ Write updated model weights to /output/weights.bin.
113
+
114
+ This file **must** be written before the container exits, otherwise
115
+ the host will treat the round as failed.
116
+
117
+ Args:
118
+ weights: Serialised model weights (e.g. pickle.dumps(state_dict)).
119
+ """
120
+ os.makedirs(os.path.dirname(StrataBridge.WEIGHTS_OUT_PATH), exist_ok=True)
121
+ with open(StrataBridge.WEIGHTS_OUT_PATH, "wb") as fh:
122
+ fh.write(weights)
123
+
124
+ @staticmethod
125
+ def save_metrics(metrics: Dict[str, Any]) -> None:
126
+ """
127
+ Write training / inference metrics to /output/metrics.json.
128
+
129
+ Optional but strongly recommended. Strata reads this file and
130
+ returns the values alongside the aggregated weights.
131
+
132
+ Recommended keys:
133
+ loss float — final training loss
134
+ samples int — number of local samples used
135
+ accuracy float — (optional) accuracy or other metric
136
+
137
+ Args:
138
+ metrics: JSON-serialisable dict.
139
+ """
140
+ os.makedirs(os.path.dirname(StrataBridge.METRICS_PATH), exist_ok=True)
141
+ with open(StrataBridge.METRICS_PATH, "w") as fh:
142
+ json.dump(metrics, fh)
143
+
144
+ @staticmethod
145
+ def log(message: str) -> None:
146
+ """Print a log message to stdout (captured by Docker and returned on error)."""
147
+ print(f"[strata_bridge] {message}", flush=True)
@@ -0,0 +1,148 @@
1
+ # strata_bridge/run.py
2
+ """
3
+ Container entrypoint dispatcher.
4
+
5
+ Strata runs this module as ``python -m strata_bridge.run`` inside the
6
+ sponsor's Docker container. It reads config.json from /data, dynamically
7
+ imports the sponsor's StrataModelAdapter subclass, and calls either
8
+ local_train() or local_inference() depending on the ``mode`` field.
9
+
10
+ The sponsor does NOT need to modify this file — they implement their adapter
11
+ class and declare its fully-qualified name in their container registration
12
+ (adapter_class field, default: "model.MyModel").
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import importlib
18
+ import sys
19
+ import traceback
20
+
21
+ from .adapter import InferenceResult, LocalTrainResult, TrainingConfig
22
+ from .bridge import StrataBridge
23
+
24
+
25
+ def _import_adapter(adapter_class: str):
26
+ """Dynamically import sponsor's adapter class."""
27
+ module_name, class_name = adapter_class.rsplit(".", 1)
28
+ try:
29
+ module = importlib.import_module(module_name)
30
+ except ModuleNotFoundError as exc:
31
+ raise ImportError(
32
+ f"Could not import module '{module_name}' for adapter '{adapter_class}'. "
33
+ f"Make sure the module is on the Python path inside the container. "
34
+ f"Original error: {exc}"
35
+ ) from exc
36
+
37
+ try:
38
+ cls = getattr(module, class_name)
39
+ except AttributeError:
40
+ raise ImportError(
41
+ f"Module '{module_name}' has no class '{class_name}'. "
42
+ f"Check the adapter_class field in your model registration."
43
+ )
44
+
45
+ return cls
46
+
47
+
48
+ def main() -> None:
49
+ StrataBridge.log("Strata Bridge starting…")
50
+
51
+ try:
52
+ config = StrataBridge.get_config()
53
+ except Exception as exc:
54
+ print(f"[strata_bridge] FATAL: could not read config.json: {exc}", flush=True)
55
+ sys.exit(1)
56
+
57
+ mode = config.get("mode", "train")
58
+ adapter_class = config.get("adapter_class", "model.MyModel")
59
+ round_number = int(config.get("round_number", 0))
60
+
61
+ StrataBridge.log(f"mode={mode} round={round_number} adapter={adapter_class}")
62
+
63
+ try:
64
+ AdapterClass = _import_adapter(adapter_class)
65
+ adapter = AdapterClass()
66
+ adapter.initialize(config)
67
+ except Exception as exc:
68
+ print(f"[strata_bridge] FATAL: failed to initialise adapter: {exc}", flush=True)
69
+ traceback.print_exc()
70
+ sys.exit(1)
71
+
72
+ # Load global weights (may be empty on round 0)
73
+ try:
74
+ weights_in = StrataBridge.get_weights()
75
+ if weights_in:
76
+ adapter.set_weights(weights_in)
77
+ StrataBridge.log(f"Loaded global weights ({len(weights_in):,} bytes)")
78
+ else:
79
+ StrataBridge.log("No global weights provided (round 0 — using fresh model)")
80
+ except Exception as exc:
81
+ print(f"[strata_bridge] FATAL: failed to load weights: {exc}", flush=True)
82
+ traceback.print_exc()
83
+ sys.exit(1)
84
+
85
+ try:
86
+ data = StrataBridge.get_data()
87
+ StrataBridge.log(f"Loaded data: {len(data)} rows × {len(data.columns)} columns")
88
+ except Exception as exc:
89
+ print(f"[strata_bridge] FATAL: failed to load data: {exc}", flush=True)
90
+ traceback.print_exc()
91
+ sys.exit(1)
92
+
93
+ if mode == "train":
94
+ try:
95
+ train_cfg = TrainingConfig(
96
+ epochs=int(config.get("epochs", 1)),
97
+ batch_size=int(config.get("batch_size", 32)),
98
+ learning_rate=float(config.get("learning_rate", 0.001)),
99
+ )
100
+ result: LocalTrainResult = adapter.local_train(data, round_number, train_cfg)
101
+ except Exception as exc:
102
+ print(f"[strata_bridge] FATAL: local_train failed: {exc}", flush=True)
103
+ traceback.print_exc()
104
+ sys.exit(1)
105
+
106
+ StrataBridge.save_weights(result.weights)
107
+ StrataBridge.save_metrics(
108
+ {
109
+ "loss": result.loss,
110
+ "samples": result.sample_count,
111
+ "epochs_completed": result.epochs_completed,
112
+ **result.metrics,
113
+ }
114
+ )
115
+ StrataBridge.log(
116
+ f"Training complete — loss={result.loss:.4f} samples={result.sample_count}"
117
+ )
118
+
119
+ elif mode == "inference":
120
+ try:
121
+ result_inf: InferenceResult = adapter.local_inference(data)
122
+ except Exception as exc:
123
+ print(f"[strata_bridge] FATAL: local_inference failed: {exc}", flush=True)
124
+ traceback.print_exc()
125
+ sys.exit(1)
126
+
127
+ # Inference mode: write a placeholder weights file (required by host) and metrics
128
+ StrataBridge.save_weights(b"inference-mode")
129
+ StrataBridge.save_metrics(
130
+ {
131
+ "mode": "inference",
132
+ "sample_count": result_inf.sample_count,
133
+ **result_inf.prediction_distribution,
134
+ }
135
+ )
136
+ StrataBridge.log(
137
+ f"Inference complete — {result_inf.sample_count} samples processed"
138
+ )
139
+
140
+ else:
141
+ print(f"[strata_bridge] FATAL: unknown mode '{mode}'", flush=True)
142
+ sys.exit(1)
143
+
144
+ StrataBridge.log("Done.")
145
+
146
+
147
+ if __name__ == "__main__":
148
+ main()
@@ -0,0 +1,118 @@
1
+ Metadata-Version: 2.4
2
+ Name: strata_bridge
3
+ Version: 1.0.0
4
+ Summary: Strata BYOM SDK — StrataBridge and StrataModelAdapter for sponsor containers
5
+ Author-email: Intheris Health <intheris.health@gmail.com>
6
+ License-Expression: LicenseRef-Proprietary
7
+ Project-URL: Homepage, https://intheris.health
8
+ Project-URL: Documentation, https://github.com/intheris-health/strata-platform/blob/main/docs/user-guides/BYOM_SPONSOR_SDK.md
9
+ Project-URL: Bug Tracker, https://github.com/intheris-health/strata-platform/issues
10
+ Keywords: federated-learning,healthcare,BYOM,strata
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: pandas>=1.5
23
+ Requires-Dist: pyarrow>=12.0
24
+
25
+ # strata_bridge
26
+
27
+ **Strata BYOM SDK** — the bridge between your ML model and the [Strata federated healthcare platform](https://intheris.health) by Intheris Health.
28
+
29
+ ## Overview
30
+
31
+ `strata_bridge` is the Python package your Docker container uses when participating in federated training or inference across hospital sites. It provides:
32
+
33
+ - **`StrataBridge`** — file-based I/O helpers (`/data` → `/output`)
34
+ - **`StrataModelAdapter`** — the interface your model class must implement
35
+ - **`run.py`** — the container entrypoint dispatcher (`python -m strata_bridge.run`)
36
+
37
+ Core principle: **algorithms travel, data stays local.** Patient data never leaves the hospital; your container receives a privacy-safe DataFrame and returns only model weights or aggregate statistics.
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ pip install strata_bridge
43
+ ```
44
+
45
+ ## Quick Start
46
+
47
+ ```python
48
+ from strata_bridge import StrataModelAdapter, ModelSpec, TrainingConfig, LocalTrainResult, InferenceResult
49
+ import pandas as pd
50
+
51
+ class MyModel(StrataModelAdapter):
52
+
53
+ def get_model_spec(self) -> ModelSpec:
54
+ return ModelSpec(
55
+ model_id="my-model-v1",
56
+ name="My Federated Model",
57
+ version="1.0.0",
58
+ input_columns=["age", "length_of_stay", "drg_weight"],
59
+ output_names=["risk_score"],
60
+ min_samples=50,
61
+ )
62
+
63
+ def initialize(self, config: dict) -> None:
64
+ # load or build your model here
65
+ pass
66
+
67
+ def get_weights(self) -> bytes:
68
+ # serialize model weights
69
+ return b""
70
+
71
+ def set_weights(self, weights: bytes) -> None:
72
+ # deserialize and apply global weights
73
+ pass
74
+
75
+ def local_train(self, data: pd.DataFrame, round_number: int, config: TrainingConfig) -> LocalTrainResult:
76
+ # train on local data, return updated weights + metrics
77
+ return LocalTrainResult(weights=self.get_weights(), sample_count=len(data), loss=0.0)
78
+
79
+ def local_inference(self, data: pd.DataFrame) -> InferenceResult:
80
+ # run inference, return aggregate distribution only
81
+ return InferenceResult(prediction_distribution={"low": len(data)}, sample_count=len(data))
82
+ ```
83
+
84
+ Your `Dockerfile`:
85
+
86
+ ```dockerfile
87
+ FROM python:3.11-slim
88
+ WORKDIR /app
89
+ RUN pip install strata_bridge torch --index-url https://download.pytorch.org/whl/cpu
90
+ COPY model.py .
91
+ CMD ["python", "-m", "strata_bridge.run"]
92
+ ```
93
+
94
+ ## Data Schema
95
+
96
+ Your container receives local patient data at `/data/input.parquet` as a privacy-safe DataFrame. No PII is ever exposed.
97
+
98
+ | Column | Type | Description |
99
+ |--------|------|-------------|
100
+ | `patient_id` | str | Anonymized hash |
101
+ | `age` | int | Computed from DOB |
102
+ | `gender` | str | MALE / FEMALE / OTHER / UNKNOWN |
103
+ | `admission_type` | str | EMERGENCY / ELECTIVE / TRANSFER / AMBULATORY |
104
+ | `length_of_stay` | int | Days |
105
+ | `primary_diagnosis` | str | ICD-10 code |
106
+ | `drg_code` | str | SwissDRG code |
107
+ | `drg_weight` | float | DRG cost weight |
108
+ | `num_procedures` | int | Count |
109
+ | `num_medications` | int | Count |
110
+ | `num_lab_results` | int | Count |
111
+
112
+ ## Documentation
113
+
114
+ Full sponsor guide: [BYOM_SPONSOR_SDK.md](https://github.com/intheris-health/strata-platform/blob/main/docs/user-guides/BYOM_SPONSOR_SDK.md)
115
+
116
+ ## License
117
+
118
+ Proprietary — Intheris Health. Contact [intheris.health@gmail.com](mailto:intheris.health@gmail.com) for access.
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/strata_bridge/__init__.py
4
+ src/strata_bridge/adapter.py
5
+ src/strata_bridge/bridge.py
6
+ src/strata_bridge/run.py
7
+ src/strata_bridge.egg-info/PKG-INFO
8
+ src/strata_bridge.egg-info/SOURCES.txt
9
+ src/strata_bridge.egg-info/dependency_links.txt
10
+ src/strata_bridge.egg-info/requires.txt
11
+ src/strata_bridge.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ pandas>=1.5
2
+ pyarrow>=12.0
@@ -0,0 +1 @@
1
+ strata_bridge