mekosotto Claude Sonnet 4.6 commited on
Commit
fae874a
·
1 Parent(s): 837970b

feat(api): scaffold FastAPI app + /health + shared Pydantic schemas

Browse files
src/api/main.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """NeuroBridge FastAPI entrypoint.
2
+
3
+ Exposes /health for liveness. Pipeline routes are mounted in Task 8.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ from fastapi import FastAPI
8
+
9
+ from src.api.schemas import HealthResponse
10
+
11
+ app = FastAPI(
12
+ title="NeuroBridge Enterprise",
13
+ description="Three-modality clinical-ML pipeline surface (BBB / EEG / MRI).",
14
+ version="0.4.0",
15
+ )
16
+
17
+
18
+ @app.get("/health", response_model=HealthResponse)
19
+ def health() -> HealthResponse:
20
+ """Liveness probe — used by docker-compose health checks and Streamlit."""
21
+ return HealthResponse(status="ok", pipelines=["bbb", "eeg", "mri"])
src/api/schemas.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Pydantic request / response models for the NeuroBridge FastAPI surface.
2
+
3
+ Each pipeline accepts its own request schema (BBBRequest / EEGRequest /
4
+ MRIRequest) but they all return a unified PipelineResponse — the dashboard
5
+ can render a single result card regardless of modality.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from pydantic import BaseModel, Field
10
+
11
+
12
+ class BBBRequest(BaseModel):
13
+ input_path: str = Field(..., description="CSV path with a 'smiles' column")
14
+ output_path: str = Field(..., description="Parquet output path")
15
+ smiles_col: str = "smiles"
16
+ n_bits: int = 2048
17
+ radius: int = 2
18
+
19
+
20
+ class EEGRequest(BaseModel):
21
+ """Field names mirror eeg_pipeline.run_pipeline kwargs exactly."""
22
+ input_path: str = Field(..., description="FIF or EDF file")
23
+ output_path: str = Field(..., description="Parquet output path")
24
+ epoch_duration_s: float = 2.0
25
+ eog_ch_name: str | None = None
26
+ n_components: int = 15
27
+ random_state: int = 97
28
+
29
+
30
+ class MRIRequest(BaseModel):
31
+ input_dir: str = Field(..., description="Directory of .nii.gz files")
32
+ sites_csv: str = Field(..., description="CSV mapping subject_id → site")
33
+ output_path: str = Field(..., description="Parquet output path")
34
+
35
+
36
+ class PipelineResponse(BaseModel):
37
+ """Uniform response for every pipeline route."""
38
+ status: str
39
+ output_path: str
40
+ rows: int
41
+ columns: int
42
+ duration_sec: float
43
+ mlflow_run_id: str | None = None
44
+
45
+
46
+ class HealthResponse(BaseModel):
47
+ status: str
48
+ pipelines: list[str]
tests/api/__init__.py ADDED
File without changes
tests/api/test_main.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tests for the FastAPI app surface (health + schema imports)."""
2
+ from __future__ import annotations
3
+
4
+ from fastapi.testclient import TestClient
5
+
6
+ from src.api.main import app
7
+
8
+
9
+ client = TestClient(app)
10
+
11
+
12
+ class TestHealthEndpoint:
13
+ def test_get_health_returns_200(self):
14
+ resp = client.get("/health")
15
+ assert resp.status_code == 200
16
+
17
+ def test_get_health_returns_status_ok(self):
18
+ resp = client.get("/health")
19
+ assert resp.json()["status"] == "ok"
20
+
21
+ def test_get_health_returns_pipeline_list(self):
22
+ resp = client.get("/health")
23
+ body = resp.json()
24
+ assert set(body["pipelines"]) == {"bbb", "eeg", "mri"}