Abeshith commited on
Commit
475d366
·
1 Parent(s): 5c61354

Add web UI with forms

Browse files
app/main.py CHANGED
@@ -1,7 +1,9 @@
1
  from fastapi import FastAPI, Request
2
  from fastapi.middleware.cors import CORSMiddleware
3
  from fastapi.responses import JSONResponse
4
- from app.routers import health, predict, train
 
 
5
  from mlpipeline.exception import MLPipelineException
6
  import uvicorn
7
 
@@ -11,6 +13,9 @@ app = FastAPI(
11
  version="1.0.0"
12
  )
13
 
 
 
 
14
  app.add_middleware(
15
  CORSMiddleware,
16
  allow_origins=["*"],
@@ -22,6 +27,7 @@ app.add_middleware(
22
  app.include_router(health.router)
23
  app.include_router(predict.router)
24
  app.include_router(train.router)
 
25
 
26
 
27
  @app.exception_handler(MLPipelineException)
@@ -33,16 +39,8 @@ async def mlpipeline_exception_handler(request: Request, exc: MLPipelineExceptio
33
 
34
 
35
  @app.get("/")
36
- async def root():
37
- return {
38
- "message": "AutoML MLOps API",
39
- "version": "1.0.0",
40
- "endpoints": {
41
- "health": "/health",
42
- "predict": "/predict",
43
- "train": "/train"
44
- }
45
- }
46
 
47
 
48
  if __name__ == "__main__":
 
1
  from fastapi import FastAPI, Request
2
  from fastapi.middleware.cors import CORSMiddleware
3
  from fastapi.responses import JSONResponse
4
+ from fastapi.staticfiles import StaticFiles
5
+ from fastapi.templating import Jinja2Templates
6
+ from app.routers import health, predict, train, ui
7
  from mlpipeline.exception import MLPipelineException
8
  import uvicorn
9
 
 
13
  version="1.0.0"
14
  )
15
 
16
+ app.mount("/static", StaticFiles(directory="app/static"), name="static")
17
+ templates = Jinja2Templates(directory="app/templates")
18
+
19
  app.add_middleware(
20
  CORSMiddleware,
21
  allow_origins=["*"],
 
27
  app.include_router(health.router)
28
  app.include_router(predict.router)
29
  app.include_router(train.router)
30
+ app.include_router(ui.router)
31
 
32
 
33
  @app.exception_handler(MLPipelineException)
 
39
 
40
 
41
  @app.get("/")
42
+ async def root(request: Request):
43
+ return templates.TemplateResponse("index.html", {"request": request})
 
 
 
 
 
 
 
 
44
 
45
 
46
  if __name__ == "__main__":
app/routers/ui.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Request
2
+ from fastapi.templating import Jinja2Templates
3
+
4
+ router = APIRouter(prefix="/ui", tags=["ui"])
5
+ templates = Jinja2Templates(directory="app/templates")
6
+
7
+
8
+ @router.get("/predict")
9
+ async def predict_page(request: Request):
10
+ return templates.TemplateResponse("predict.html", {"request": request})
11
+
12
+
13
+ @router.get("/health")
14
+ async def health_page(request: Request):
15
+ return templates.TemplateResponse("health.html", {"request": request})
app/static/style.css ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ body {
8
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
9
+ background: #f5f7fa;
10
+ color: #333;
11
+ }
12
+
13
+ nav {
14
+ background: #2c3e50;
15
+ color: white;
16
+ padding: 1rem 2rem;
17
+ display: flex;
18
+ align-items: center;
19
+ gap: 2rem;
20
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
21
+ }
22
+
23
+ nav h1 {
24
+ font-size: 1.5rem;
25
+ margin-right: auto;
26
+ }
27
+
28
+ nav a {
29
+ color: white;
30
+ text-decoration: none;
31
+ padding: 0.5rem 1rem;
32
+ border-radius: 4px;
33
+ transition: background 0.3s;
34
+ }
35
+
36
+ nav a:hover {
37
+ background: #34495e;
38
+ }
39
+
40
+ .container {
41
+ max-width: 1200px;
42
+ margin: 2rem auto;
43
+ padding: 0 2rem;
44
+ }
45
+
46
+ .card {
47
+ background: white;
48
+ border-radius: 8px;
49
+ padding: 2rem;
50
+ margin-bottom: 2rem;
51
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
52
+ }
53
+
54
+ .card h3 {
55
+ margin-bottom: 1rem;
56
+ color: #2c3e50;
57
+ }
58
+
59
+ .form-row {
60
+ display: grid;
61
+ grid-template-columns: 1fr 1fr;
62
+ gap: 1rem;
63
+ margin-bottom: 1rem;
64
+ }
65
+
66
+ .form-group {
67
+ display: flex;
68
+ flex-direction: column;
69
+ }
70
+
71
+ .form-group label {
72
+ font-weight: 600;
73
+ margin-bottom: 0.5rem;
74
+ color: #555;
75
+ }
76
+
77
+ .form-group input {
78
+ padding: 0.75rem;
79
+ border: 1px solid #ddd;
80
+ border-radius: 4px;
81
+ font-size: 1rem;
82
+ }
83
+
84
+ .form-group input:focus {
85
+ outline: none;
86
+ border-color: #3498db;
87
+ }
88
+
89
+ .btn {
90
+ background: #3498db;
91
+ color: white;
92
+ padding: 0.75rem 1.5rem;
93
+ border: none;
94
+ border-radius: 4px;
95
+ font-size: 1rem;
96
+ cursor: pointer;
97
+ text-decoration: none;
98
+ display: inline-block;
99
+ transition: background 0.3s;
100
+ }
101
+
102
+ .btn:hover {
103
+ background: #2980b9;
104
+ }
105
+
106
+ .actions {
107
+ display: flex;
108
+ gap: 1rem;
109
+ margin-top: 1rem;
110
+ }
111
+
112
+ h2 {
113
+ margin-bottom: 1.5rem;
114
+ color: #2c3e50;
115
+ }
116
+
117
+ p {
118
+ line-height: 1.6;
119
+ margin-bottom: 0.5rem;
120
+ }
121
+
122
+ @media (max-width: 768px) {
123
+ .form-row {
124
+ grid-template-columns: 1fr;
125
+ }
126
+
127
+ .actions {
128
+ flex-direction: column;
129
+ }
130
+
131
+ nav {
132
+ flex-wrap: wrap;
133
+ }
134
+ }
app/templates/health.html ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Status - AutoML MLOps</title>
5
+ <link rel="stylesheet" href="/static/style.css">
6
+ </head>
7
+ <body>
8
+ <nav>
9
+ <h1>AutoML MLOps Pipeline</h1>
10
+ <a href="/">Home</a>
11
+ <a href="/ui/predict">Predict</a>
12
+ <a href="/ui/health">Status</a>
13
+ <a href="/docs">API Docs</a>
14
+ </nav>
15
+
16
+ <div class="container">
17
+ <h2>System Status</h2>
18
+
19
+ <div class="card">
20
+ <h3>Health Check</h3>
21
+ <div id="healthStatus">Loading...</div>
22
+ </div>
23
+
24
+ <div class="card">
25
+ <h3>Model Management</h3>
26
+ <button onclick="reloadModel()" class="btn">Reload Model</button>
27
+ <div id="reloadStatus"></div>
28
+ </div>
29
+ </div>
30
+
31
+ <script>
32
+ async function checkHealth() {
33
+ try {
34
+ const response = await fetch('/health/');
35
+ const data = await response.json();
36
+
37
+ const color = data.status === 'healthy' ? '#27ae60' : '#e74c3c';
38
+ document.getElementById('healthStatus').innerHTML = `
39
+ <p><strong>Status:</strong> <span style="color: ${color}">${data.status.toUpperCase()}</span></p>
40
+ <p><strong>Model Loaded:</strong> ${data.model_loaded ? 'Yes' : 'No'}</p>
41
+ <p><strong>Message:</strong> ${data.message}</p>
42
+ `;
43
+ } catch (error) {
44
+ document.getElementById('healthStatus').innerHTML = `<p style="color: #e74c3c;">Error checking health</p>`;
45
+ }
46
+ }
47
+
48
+ async function reloadModel() {
49
+ try {
50
+ const response = await fetch('/train/reload', {method: 'POST'});
51
+ const data = await response.json();
52
+
53
+ document.getElementById('reloadStatus').innerHTML = `
54
+ <p style="color: #27ae60; margin-top: 10px;">${data.message}</p>
55
+ `;
56
+ setTimeout(checkHealth, 1000);
57
+ } catch (error) {
58
+ document.getElementById('reloadStatus').innerHTML = `
59
+ <p style="color: #e74c3c; margin-top: 10px;">Error reloading model</p>
60
+ `;
61
+ }
62
+ }
63
+
64
+ checkHealth();
65
+ setInterval(checkHealth, 5000);
66
+ </script>
67
+ </body>
68
+ </html>
app/templates/index.html ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>AutoML MLOps</title>
5
+ <link rel="stylesheet" href="/static/style.css">
6
+ </head>
7
+ <body>
8
+ <nav>
9
+ <h1>AutoML MLOps Pipeline</h1>
10
+ <a href="/">Home</a>
11
+ <a href="/ui/predict">Predict</a>
12
+ <a href="/ui/health">Status</a>
13
+ <a href="/docs">API Docs</a>
14
+ </nav>
15
+
16
+ <div class="container">
17
+ <h2>Heart Disease Prediction System</h2>
18
+ <p>Welcome to the AutoML-powered heart disease prediction system.</p>
19
+
20
+ <div class="card">
21
+ <h3>Quick Actions</h3>
22
+ <div class="actions">
23
+ <a href="/ui/predict" class="btn">Make Prediction</a>
24
+ <a href="/ui/health" class="btn">Check Status</a>
25
+ <a href="/docs" class="btn">View API</a>
26
+ </div>
27
+ </div>
28
+
29
+ <div class="card">
30
+ <h3>System Information</h3>
31
+ <p><strong>Model:</strong> AutoGluon WeightedEnsemble</p>
32
+ <p><strong>Accuracy:</strong> 88.74%</p>
33
+ <p><strong>Features:</strong> 14 input features</p>
34
+ </div>
35
+ </div>
36
+ </body>
37
+ </html>
app/templates/predict.html ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Predict - AutoML MLOps</title>
5
+ <link rel="stylesheet" href="/static/style.css">
6
+ </head>
7
+ <body>
8
+ <nav>
9
+ <h1>AutoML MLOps Pipeline</h1>
10
+ <a href="/">Home</a>
11
+ <a href="/ui/predict">Predict</a>
12
+ <a href="/ui/health">Status</a>
13
+ <a href="/docs">API Docs</a>
14
+ </nav>
15
+
16
+ <div class="container">
17
+ <h2>Heart Disease Prediction</h2>
18
+
19
+ <form id="predictionForm" class="card">
20
+ <div class="form-row">
21
+ <div class="form-group">
22
+ <label>ID:</label>
23
+ <input type="number" name="id" value="1" required>
24
+ </div>
25
+ <div class="form-group">
26
+ <label>Age:</label>
27
+ <input type="number" name="Age" value="50" required>
28
+ </div>
29
+ </div>
30
+
31
+ <div class="form-row">
32
+ <div class="form-group">
33
+ <label>Sex (0=F, 1=M):</label>
34
+ <input type="number" name="Sex" min="0" max="1" value="1" required>
35
+ </div>
36
+ <div class="form-group">
37
+ <label>Chest Pain Type (1-4):</label>
38
+ <input type="number" name="Chest pain type" min="1" max="4" value="2" required>
39
+ </div>
40
+ </div>
41
+
42
+ <div class="form-row">
43
+ <div class="form-group">
44
+ <label>BP (mmHg):</label>
45
+ <input type="number" name="BP" value="120" required>
46
+ </div>
47
+ <div class="form-group">
48
+ <label>Cholesterol (mg/dl):</label>
49
+ <input type="number" name="Cholesterol" value="200" required>
50
+ </div>
51
+ </div>
52
+
53
+ <div class="form-row">
54
+ <div class="form-group">
55
+ <label>FBS over 120 (0/1):</label>
56
+ <input type="number" name="FBS over 120" min="0" max="1" value="0" required>
57
+ </div>
58
+ <div class="form-group">
59
+ <label>EKG Results (0-2):</label>
60
+ <input type="number" name="EKG results" min="0" max="2" value="0" required>
61
+ </div>
62
+ </div>
63
+
64
+ <div class="form-row">
65
+ <div class="form-group">
66
+ <label>Max HR:</label>
67
+ <input type="number" name="Max HR" value="150" required>
68
+ </div>
69
+ <div class="form-group">
70
+ <label>Exercise Angina (0/1):</label>
71
+ <input type="number" name="Exercise angina" min="0" max="1" value="0" required>
72
+ </div>
73
+ </div>
74
+
75
+ <div class="form-row">
76
+ <div class="form-group">
77
+ <label>ST Depression:</label>
78
+ <input type="number" name="ST depression" step="0.1" value="1.0" required>
79
+ </div>
80
+ <div class="form-group">
81
+ <label>Slope of ST (1-3):</label>
82
+ <input type="number" name="Slope of ST" min="1" max="3" value="1" required>
83
+ </div>
84
+ </div>
85
+
86
+ <div class="form-row">
87
+ <div class="form-group">
88
+ <label>Vessels Fluro (0-3):</label>
89
+ <input type="number" name="Number of vessels fluro" min="0" max="3" value="0" required>
90
+ </div>
91
+ <div class="form-group">
92
+ <label>Thallium (3/6/7):</label>
93
+ <input type="number" name="Thallium" value="3" required>
94
+ </div>
95
+ </div>
96
+
97
+ <button type="submit" class="btn">Predict</button>
98
+ </form>
99
+
100
+ <div id="result" class="card" style="display:none">
101
+ <h3>Prediction Result</h3>
102
+ <div id="resultContent"></div>
103
+ </div>
104
+ </div>
105
+
106
+ <script>
107
+ document.getElementById('predictionForm').addEventListener('submit', async (e) => {
108
+ e.preventDefault();
109
+ const formData = new FormData(e.target);
110
+ const data = {};
111
+ formData.forEach((value, key) => {
112
+ data[key] = key.includes('depression') ? parseFloat(value) : parseInt(value);
113
+ });
114
+
115
+ try {
116
+ const response = await fetch('/predict/', {
117
+ method: 'POST',
118
+ headers: {'Content-Type': 'application/json'},
119
+ body: JSON.stringify(data)
120
+ });
121
+ const result = await response.json();
122
+
123
+ const resultDiv = document.getElementById('result');
124
+ const resultContent = document.getElementById('resultContent');
125
+
126
+ if (response.ok) {
127
+ const prediction = result.prediction === 1 ? 'POSITIVE' : 'NEGATIVE';
128
+ const color = result.prediction === 1 ? '#e74c3c' : '#27ae60';
129
+ const prob = result.probability ? `${(result.probability[result.prediction] * 100).toFixed(2)}%` : 'N/A';
130
+
131
+ resultContent.innerHTML = `
132
+ <p style="font-size: 24px; color: ${color}; font-weight: bold;">${prediction}</p>
133
+ <p><strong>Confidence:</strong> ${prob}</p>
134
+ <p><strong>Probabilities:</strong> [Negative: ${(result.probability[0] * 100).toFixed(2)}%, Positive: ${(result.probability[1] * 100).toFixed(2)}%]</p>
135
+ `;
136
+ } else {
137
+ resultContent.innerHTML = `<p style="color: #e74c3c;">Error: ${result.detail}</p>`;
138
+ }
139
+
140
+ resultDiv.style.display = 'block';
141
+ } catch (error) {
142
+ alert('Error: ' + error.message);
143
+ }
144
+ });
145
+ </script>
146
+ </body>
147
+ </html>
requirements.txt CHANGED
@@ -2,6 +2,7 @@ fastapi
2
  uvicorn[standard]
3
  pydantic
4
  pydantic-settings
 
5
 
6
  pandas
7
  numpy
 
2
  uvicorn[standard]
3
  pydantic
4
  pydantic-settings
5
+ jinja2
6
 
7
  pandas
8
  numpy
test_api.py DELETED
@@ -1,85 +0,0 @@
1
- import requests
2
- import json
3
-
4
- BASE_URL = "http://localhost:8000"
5
-
6
-
7
- def test_health():
8
- response = requests.get(f"{BASE_URL}/health/")
9
- print(f"Health Check: {response.json()}")
10
-
11
-
12
- def test_predict():
13
- data = {
14
- "id": 1,
15
- "Age": 50,
16
- "Sex": 1,
17
- "Chest pain type": 2,
18
- "BP": 120,
19
- "Cholesterol": 200,
20
- "FBS over 120": 0,
21
- "EKG results": 0,
22
- "Max HR": 150,
23
- "Exercise angina": 0,
24
- "ST depression": 1.0,
25
- "Slope of ST": 1,
26
- "Number of vessels fluro": 0,
27
- "Thallium": 3
28
- }
29
- response = requests.post(f"{BASE_URL}/predict/", json=data)
30
- print(f"Prediction: {response.json()}")
31
-
32
-
33
- def test_batch_predict():
34
- data = {
35
- "data": [
36
- {
37
- "id": 1,
38
- "Age": 50,
39
- "Sex": 1,
40
- "Chest pain type": 2,
41
- "BP": 120,
42
- "Cholesterol": 200,
43
- "FBS over 120": 0,
44
- "EKG results": 0,
45
- "Max HR": 150,
46
- "Exercise angina": 0,
47
- "ST depression": 1.0,
48
- "Slope of ST": 1,
49
- "Number of vessels fluro": 0,
50
- "Thallium": 3
51
- },
52
- {
53
- "id": 2,
54
- "Age": 60,
55
- "Sex": 0,
56
- "Chest pain type": 1,
57
- "BP": 130,
58
- "Cholesterol": 220,
59
- "FBS over 120": 1,
60
- "EKG results": 1,
61
- "Max HR": 140,
62
- "Exercise angina": 1,
63
- "ST depression": 2.0,
64
- "Slope of ST": 2,
65
- "Number of vessels fluro": 1,
66
- "Thallium": 6
67
- }
68
- ]
69
- }
70
- response = requests.post(f"{BASE_URL}/predict/batch", json=data)
71
- print(f"Batch Prediction: {response.json()}")
72
-
73
-
74
- if __name__ == "__main__":
75
- print("Testing API Endpoints...")
76
- print("-" * 50)
77
-
78
- try:
79
- test_health()
80
- print("-" * 50)
81
- test_predict()
82
- print("-" * 50)
83
- test_batch_predict()
84
- except Exception as e:
85
- print(f"Error: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
test_train_endpoints.py DELETED
@@ -1,28 +0,0 @@
1
- import requests
2
-
3
- BASE_URL = "http://localhost:8000"
4
-
5
-
6
- def test_reload():
7
- print("Testing model reload...")
8
- response = requests.post(f"{BASE_URL}/train/reload")
9
- print(f"Response: {response.json()}\n")
10
-
11
-
12
- def test_health_after_load():
13
- print("Testing health after model load...")
14
- response = requests.get(f"{BASE_URL}/health/")
15
- print(f"Response: {response.json()}\n")
16
-
17
-
18
- def test_ready():
19
- print("Testing readiness endpoint...")
20
- response = requests.get(f"{BASE_URL}/health/ready")
21
- print(f"Response: {response.json()}\n")
22
-
23
-
24
- if __name__ == "__main__":
25
- test_reload()
26
- test_health_after_load()
27
- test_ready()
28
- print("All endpoint tests completed!")