nodae_bridge 1.0.5__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,221 @@
1
+ Metadata-Version: 2.4
2
+ Name: nodae_bridge
3
+ Version: 1.0.5
4
+ Summary: Nodae BYOM SDK — NodaeBridge and NodaeModelAdapter for sponsor containers
5
+ Author-email: Intheris Health <intheris.health@gmail.com>
6
+ License-Expression: LicenseRef-Proprietary
7
+ Project-URL: Homepage, https://www.intheris-health.com
8
+ Keywords: federated-learning,healthcare,BYOM,nodae
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
17
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ Requires-Dist: pandas>=1.5
21
+ Requires-Dist: pyarrow>=12.0
22
+ Requires-Dist: pydantic>=2.0
23
+
24
+ # nodae_bridge
25
+
26
+ **Nodae BYOM SDK v1.0.4** — the bridge between your ML model and the [Nodae federated healthcare platform](https://www.intheris-health.com) by Intheris Health.
27
+
28
+ ## Overview
29
+
30
+ `nodae_bridge` is the Python package your Docker container uses when participating in federated training or inference across hospital sites. It provides:
31
+
32
+ - **`NodaeBridge`** — file-based I/O helpers (`/data` → `/output`)
33
+ - **`NodaeModelAdapter`** — the interface your model class must implement
34
+ - **`run.py`** — the container entrypoint dispatcher (`python -m nodae_bridge.run`)
35
+
36
+ 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.
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ pip install nodae_bridge
42
+ ```
43
+
44
+ Minimum Python version: **3.10**. Add it alongside your ML framework in your Dockerfile:
45
+
46
+ ```dockerfile
47
+ RUN pip install nodae_bridge scikit-learn # or torch, xgboost, etc.
48
+ ```
49
+
50
+ ## Quick Start
51
+
52
+ ```python
53
+ from nodae_bridge import NodaeModelAdapter, ModelSpec, TrainingConfig, LocalTrainResult, InferenceResult
54
+ import pandas as pd
55
+
56
+ class MyModel(NodaeModelAdapter):
57
+
58
+ def get_model_spec(self) -> ModelSpec:
59
+ return ModelSpec(
60
+ model_id="my-model-v1",
61
+ name="My Federated Model",
62
+ version="1.0.0",
63
+ input_columns=["age", "length_of_stay", "drg_weight"],
64
+ output_names=["risk_score"],
65
+ min_samples=50,
66
+ )
67
+
68
+ def initialize(self, config: dict) -> None:
69
+ pass
70
+
71
+ def get_weights(self) -> bytes:
72
+ return b""
73
+
74
+ def set_weights(self, weights: bytes) -> None:
75
+ pass
76
+
77
+ def local_train(self, data: pd.DataFrame, round_number: int, config: TrainingConfig) -> LocalTrainResult:
78
+ return LocalTrainResult(weights=self.get_weights(), sample_count=len(data), loss=0.0)
79
+
80
+ def local_inference(self, data: pd.DataFrame) -> InferenceResult:
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 nodae_bridge torch --index-url https://download.pytorch.org/whl/cpu
90
+ COPY model.py .
91
+ CMD ["python", "-m", "nodae_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 SHA-256 hash |
101
+ | `age` | int | Computed from DOB |
102
+ | `gender` | str | MALE / FEMALE / OTHER / UNKNOWN |
103
+ | `admission_type` | str | EMERGENCY / ELECTIVE / TRANSFER / AMBULATORY |
104
+ | `discharge_disposition` | str | HOME / TRANSFER / DECEASED / REHABILITATION |
105
+ | `length_of_stay` | int | Hospitalization days |
106
+ | `primary_diagnosis` | str | ICD-10 code |
107
+ | `num_secondary_diagnoses` | int | Count |
108
+ | `drg_code` | str | SwissDRG code |
109
+ | `drg_weight` | float | DRG cost weight |
110
+ | `num_procedures` | int | Count |
111
+ | `num_medications` | int | Count |
112
+ | `num_lab_results` | int | Count |
113
+ | `insurance_type` | str | Swiss insurance category |
114
+
115
+ Always call `fillna()` before passing data to your model — not all columns are populated for every patient.
116
+
117
+ ## API Reference
118
+
119
+ ### Core I/O
120
+
121
+ | Method | Description |
122
+ |--------|-------------|
123
+ | `NodaeBridge.get_data()` | Load `/data/input.parquet` as a DataFrame |
124
+ | `NodaeBridge.get_weights()` | Load global model weights; `b""` on round 0 |
125
+ | `NodaeBridge.get_config()` | Load training/inference config dict |
126
+ | `NodaeBridge.get_site_metadata()` | Convenience wrapper for `config["site_metadata"]` |
127
+ | `NodaeBridge.save_weights(bytes)` | Write updated weights to `/output/weights.bin` (**required**) |
128
+ | `NodaeBridge.save_metrics(dict)` | Write aggregate metrics to `/output/metrics.json` |
129
+ | `NodaeBridge.log(str)` | Print timestamped message to stdout |
130
+
131
+ ### Extended scope (`byom_data_scope="extended"`)
132
+
133
+ Register with `byom_data_scope="extended"` to receive additional raw data files. Returns empty DataFrame/dict when absent (standard scope), so imports are always safe.
134
+
135
+ | Method | Returns | Content |
136
+ |--------|---------|---------|
137
+ | `NodaeBridge.get_labs()` | DataFrame | Per-result lab data: `patient_id, loinc_code, test_name, value, unit, flag, date, …` |
138
+ | `NodaeBridge.get_vitals()` | DataFrame | Latest vitals per patient: `bp_systolic, heart_rate, temperature, weight_kg, …` |
139
+ | `NodaeBridge.get_procedures()` | DataFrame | Per-procedure: `patient_id, code, description, date, source` |
140
+ | `NodaeBridge.get_signals()` | dict | Study-specific template signals: `{patient_id: {signal_key: value}}` |
141
+
142
+ Feature engineering is the container's responsibility — the platform exposes raw data only.
143
+
144
+ ### FHIR scope (`byom_data_scope="fhir"`)
145
+
146
+ Available when registered with `byom_data_scope="fhir"`. The host writes one FHIR R4 Bundle per cohort patient under `/data/fhir/`. The standard `input.parquet` is always present alongside the bundles.
147
+
148
+ | Method | Returns | Description |
149
+ |--------|---------|-------------|
150
+ | `NodaeBridge.list_fhir_patients()` | `list[str]` | Patient IDs (anonymized hashes) with FHIR bundles; reads `index.json` |
151
+ | `NodaeBridge.get_fhir_bundle(patient_id)` | `Optional[dict]` | Full FHIR R4 Bundle dict for one patient, or `None` |
152
+
153
+ Each bundle contains: `Patient` (age, gender, LOS, DRG — no PHI), `Condition`s (ICD-10 + SNOMED), `Observation`s (LOINC-coded labs and vitals), `Procedure`s (CHOP), `MedicationStatement`s, `AllergyIntolerance`s, a `Basic` resource for template_signals, and an `ImagingStudy` reference when DICOM data is present.
154
+
155
+ ```python
156
+ for pid in NodaeBridge.list_fhir_patients():
157
+ bundle = NodaeBridge.get_fhir_bundle(pid)
158
+ entries = {e["resource"]["resourceType"]: e["resource"] for e in bundle["entry"]}
159
+ # access any field directly — no platform-imposed field selection
160
+ ```
161
+
162
+ ### SCAFFOLD (`aggregation_strategy="scaffold"`)
163
+
164
+ Used when the study uses SCAFFOLD to correct client drift in non-IID populations.
165
+
166
+ | Method | Description |
167
+ |--------|-------------|
168
+ | `NodaeBridge.get_control_variates()` | Load global control variate from `/data/control_variates.bin`; `b""` when not active |
169
+ | `NodaeBridge.save_control_variate_delta(bytes)` | Write per-site delta to `/output/control_variates.bin` |
170
+
171
+ `config["scaffold_enabled"]` is `true` when the host has sent a global control variate. If `save_control_variate_delta()` is not called, the aggregator falls back to FedAvg for this site (backward compatible).
172
+
173
+ ### Imaging (`imaging_access=True`)
174
+
175
+ Available when registered with `imaging_access=True` and the SA host has `BYOM_DICOM_STORE_ENABLED=True`. A read-only DICOM store is mounted at `/data/imaging/`.
176
+
177
+ | Method | Returns | Description |
178
+ |--------|---------|-------------|
179
+ | `NodaeBridge.get_imaging()` | `Optional[Path]` | Path to `/data/imaging/`, or `None` if unavailable |
180
+ | `NodaeBridge.list_imaging_series()` | `list[dict]` | Manifest: `patient_id, study_uid, modality, slice_count, path` |
181
+
182
+ ```python
183
+ import pydicom
184
+ from pathlib import Path
185
+
186
+ base = NodaeBridge.get_imaging()
187
+ for series in NodaeBridge.list_imaging_series():
188
+ if series["modality"] != "CT":
189
+ continue
190
+ slices = sorted((base / series["path"]).glob("*.dcm"))
191
+ datasets = [pydicom.dcmread(str(s)) for s in slices]
192
+ # HU windowing, segmentation, feature extraction — container's responsibility
193
+ ```
194
+
195
+ ## Changelog
196
+
197
+ ### 1.0.5 (June 2026)
198
+ - Added `list_fhir_patients()`, `get_fhir_bundle()` — FHIR R4 bundle access for `byom_data_scope="fhir"`
199
+ - Added `FHIR_PATH` class constant
200
+
201
+ ### 1.0.4 (June 2026)
202
+ - Added `get_imaging()`, `list_imaging_series()` — DICOM store access
203
+ - Updated `get_site_metadata()` docs to include `imaging.*` keys
204
+
205
+ ### 1.0.3 (June 2026)
206
+ - Added `get_control_variates()`, `save_control_variate_delta()` — SCAFFOLD support
207
+
208
+ ### 1.0.2 (June 2026)
209
+ - Added `get_signals()` — template signal access
210
+ - Added `get_labs()`, `get_vitals()`, `get_procedures()` — extended data scope
211
+
212
+ ### 1.0.1 (June 2026)
213
+ - Added `get_site_metadata()` convenience wrapper
214
+
215
+ ### 1.0.0 (June 2026)
216
+ - Initial release: `NodaeBridge`, `NodaeModelAdapter`, `run.py` entrypoint
217
+
218
+ ## License
219
+
220
+ Proprietary — Intheris Health. Contact [support@intheris-health.com](mailto:support@intheris-health.com) for access.
221
+
@@ -0,0 +1,198 @@
1
+ # nodae_bridge
2
+
3
+ **Nodae BYOM SDK v1.0.4** — the bridge between your ML model and the [Nodae federated healthcare platform](https://www.intheris-health.com) by Intheris Health.
4
+
5
+ ## Overview
6
+
7
+ `nodae_bridge` is the Python package your Docker container uses when participating in federated training or inference across hospital sites. It provides:
8
+
9
+ - **`NodaeBridge`** — file-based I/O helpers (`/data` → `/output`)
10
+ - **`NodaeModelAdapter`** — the interface your model class must implement
11
+ - **`run.py`** — the container entrypoint dispatcher (`python -m nodae_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 nodae_bridge
19
+ ```
20
+
21
+ Minimum Python version: **3.10**. Add it alongside your ML framework in your Dockerfile:
22
+
23
+ ```dockerfile
24
+ RUN pip install nodae_bridge scikit-learn # or torch, xgboost, etc.
25
+ ```
26
+
27
+ ## Quick Start
28
+
29
+ ```python
30
+ from nodae_bridge import NodaeModelAdapter, ModelSpec, TrainingConfig, LocalTrainResult, InferenceResult
31
+ import pandas as pd
32
+
33
+ class MyModel(NodaeModelAdapter):
34
+
35
+ def get_model_spec(self) -> ModelSpec:
36
+ return ModelSpec(
37
+ model_id="my-model-v1",
38
+ name="My Federated Model",
39
+ version="1.0.0",
40
+ input_columns=["age", "length_of_stay", "drg_weight"],
41
+ output_names=["risk_score"],
42
+ min_samples=50,
43
+ )
44
+
45
+ def initialize(self, config: dict) -> None:
46
+ pass
47
+
48
+ def get_weights(self) -> bytes:
49
+ return b""
50
+
51
+ def set_weights(self, weights: bytes) -> None:
52
+ pass
53
+
54
+ def local_train(self, data: pd.DataFrame, round_number: int, config: TrainingConfig) -> LocalTrainResult:
55
+ return LocalTrainResult(weights=self.get_weights(), sample_count=len(data), loss=0.0)
56
+
57
+ def local_inference(self, data: pd.DataFrame) -> InferenceResult:
58
+ return InferenceResult(prediction_distribution={"low": len(data)}, sample_count=len(data))
59
+ ```
60
+
61
+ Your `Dockerfile`:
62
+
63
+ ```dockerfile
64
+ FROM python:3.11-slim
65
+ WORKDIR /app
66
+ RUN pip install nodae_bridge torch --index-url https://download.pytorch.org/whl/cpu
67
+ COPY model.py .
68
+ CMD ["python", "-m", "nodae_bridge.run"]
69
+ ```
70
+
71
+ ## Data Schema
72
+
73
+ Your container receives local patient data at `/data/input.parquet` as a privacy-safe DataFrame. No PII is ever exposed.
74
+
75
+ | Column | Type | Description |
76
+ |--------|------|-------------|
77
+ | `patient_id` | str | Anonymized SHA-256 hash |
78
+ | `age` | int | Computed from DOB |
79
+ | `gender` | str | MALE / FEMALE / OTHER / UNKNOWN |
80
+ | `admission_type` | str | EMERGENCY / ELECTIVE / TRANSFER / AMBULATORY |
81
+ | `discharge_disposition` | str | HOME / TRANSFER / DECEASED / REHABILITATION |
82
+ | `length_of_stay` | int | Hospitalization days |
83
+ | `primary_diagnosis` | str | ICD-10 code |
84
+ | `num_secondary_diagnoses` | int | Count |
85
+ | `drg_code` | str | SwissDRG code |
86
+ | `drg_weight` | float | DRG cost weight |
87
+ | `num_procedures` | int | Count |
88
+ | `num_medications` | int | Count |
89
+ | `num_lab_results` | int | Count |
90
+ | `insurance_type` | str | Swiss insurance category |
91
+
92
+ Always call `fillna()` before passing data to your model — not all columns are populated for every patient.
93
+
94
+ ## API Reference
95
+
96
+ ### Core I/O
97
+
98
+ | Method | Description |
99
+ |--------|-------------|
100
+ | `NodaeBridge.get_data()` | Load `/data/input.parquet` as a DataFrame |
101
+ | `NodaeBridge.get_weights()` | Load global model weights; `b""` on round 0 |
102
+ | `NodaeBridge.get_config()` | Load training/inference config dict |
103
+ | `NodaeBridge.get_site_metadata()` | Convenience wrapper for `config["site_metadata"]` |
104
+ | `NodaeBridge.save_weights(bytes)` | Write updated weights to `/output/weights.bin` (**required**) |
105
+ | `NodaeBridge.save_metrics(dict)` | Write aggregate metrics to `/output/metrics.json` |
106
+ | `NodaeBridge.log(str)` | Print timestamped message to stdout |
107
+
108
+ ### Extended scope (`byom_data_scope="extended"`)
109
+
110
+ Register with `byom_data_scope="extended"` to receive additional raw data files. Returns empty DataFrame/dict when absent (standard scope), so imports are always safe.
111
+
112
+ | Method | Returns | Content |
113
+ |--------|---------|---------|
114
+ | `NodaeBridge.get_labs()` | DataFrame | Per-result lab data: `patient_id, loinc_code, test_name, value, unit, flag, date, …` |
115
+ | `NodaeBridge.get_vitals()` | DataFrame | Latest vitals per patient: `bp_systolic, heart_rate, temperature, weight_kg, …` |
116
+ | `NodaeBridge.get_procedures()` | DataFrame | Per-procedure: `patient_id, code, description, date, source` |
117
+ | `NodaeBridge.get_signals()` | dict | Study-specific template signals: `{patient_id: {signal_key: value}}` |
118
+
119
+ Feature engineering is the container's responsibility — the platform exposes raw data only.
120
+
121
+ ### FHIR scope (`byom_data_scope="fhir"`)
122
+
123
+ Available when registered with `byom_data_scope="fhir"`. The host writes one FHIR R4 Bundle per cohort patient under `/data/fhir/`. The standard `input.parquet` is always present alongside the bundles.
124
+
125
+ | Method | Returns | Description |
126
+ |--------|---------|-------------|
127
+ | `NodaeBridge.list_fhir_patients()` | `list[str]` | Patient IDs (anonymized hashes) with FHIR bundles; reads `index.json` |
128
+ | `NodaeBridge.get_fhir_bundle(patient_id)` | `Optional[dict]` | Full FHIR R4 Bundle dict for one patient, or `None` |
129
+
130
+ Each bundle contains: `Patient` (age, gender, LOS, DRG — no PHI), `Condition`s (ICD-10 + SNOMED), `Observation`s (LOINC-coded labs and vitals), `Procedure`s (CHOP), `MedicationStatement`s, `AllergyIntolerance`s, a `Basic` resource for template_signals, and an `ImagingStudy` reference when DICOM data is present.
131
+
132
+ ```python
133
+ for pid in NodaeBridge.list_fhir_patients():
134
+ bundle = NodaeBridge.get_fhir_bundle(pid)
135
+ entries = {e["resource"]["resourceType"]: e["resource"] for e in bundle["entry"]}
136
+ # access any field directly — no platform-imposed field selection
137
+ ```
138
+
139
+ ### SCAFFOLD (`aggregation_strategy="scaffold"`)
140
+
141
+ Used when the study uses SCAFFOLD to correct client drift in non-IID populations.
142
+
143
+ | Method | Description |
144
+ |--------|-------------|
145
+ | `NodaeBridge.get_control_variates()` | Load global control variate from `/data/control_variates.bin`; `b""` when not active |
146
+ | `NodaeBridge.save_control_variate_delta(bytes)` | Write per-site delta to `/output/control_variates.bin` |
147
+
148
+ `config["scaffold_enabled"]` is `true` when the host has sent a global control variate. If `save_control_variate_delta()` is not called, the aggregator falls back to FedAvg for this site (backward compatible).
149
+
150
+ ### Imaging (`imaging_access=True`)
151
+
152
+ Available when registered with `imaging_access=True` and the SA host has `BYOM_DICOM_STORE_ENABLED=True`. A read-only DICOM store is mounted at `/data/imaging/`.
153
+
154
+ | Method | Returns | Description |
155
+ |--------|---------|-------------|
156
+ | `NodaeBridge.get_imaging()` | `Optional[Path]` | Path to `/data/imaging/`, or `None` if unavailable |
157
+ | `NodaeBridge.list_imaging_series()` | `list[dict]` | Manifest: `patient_id, study_uid, modality, slice_count, path` |
158
+
159
+ ```python
160
+ import pydicom
161
+ from pathlib import Path
162
+
163
+ base = NodaeBridge.get_imaging()
164
+ for series in NodaeBridge.list_imaging_series():
165
+ if series["modality"] != "CT":
166
+ continue
167
+ slices = sorted((base / series["path"]).glob("*.dcm"))
168
+ datasets = [pydicom.dcmread(str(s)) for s in slices]
169
+ # HU windowing, segmentation, feature extraction — container's responsibility
170
+ ```
171
+
172
+ ## Changelog
173
+
174
+ ### 1.0.5 (June 2026)
175
+ - Added `list_fhir_patients()`, `get_fhir_bundle()` — FHIR R4 bundle access for `byom_data_scope="fhir"`
176
+ - Added `FHIR_PATH` class constant
177
+
178
+ ### 1.0.4 (June 2026)
179
+ - Added `get_imaging()`, `list_imaging_series()` — DICOM store access
180
+ - Updated `get_site_metadata()` docs to include `imaging.*` keys
181
+
182
+ ### 1.0.3 (June 2026)
183
+ - Added `get_control_variates()`, `save_control_variate_delta()` — SCAFFOLD support
184
+
185
+ ### 1.0.2 (June 2026)
186
+ - Added `get_signals()` — template signal access
187
+ - Added `get_labs()`, `get_vitals()`, `get_procedures()` — extended data scope
188
+
189
+ ### 1.0.1 (June 2026)
190
+ - Added `get_site_metadata()` convenience wrapper
191
+
192
+ ### 1.0.0 (June 2026)
193
+ - Initial release: `NodaeBridge`, `NodaeModelAdapter`, `run.py` entrypoint
194
+
195
+ ## License
196
+
197
+ Proprietary — Intheris Health. Contact [support@intheris-health.com](mailto:support@intheris-health.com) for access.
198
+
@@ -0,0 +1,38 @@
1
+ [project]
2
+ name = "nodae_bridge"
3
+ version = "1.0.5"
4
+ description = "Nodae BYOM SDK — NodaeBridge and NodaeModelAdapter 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", "nodae"]
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
+ "pydantic>=2.0",
27
+ ]
28
+
29
+ [project.urls]
30
+ Homepage = "https://www.intheris-health.com"
31
+
32
+ [build-system]
33
+ requires = ["setuptools>=68", "wheel"]
34
+ build-backend = "setuptools.build_meta"
35
+
36
+ [tool.setuptools.packages.find]
37
+ where = ["src"]
38
+ include = ["nodae_bridge*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,28 @@
1
+ """
2
+ nodae_bridge — Nodae BYOM SDK for sponsor containers.
3
+
4
+ Quick start::
5
+
6
+ from nodae_bridge import NodaeBridge, NodaeModelAdapter
7
+ from nodae_bridge import ModelSpec, TrainingConfig, LocalTrainResult, InferenceResult
8
+ """
9
+
10
+ from .adapter import (
11
+ ContainerResourceRequirements,
12
+ InferenceResult,
13
+ LocalTrainResult,
14
+ ModelSpec,
15
+ NodaeModelAdapter,
16
+ TrainingConfig,
17
+ )
18
+ from .bridge import NodaeBridge
19
+
20
+ __all__ = [
21
+ "NodaeBridge",
22
+ "NodaeModelAdapter",
23
+ "ModelSpec",
24
+ "TrainingConfig",
25
+ "LocalTrainResult",
26
+ "InferenceResult",
27
+ "ContainerResourceRequirements",
28
+ ]