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.
- strata_bridge-1.0.0/PKG-INFO +118 -0
- strata_bridge-1.0.0/README.md +94 -0
- strata_bridge-1.0.0/pyproject.toml +39 -0
- strata_bridge-1.0.0/setup.cfg +4 -0
- strata_bridge-1.0.0/src/strata_bridge/__init__.py +28 -0
- strata_bridge-1.0.0/src/strata_bridge/adapter.py +231 -0
- strata_bridge-1.0.0/src/strata_bridge/bridge.py +147 -0
- strata_bridge-1.0.0/src/strata_bridge/run.py +148 -0
- strata_bridge-1.0.0/src/strata_bridge.egg-info/PKG-INFO +118 -0
- strata_bridge-1.0.0/src/strata_bridge.egg-info/SOURCES.txt +11 -0
- strata_bridge-1.0.0/src/strata_bridge.egg-info/dependency_links.txt +1 -0
- strata_bridge-1.0.0/src/strata_bridge.egg-info/requires.txt +2 -0
- strata_bridge-1.0.0/src/strata_bridge.egg-info/top_level.txt +1 -0
|
@@ -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,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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
strata_bridge
|