pacman2223 commited on
Commit
0b2787c
·
verified ·
1 Parent(s): 28616e4

Upload 6 files

Browse files
Files changed (6) hide show
  1. Dockerfile +15 -0
  2. index.html +714 -0
  3. main.py +304 -0
  4. models/xgb_rf.pkl +3 -0
  5. models/xgboost.pkl +3 -0
  6. requirements.txt +26 -0
Dockerfile ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM ubuntu:22.04
2
+
3
+ WORKDIR /code
4
+
5
+ # install app dependencies
6
+ RUN apt-get update && apt-get install -y python3 python3-pip
7
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
8
+
9
+ # install app
10
+ COPY . .
11
+
12
+ # final configuration
13
+
14
+ EXPOSE 8000
15
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
index.html ADDED
@@ -0,0 +1,714 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Exoplanet Prediction System</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16
+ background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%);
17
+ min-height: 100vh;
18
+ color: #fff;
19
+ overflow-x: hidden;
20
+ }
21
+
22
+ .stars {
23
+ position: fixed;
24
+ width: 100%;
25
+ height: 100%;
26
+ pointer-events: none;
27
+ }
28
+
29
+ .star {
30
+ position: absolute;
31
+ width: 2px;
32
+ height: 2px;
33
+ background: white;
34
+ border-radius: 50%;
35
+ animation: twinkle 3s infinite;
36
+ }
37
+
38
+ @keyframes twinkle {
39
+ 0%, 100% { opacity: 0.3; }
40
+ 50% { opacity: 1; }
41
+ }
42
+
43
+ .container {
44
+ max-width: 1200px;
45
+ margin: 0 auto;
46
+ padding: 40px 20px;
47
+ position: relative;
48
+ z-index: 1;
49
+ }
50
+
51
+ .header {
52
+ text-align: center;
53
+ margin-bottom: 50px;
54
+ }
55
+
56
+ .header h1 {
57
+ font-size: 3rem;
58
+ background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);
59
+ -webkit-background-clip: text;
60
+ -webkit-text-fill-color: transparent;
61
+ background-clip: text;
62
+ margin-bottom: 10px;
63
+ text-shadow: 0 0 30px rgba(102, 126, 234, 0.5);
64
+ }
65
+
66
+ .header p {
67
+ color: #a0aec0;
68
+ font-size: 1.1rem;
69
+ }
70
+
71
+ .tabs {
72
+ display: flex;
73
+ gap: 10px;
74
+ margin-bottom: 30px;
75
+ justify-content: center;
76
+ }
77
+
78
+ .tab {
79
+ padding: 15px 30px;
80
+ background: rgba(255, 255, 255, 0.05);
81
+ border: 2px solid rgba(255, 255, 255, 0.1);
82
+ border-radius: 15px;
83
+ cursor: pointer;
84
+ transition: all 0.3s ease;
85
+ font-size: 1rem;
86
+ color: #fff;
87
+ backdrop-filter: blur(10px);
88
+ }
89
+
90
+ .tab:hover {
91
+ background: rgba(255, 255, 255, 0.1);
92
+ transform: translateY(-2px);
93
+ }
94
+
95
+ .tab.active {
96
+ background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);
97
+ border-color: #667eea;
98
+ box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3);
99
+ }
100
+
101
+ .tab-content {
102
+ display: none;
103
+ animation: fadeIn 0.5s ease;
104
+ }
105
+
106
+ .tab-content.active {
107
+ display: block;
108
+ }
109
+
110
+ @keyframes fadeIn {
111
+ from { opacity: 0; transform: translateY(20px); }
112
+ to { opacity: 1; transform: translateY(0); }
113
+ }
114
+
115
+ .card {
116
+ background: rgba(255, 255, 255, 0.05);
117
+ backdrop-filter: blur(10px);
118
+ border: 1px solid rgba(255, 255, 255, 0.1);
119
+ border-radius: 20px;
120
+ padding: 40px;
121
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
122
+ }
123
+
124
+ .form-grid {
125
+ display: grid;
126
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
127
+ gap: 25px;
128
+ margin-bottom: 30px;
129
+ }
130
+
131
+ .form-group {
132
+ display: flex;
133
+ flex-direction: column;
134
+ }
135
+
136
+ label {
137
+ margin-bottom: 8px;
138
+ color: #a0aec0;
139
+ font-size: 0.9rem;
140
+ font-weight: 500;
141
+ }
142
+
143
+ input[type="number"], input[type="file"] {
144
+ padding: 12px 15px;
145
+ background: rgba(255, 255, 255, 0.05);
146
+ border: 1px solid rgba(255, 255, 255, 0.2);
147
+ border-radius: 10px;
148
+ color: #fff;
149
+ font-size: 1rem;
150
+ transition: all 0.3s ease;
151
+ }
152
+
153
+ input[type="number"]:focus, input[type="file"]:focus, select:focus {
154
+ outline: none;
155
+ border-color: #667eea;
156
+ box-shadow: 0 0 20px rgba(102, 126, 234, 0.3);
157
+ }
158
+
159
+ select {
160
+ background: rgba(255, 255, 255, 0.05);
161
+ border: 1px solid rgba(255, 255, 255, 0.2);
162
+ border-radius: 10px;
163
+ color: #fff;
164
+ padding: 12px 15px;
165
+ font-size: 1rem;
166
+ cursor: pointer;
167
+ transition: all 0.3s ease;
168
+ }
169
+
170
+ select option {
171
+ background: #302b63;
172
+ color: #fff;
173
+ }
174
+
175
+ select:hover {
176
+ border-color: rgba(255, 255, 255, 0.4);
177
+ }
178
+
179
+ input::placeholder {
180
+ color: rgba(255, 255, 255, 0.3);
181
+ }
182
+
183
+ .btn {
184
+ padding: 15px 40px;
185
+ background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);
186
+ border: none;
187
+ border-radius: 12px;
188
+ color: #fff;
189
+ font-size: 1.1rem;
190
+ font-weight: 600;
191
+ cursor: pointer;
192
+ transition: all 0.3s ease;
193
+ box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3);
194
+ width: 100%;
195
+ }
196
+
197
+ .btn:hover {
198
+ transform: translateY(-3px);
199
+ box-shadow: 0 15px 40px rgba(102, 126, 234, 0.5);
200
+ }
201
+
202
+ .btn:disabled {
203
+ opacity: 0.6;
204
+ cursor: not-allowed;
205
+ transform: none;
206
+ }
207
+
208
+ .result {
209
+ margin-top: 30px;
210
+ padding: 25px;
211
+ background: rgba(255, 255, 255, 0.05);
212
+ border-radius: 15px;
213
+ border: 1px solid rgba(255, 255, 255, 0.1);
214
+ animation: slideUp 0.5s ease;
215
+ }
216
+
217
+ @keyframes slideUp {
218
+ from { opacity: 0; transform: translateY(30px); }
219
+ to { opacity: 1; transform: translateY(0); }
220
+ }
221
+
222
+ .result h3 {
223
+ margin-bottom: 15px;
224
+ color: #667eea;
225
+ font-size: 1.5rem;
226
+ }
227
+
228
+ .result-item {
229
+ display: flex;
230
+ justify-content: space-between;
231
+ padding: 10px 0;
232
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
233
+ }
234
+
235
+ .result-item:last-child {
236
+ border-bottom: none;
237
+ }
238
+
239
+ .result-label {
240
+ color: #a0aec0;
241
+ }
242
+
243
+ .result-value {
244
+ font-weight: 600;
245
+ color: #fff;
246
+ }
247
+
248
+ .badge {
249
+ display: inline-block;
250
+ padding: 8px 20px;
251
+ border-radius: 20px;
252
+ font-size: 0.9rem;
253
+ font-weight: 600;
254
+ }
255
+
256
+ .badge.positive {
257
+ background: linear-gradient(45deg, #11998e 0%, #38ef7d 100%);
258
+ }
259
+
260
+ .badge.negative {
261
+ background: linear-gradient(45deg, #ee0979 0%, #ff6a00 100%);
262
+ }
263
+
264
+ .upload-area {
265
+ border: 2px dashed rgba(255, 255, 255, 0.3);
266
+ border-radius: 15px;
267
+ padding: 40px;
268
+ text-align: center;
269
+ margin-bottom: 30px;
270
+ transition: all 0.3s ease;
271
+ cursor: pointer;
272
+ }
273
+
274
+ .upload-area:hover {
275
+ border-color: #667eea;
276
+ background: rgba(102, 126, 234, 0.1);
277
+ }
278
+
279
+ .upload-icon {
280
+ font-size: 3rem;
281
+ margin-bottom: 15px;
282
+ opacity: 0.5;
283
+ }
284
+
285
+ .file-info {
286
+ margin-top: 15px;
287
+ color: #667eea;
288
+ font-weight: 500;
289
+ }
290
+
291
+ .loading {
292
+ display: inline-block;
293
+ width: 20px;
294
+ height: 20px;
295
+ border: 3px solid rgba(255, 255, 255, 0.3);
296
+ border-radius: 50%;
297
+ border-top-color: #fff;
298
+ animation: spin 1s linear infinite;
299
+ margin-right: 10px;
300
+ vertical-align: middle;
301
+ }
302
+
303
+ @keyframes spin {
304
+ to { transform: rotate(360deg); }
305
+ }
306
+
307
+ .error {
308
+ background: rgba(238, 9, 121, 0.1);
309
+ border: 1px solid rgba(238, 9, 121, 0.3);
310
+ color: #ff6b9d;
311
+ padding: 15px;
312
+ border-radius: 10px;
313
+ margin-top: 15px;
314
+ }
315
+
316
+ .info-tooltip {
317
+ display: inline-block;
318
+ margin-left: 5px;
319
+ width: 16px;
320
+ height: 16px;
321
+ background: rgba(255, 255, 255, 0.2);
322
+ border-radius: 50%;
323
+ text-align: center;
324
+ line-height: 16px;
325
+ font-size: 0.7rem;
326
+ cursor: help;
327
+ }
328
+
329
+ .fireworks-container {
330
+ position: fixed;
331
+ top: 0;
332
+ left: 0;
333
+ width: 100%;
334
+ height: 100%;
335
+ pointer-events: none;
336
+ z-index: 9999;
337
+ }
338
+
339
+ .firework {
340
+ position: absolute;
341
+ width: 4px;
342
+ height: 4px;
343
+ border-radius: 50%;
344
+ animation: firework-launch 1s ease-out forwards;
345
+ }
346
+
347
+ @keyframes firework-launch {
348
+ 0% {
349
+ transform: translateY(0);
350
+ opacity: 1;
351
+ }
352
+ 100% {
353
+ transform: translateY(-400px);
354
+ opacity: 0;
355
+ }
356
+ }
357
+
358
+ .particle {
359
+ position: absolute;
360
+ width: 6px;
361
+ height: 6px;
362
+ border-radius: 50%;
363
+ animation: particle-explode 1s ease-out forwards;
364
+ }
365
+
366
+ @keyframes particle-explode {
367
+ 0% {
368
+ transform: translate(0, 0) scale(1);
369
+ opacity: 1;
370
+ }
371
+ 100% {
372
+ opacity: 0;
373
+ transform: scale(0);
374
+ }
375
+ }
376
+ </style>
377
+ </head>
378
+ <body>
379
+ <div class="stars" id="stars"></div>
380
+ <div class="fireworks-container" id="fireworks"></div>
381
+
382
+ <div class="container">
383
+ <div class="header">
384
+ <h1>🪐 Exoplanet Detection System</h1>
385
+ <p>AI-Powered Classification of Kepler Objects of Interest</p>
386
+ </div>
387
+
388
+ <div class="tabs">
389
+ <div class="tab active" onclick="switchTab('manual')">Manual Prediction</div>
390
+ <div class="tab" onclick="switchTab('batch')">Batch Upload</div>
391
+ </div>
392
+
393
+ <div id="manual-tab" class="tab-content active">
394
+ <div class="card">
395
+ <form id="prediction-form">
396
+ <div class="form-group" style="margin-bottom: 30px;">
397
+ <label>Select Model <span class="info-tooltip" title="Choose which ML model to use for prediction">?</span></label>
398
+ <select id="model-select" name="model" style="padding: 12px 15px; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 10px; color: #fff; font-size: 1rem; width: 100%; cursor: pointer; transition: all 0.3s ease;">
399
+ <!-- <option value="random_forest">Random Forest</option> -->
400
+ <option value="xgboost">XGBoost</option>
401
+ <option value="ensemble">XGB + RF</option>
402
+ <!-- <option value="logistic_regression">Logistic Regression</option>
403
+ <option value="svm">Support Vector Machine</option>
404
+ <option value="neural_network">Neural Network</option>
405
+ <option value="gradient_boosting">Gradient Boosting</option> -->
406
+ </select>
407
+ </div>
408
+ <div class="form-grid">
409
+ <div class="form-group">
410
+ <label>Signal-to-Noise Ratio <span class="info-tooltip" title="Transit signal-to-noise ratio">?</span></label>
411
+ <input type="number" step="0.01" name="koi_model_snr" placeholder="e.g., 15.5" required>
412
+ </div>
413
+ <div class="form-group">
414
+ <label>Planetary Radius (Earth radii) <span class="info-tooltip" title="Planet size relative to Earth">?</span></label>
415
+ <input type="number" step="0.01" name="koi_prad" placeholder="e.g., 2.3" required>
416
+ </div>
417
+ <div class="form-group">
418
+ <label>Stellar Eclipse Flag <span class="info-tooltip" title="0 or 1">?</span></label>
419
+ <input type="number" min="0" max="1" name="koi_fpflag_ss" placeholder="0 or 1" required>
420
+ </div>
421
+ <div class="form-group">
422
+ <label>Centroid Offset Flag <span class="info-tooltip" title="0 or 1">?</span></label>
423
+ <input type="number" min="0" max="1" name="koi_fpflag_co" placeholder="0 or 1" required>
424
+ </div>
425
+ <div class="form-group">
426
+ <label>Orbital Period (days) <span class="info-tooltip" title="Time for one complete orbit">?</span></label>
427
+ <input type="number" step="0.01" name="koi_period" placeholder="e.g., 10.5" required>
428
+ </div>
429
+ <div class="form-group">
430
+ <label>Transit Depth (ppm) <span class="info-tooltip" title="Parts per million">?</span></label>
431
+ <input type="number" step="0.01" name="koi_depth" placeholder="e.g., 500.0" required>
432
+ </div>
433
+ <div class="form-group">
434
+ <label>Not Transit-Like Flag <span class="info-tooltip" title="0 or 1">?</span></label>
435
+ <input type="number" min="0" max="1" name="koi_fpflag_nt" placeholder="0 or 1" required>
436
+ </div>
437
+ <div class="form-group">
438
+ <label>Insolation Flux (Earth units) <span class="info-tooltip" title="Amount of stellar radiation">?</span></label>
439
+ <input type="number" step="0.01" name="koi_insol" placeholder="e.g., 1.2" required>
440
+ </div>
441
+ </div>
442
+ <button type="submit" class="btn" id="predict-btn">
443
+ Predict Exoplanet
444
+ </button>
445
+ </form>
446
+ <div id="result-container"></div>
447
+ </div>
448
+ </div>
449
+
450
+ <div id="batch-tab" class="tab-content">
451
+ <div class="card">
452
+ <div class="form-group" style="margin-bottom: 30px;">
453
+ <label>Select Model <span class="info-tooltip" title="Choose which ML model to use for batch predictions">?</span></label>
454
+ <select id="batch-model-select" name="model" style="padding: 12px 15px; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 10px; color: #fff; font-size: 1rem; width: 100%; cursor: pointer; transition: all 0.3s ease;">
455
+ <!-- <option value="random_forest">Random Forest</option> -->
456
+ <option value="xgboost">XGBoost</option>
457
+ <option value="ensemble">XGB + RF</option>
458
+ <!-- <option value="logistic_regression">Logistic Regression</option>
459
+ <option value="svm">Support Vector Machine</option>
460
+ <option value="neural_network">Neural Network</option>
461
+ <option value="gradient_boosting">Gradient Boosting</option> -->
462
+ </select>
463
+ </div>
464
+ <div class="upload-area" onclick="document.getElementById('csv-file').click()">
465
+ <div class="upload-icon">📁</div>
466
+ <h3>Upload CSV File</h3>
467
+ <p style="color: #a0aec0; margin-top: 10px;">Click to browse or drag and drop your CSV file</p>
468
+ <input type="file" id="csv-file" accept=".csv" style="display: none;" onchange="handleFileSelect(event)">
469
+ <div id="file-info" class="file-info"></div>
470
+ </div>
471
+ <button class="btn" id="batch-btn" onclick="uploadBatch()" disabled>
472
+ Process Batch Predictions
473
+ </button>
474
+ <div id="batch-result-container"></div>
475
+ </div>
476
+ </div>
477
+ </div>
478
+
479
+ <script>
480
+ // Create stars
481
+ const starsContainer = document.getElementById('stars');
482
+ for (let i = 0; i < 100; i++) {
483
+ const star = document.createElement('div');
484
+ star.className = 'star';
485
+ star.style.left = Math.random() * 100 + '%';
486
+ star.style.top = Math.random() * 100 + '%';
487
+ star.style.animationDelay = Math.random() * 3 + 's';
488
+ starsContainer.appendChild(star);
489
+ }
490
+
491
+ // API endpoint - update this to your actual API URL
492
+ const API_URL = 'http://localhost:8000';
493
+
494
+ function switchTab(tab) {
495
+ document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
496
+ document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
497
+
498
+ event.target.classList.add('active');
499
+ document.getElementById(tab + '-tab').classList.add('active');
500
+ }
501
+
502
+ document.getElementById('prediction-form').addEventListener('submit', async (e) => {
503
+ e.preventDefault();
504
+
505
+ const btn = document.getElementById('predict-btn');
506
+ btn.disabled = true;
507
+ btn.innerHTML = '<span class="loading"></span>Predicting...';
508
+
509
+ const formData = new FormData(e.target);
510
+ const data = Object.fromEntries(formData);
511
+
512
+ // Extract model selection
513
+ const selectedModel = data.model;
514
+ delete data.model;
515
+
516
+ // Convert to numbers
517
+ Object.keys(data).forEach(key => {
518
+ data[key] = parseFloat(data[key]);
519
+ });
520
+
521
+ try {
522
+ const response = await fetch(`${API_URL}/predict?model=${selectedModel}`, {
523
+ method: 'POST',
524
+ headers: {
525
+ 'Content-Type': 'application/json',
526
+ },
527
+ body: JSON.stringify(data)
528
+ });
529
+
530
+ const result = await response.json();
531
+ displayResult(result, selectedModel);
532
+ } catch (error) {
533
+ document.getElementById('result-container').innerHTML = `
534
+ <div class="error">
535
+ <strong>Error:</strong> ${error.message}<br>
536
+ <small>Make sure the API server is running at ${API_URL}</small>
537
+ </div>
538
+ `;
539
+ } finally {
540
+ btn.disabled = false;
541
+ btn.innerHTML = 'Predict Exoplanet';
542
+ }
543
+ });
544
+
545
+ function displayResult(result, modelName) {
546
+ let badgeClass, statusText;
547
+
548
+ if (result.prediction === 1) {
549
+ badgeClass = 'positive';
550
+ statusText = 'Confirmed Exoplanet';
551
+ } else if (result.prediction === 2) {
552
+ badgeClass = 'positive';
553
+ statusText = 'Exoplanet Candidate';
554
+ } else {
555
+ badgeClass = 'negative';
556
+ statusText = 'False Positive';
557
+ }
558
+
559
+ const modelDisplay = modelName ? modelName.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()) : 'N/A';
560
+
561
+ const html = `
562
+ <div class="result">
563
+ <h3>Prediction Results</h3>
564
+ <div class="result-item">
565
+ <span class="result-label">Model Used:</span>
566
+ <span class="result-value">${modelDisplay}</span>
567
+ </div>
568
+ <div class="result-item">
569
+ <span class="result-label">Classification:</span>
570
+ <span class="badge ${badgeClass}">${result.classification}</span>
571
+ </div>
572
+ <div class="result-item">
573
+ <span class="result-label">Confidence:</span>
574
+ <span class="result-value">${(result.probability * 100).toFixed(2)}%</span>
575
+ </div>
576
+ <div class="result-item">
577
+ <span class="result-label">Status:</span>
578
+ <span class="result-value">${statusText}</span>
579
+ </div>
580
+ </div>
581
+ `;
582
+ document.getElementById('result-container').innerHTML = html;
583
+
584
+ // Launch fireworks if exoplanet is confirmed!
585
+ if (result.prediction === 1) {
586
+ launchFireworks();
587
+ }
588
+ }
589
+
590
+ function launchFireworks() {
591
+ const container = document.getElementById('fireworks');
592
+ const colors = ['#ff0844', '#ffb199', '#ffd23f', '#00d9ff', '#7b5cff', '#ff006e', '#8338ec', '#3a86ff'];
593
+
594
+ // Launch 15 fireworks over 3 seconds
595
+ for (let i = 0; i < 15; i++) {
596
+ setTimeout(() => {
597
+ createFirework(container, colors);
598
+ }, i * 200);
599
+ }
600
+ }
601
+
602
+ function createFirework(container, colors) {
603
+ const startX = Math.random() * window.innerWidth;
604
+ const startY = window.innerHeight;
605
+ const endX = startX;
606
+ const endY = Math.random() * (window.innerHeight * 0.5) + 100;
607
+
608
+ const color = colors[Math.floor(Math.random() * colors.length)];
609
+
610
+ // Create launch trail
611
+ const firework = document.createElement('div');
612
+ firework.className = 'firework';
613
+ firework.style.left = startX + 'px';
614
+ firework.style.top = startY + 'px';
615
+ firework.style.backgroundColor = color;
616
+ container.appendChild(firework);
617
+
618
+ // Explode after launch
619
+ setTimeout(() => {
620
+ explode(container, endX, endY, color);
621
+ firework.remove();
622
+ }, 1000);
623
+ }
624
+
625
+ function explode(container, x, y, color) {
626
+ const particleCount = 50;
627
+
628
+ for (let i = 0; i < particleCount; i++) {
629
+ const particle = document.createElement('div');
630
+ particle.className = 'particle';
631
+ particle.style.left = x + 'px';
632
+ particle.style.top = y + 'px';
633
+ particle.style.backgroundColor = color;
634
+
635
+ const angle = (Math.PI * 2 * i) / particleCount;
636
+ const velocity = 100 + Math.random() * 100;
637
+ const tx = Math.cos(angle) * velocity;
638
+ const ty = Math.sin(angle) * velocity;
639
+
640
+ particle.style.setProperty('--tx', tx + 'px');
641
+ particle.style.setProperty('--ty', ty + 'px');
642
+
643
+ particle.style.animation = `particle-explode ${0.8 + Math.random() * 0.4}s ease-out forwards`;
644
+ particle.style.transform = `translate(${tx}px, ${ty}px)`;
645
+
646
+ container.appendChild(particle);
647
+
648
+ setTimeout(() => particle.remove(), 1500);
649
+ }
650
+ }
651
+
652
+ let selectedFile = null;
653
+
654
+ function handleFileSelect(event) {
655
+ selectedFile = event.target.files[0];
656
+ if (selectedFile) {
657
+ document.getElementById('file-info').innerHTML = `Selected: ${selectedFile.name} (${(selectedFile.size / 1024).toFixed(2)} KB)`;
658
+ document.getElementById('batch-btn').disabled = false;
659
+ }
660
+ }
661
+
662
+ async function uploadBatch() {
663
+ if (!selectedFile) return;
664
+
665
+ const btn = document.getElementById('batch-btn');
666
+ btn.disabled = true;
667
+ btn.innerHTML = '<span class="loading"></span>Processing...';
668
+
669
+ const selectedModel = document.getElementById('batch-model-select').value;
670
+ const formData = new FormData();
671
+ formData.append('file', selectedFile);
672
+
673
+ try {
674
+ const response = await fetch(`${API_URL}/predict/batch?model=${selectedModel}`, {
675
+ method: 'POST',
676
+ body: formData
677
+ });
678
+
679
+ if (response.ok) {
680
+ const blob = await response.blob();
681
+ const url = window.URL.createObjectURL(blob);
682
+ const a = document.createElement('a');
683
+ a.href = url;
684
+ a.download = `predictions_${selectedModel}_${selectedFile.name}`;
685
+ document.body.appendChild(a);
686
+ a.click();
687
+ window.URL.revokeObjectURL(url);
688
+ document.body.removeChild(a);
689
+
690
+ const modelDisplay = selectedModel.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
691
+ document.getElementById('batch-result-container').innerHTML = `
692
+ <div class="result">
693
+ <h3>✅ Success!</h3>
694
+ <p>Your predictions using <strong>${modelDisplay}</strong> have been downloaded.</p>
695
+ </div>
696
+ `;
697
+ } else {
698
+ throw new Error('Upload failed');
699
+ }
700
+ } catch (error) {
701
+ document.getElementById('batch-result-container').innerHTML = `
702
+ <div class="error">
703
+ <strong>Error:</strong> ${error.message}<br>
704
+ <small>Make sure the API server is running and the CSV has all required columns.</small>
705
+ </div>
706
+ `;
707
+ } finally {
708
+ btn.disabled = false;
709
+ btn.innerHTML = 'Process Batch Predictions';
710
+ }
711
+ }
712
+ </script>
713
+ </body>
714
+ </html>
main.py ADDED
@@ -0,0 +1,304 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, File, UploadFile, HTTPException, Query
2
+ from fastapi.responses import StreamingResponse
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ from pydantic import BaseModel, Field
5
+ from typing import List, Optional
6
+ import pandas as pd
7
+ import io
8
+ import pickle
9
+ import joblib
10
+ import numpy as np
11
+ from enum import Enum
12
+
13
+ app = FastAPI(
14
+ title="Exoplanet Prediction API",
15
+ description="API for predicting exoplanet candidates using KOI features",
16
+ version="1.0.0"
17
+ )
18
+
19
+ # Add CORS middleware
20
+ app.add_middleware(
21
+ CORSMiddleware,
22
+ allow_origins=["*"], # In production, replace with specific origins
23
+ allow_credentials=True,
24
+ allow_methods=["*"],
25
+ allow_headers=["*"],
26
+ )
27
+
28
+ # Define available models
29
+ class ModelType(str, Enum):
30
+ random_forest = "random_forest"
31
+ xgboost = "xgboost"
32
+ ensemble = "ensemble"
33
+ # logistic_regression = "logistic_regression"
34
+ # svm = "svm"
35
+ # neural_network = "neural_network"
36
+ # gradient_boosting = "gradient_boosting"
37
+
38
+ # Dictionary to store loaded models
39
+ models = {}
40
+
41
+ def load_models():
42
+ """Load all available models on startup"""
43
+ # models['random_forest'] = pickle.load(open('models/xgb_rf.pkl', 'rb'))
44
+ models['xgboost'] = joblib.load('models/xgboost.pkl')
45
+ models['ensemble'] = joblib.load('models/xgb_rf.pkl')
46
+ # models['logistic_regression'] = pickle.load(open('models/logistic_regression.pkl', 'rb'))
47
+ # models['svm'] = pickle.load(open('models/svm.pkl', 'rb'))
48
+ # models['neural_network'] = pickle.load(open('models/neural_network.pkl', 'rb'))
49
+ # models['gradient_boosting'] = pickle.load(open('models/gradient_boosting.pkl', 'rb'))
50
+
51
+ # Load models when app starts
52
+ @app.on_event("startup")
53
+ async def startup_event():
54
+ load_models()
55
+ print("Models loaded successfully")
56
+
57
+ class PredictionInput(BaseModel):
58
+ koi_model_snr: float = Field(..., description="Transit signal-to-noise ratio")
59
+ koi_prad: float = Field(..., description="Planetary radius in Earth radii")
60
+ koi_fpflag_ss: int = Field(..., ge=0, le=1, description="Stellar eclipse flag")
61
+ koi_fpflag_co: int = Field(..., ge=0, le=1, description="Centroid offset flag")
62
+ koi_period: float = Field(..., description="Orbital period in days")
63
+ koi_depth: float = Field(..., description="Transit depth in parts per million")
64
+ koi_fpflag_nt: int = Field(..., ge=0, le=1, description="Not transit-like flag")
65
+ koi_insol: float = Field(..., description="Insolation flux in Earth units")
66
+
67
+ class Config:
68
+ json_schema_extra = {
69
+ "example": {
70
+ "koi_model_snr": 15.5,
71
+ "koi_prad": 2.3,
72
+ "koi_fpflag_ss": 0,
73
+ "koi_fpflag_co": 0,
74
+ "koi_period": 10.5,
75
+ "koi_depth": 500.0,
76
+ "koi_fpflag_nt": 0,
77
+ "koi_insol": 1.2
78
+ }
79
+ }
80
+
81
+ class PredictionOutput(BaseModel):
82
+ prediction: int
83
+ probability: float
84
+ classification: str
85
+
86
+ class BatchPredictionOutput(BaseModel):
87
+ predictions: List[dict]
88
+ total_processed: int
89
+
90
+ def prepare_features(data: dict) -> np.ndarray:
91
+ """Convert input dictionary to feature array in correct order"""
92
+ feature_order = [
93
+ 'koi_model_snr', 'koi_prad', 'koi_fpflag_ss', 'koi_fpflag_co',
94
+ 'koi_period', 'koi_depth', 'koi_fpflag_nt', 'koi_insol'
95
+ ]
96
+ return np.array([[data[f] for f in feature_order]])
97
+
98
+ def preprocess(data):
99
+ for col in data.columns:
100
+ if data[col].isnull().sum() > 0 and data[col].dtype != 'O':
101
+ data.fillna({col: data[col].fillna(0)}, inplace=True)
102
+ return data
103
+
104
+ def make_prediction(features: np.ndarray, model_name: str):
105
+ """Make prediction using the selected model"""
106
+ if model_name in models:
107
+ model = models[model_name]
108
+ prediction = model.predict(features)[0]
109
+ probability = model.predict_proba(features)[0][1] if hasattr(model, 'predict_proba') else 0.5
110
+ else:
111
+ raise HTTPException(status_code=400, detail=f"Model {model_name} not found")
112
+
113
+ # Placeholder for demonstration - replace with actual model prediction
114
+ prediction = np.random.choice([0, 1, 2]) # 0=false positive, 1=confirmed, 2=candidate
115
+ probability = np.random.random()
116
+
117
+ return prediction, probability
118
+
119
+ @app.get("/")
120
+ def read_root():
121
+ return {
122
+ "message": "Exoplanet Prediction API",
123
+ "available_models": [model.value for model in ModelType],
124
+ "endpoints": {
125
+ "/predict": "Single prediction (POST)",
126
+ "/predict/batch": "Batch prediction from CSV (POST)",
127
+ "/models": "List available models (GET)",
128
+ "/health": "Health check (GET)",
129
+ "/docs": "API documentation"
130
+ }
131
+ }
132
+
133
+ @app.get("/models")
134
+ def list_models():
135
+ """List all available models"""
136
+ return {
137
+ "available_models": [model.value for model in ModelType],
138
+ "loaded_models": list(models.keys()) if models else []
139
+ }
140
+
141
+ @app.get("/health")
142
+ def health_check():
143
+ return {
144
+ "status": "healthy",
145
+ "models_loaded": len(models),
146
+ "available_models": [model.value for model in ModelType]
147
+ }
148
+
149
+ @app.post("/predict", response_model=PredictionOutput)
150
+ def predict_single(
151
+ input_data: PredictionInput,
152
+ model: ModelType = Query(ModelType.ensemble, description="Model to use for prediction")
153
+ ):
154
+ """
155
+ Make a single prediction for exoplanet classification using the specified model
156
+ """
157
+ try:
158
+ features = prepare_features(input_data.dict())
159
+ prediction, probability = make_prediction(features, model.value)
160
+
161
+ if prediction == 1:
162
+ classification = "Confirmed Exoplanet"
163
+ elif prediction == 2:
164
+ classification = "Exoplanet Candidate"
165
+ else:
166
+ classification = "False Positive"
167
+
168
+ return PredictionOutput(
169
+ prediction=int(prediction),
170
+ probability=float(probability),
171
+ classification=classification
172
+ )
173
+ except Exception as e:
174
+ raise HTTPException(status_code=500, detail=f"Prediction error: {str(e)}")
175
+
176
+ @app.post("/predict/batch")
177
+ async def predict_batch(
178
+ file: UploadFile = File(...),
179
+ model: ModelType = Query(ModelType.ensemble, description="Model to use for predictions")
180
+ ):
181
+ """
182
+ Make batch predictions from CSV file using the specified model
183
+ Returns a CSV file with predictions
184
+ """
185
+ if not file.filename.endswith('.csv'):
186
+ raise HTTPException(status_code=400, detail="File must be a CSV")
187
+
188
+ try:
189
+ # Read CSV file
190
+ contents = await file.read()
191
+ df = pd.read_csv(io.BytesIO(contents))
192
+
193
+ # Validate required columns
194
+ required_cols = [
195
+ 'koi_model_snr', 'koi_prad', 'koi_fpflag_ss', 'koi_fpflag_co',
196
+ 'koi_period', 'koi_depth', 'koi_fpflag_nt', 'koi_insol'
197
+ ]
198
+ missing_cols = set(required_cols) - set(df.columns)
199
+ if missing_cols:
200
+ raise HTTPException(
201
+ status_code=400,
202
+ detail=f"Missing required columns: {missing_cols}"
203
+ )
204
+
205
+ cleaned_df = preprocess(df)
206
+
207
+ # Make predictions
208
+ predictions = []
209
+ probabilities = []
210
+
211
+ for _, row in cleaned_df.iterrows():
212
+ features = prepare_features(row[required_cols].to_dict())
213
+ pred, prob = make_prediction(features, model.value)
214
+ predictions.append(pred)
215
+ probabilities.append(prob)
216
+
217
+ # Add predictions to dataframe
218
+ cleaned_df['prediction'] = predictions
219
+ cleaned_df['probability'] = probabilities
220
+ cleaned_df['classification'] = cleaned_df['prediction'].map({
221
+ 1: 'Confirmed Exoplanet',
222
+ 2: 'Exoplanet Candidate',
223
+ 0: 'False Positive'
224
+ })
225
+
226
+ # Convert to CSV for download
227
+ output = io.StringIO()
228
+ df.to_csv(output, index=False)
229
+ output.seek(0)
230
+
231
+ return StreamingResponse(
232
+ iter([output.getvalue()]),
233
+ media_type="text/csv",
234
+ headers={"Content-Disposition": f"attachment; filename=predictions_{model.value}_{file.filename}"}
235
+ )
236
+
237
+ except pd.errors.EmptyDataError:
238
+ raise HTTPException(status_code=400, detail="CSV file is empty")
239
+ except Exception as e:
240
+ raise HTTPException(status_code=500, detail=f"Processing error: {str(e)}")
241
+
242
+ @app.post("/predict/batch/json", response_model=BatchPredictionOutput)
243
+ async def predict_batch_json(
244
+ file: UploadFile = File(...),
245
+ model: ModelType = Query(ModelType.ensemble, description="Model to use for predictions")
246
+ ):
247
+ """
248
+ Make batch predictions from CSV file using the specified model
249
+ Returns JSON response with predictions
250
+ """
251
+ if not file.filename.endswith('.csv'):
252
+ raise HTTPException(status_code=400, detail="File must be a CSV")
253
+
254
+ try:
255
+ contents = await file.read()
256
+ df = pd.read_csv(io.BytesIO(contents))
257
+
258
+ print("file received and read")
259
+
260
+ required_cols = [
261
+ 'koi_model_snr', 'koi_prad', 'koi_fpflag_ss', 'koi_fpflag_co',
262
+ 'koi_period', 'koi_depth', 'koi_fpflag_nt', 'koi_insol'
263
+ ]
264
+ missing_cols = set(required_cols) - set(df.columns)
265
+ if missing_cols:
266
+ raise HTTPException(
267
+ status_code=400,
268
+ detail=f"Missing required columns: {missing_cols}"
269
+ )
270
+
271
+ cleaned_df = preprocess(df)
272
+
273
+ print("data cleaned")
274
+
275
+ results = []
276
+ for idx, row in cleaned_df.iterrows():
277
+ features = prepare_features(row[required_cols].to_dict())
278
+ pred, prob = make_prediction(features, model.value)
279
+
280
+ if pred == 1:
281
+ classification = "Confirmed Exoplanet"
282
+ elif pred == 2:
283
+ classification = "Exoplanet Candidate"
284
+ else:
285
+ classification = "False Positive"
286
+
287
+ results.append({
288
+ "row_index": int(idx),
289
+ "prediction": int(pred),
290
+ "probability": float(prob),
291
+ "classification": classification
292
+ })
293
+
294
+ return BatchPredictionOutput(
295
+ predictions=results,
296
+ total_processed=len(results)
297
+ )
298
+
299
+ except Exception as e:
300
+ raise HTTPException(status_code=500, detail=f"Processing error: {str(e)}")
301
+
302
+ if __name__ == "__main__":
303
+ import uvicorn
304
+ uvicorn.run(app, host="0.0.0.0", port=8000)
models/xgb_rf.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:694f36481655114b66faf14964ee947499e290880e7f7ef17e4488a278286171
3
+ size 24705012
models/xgboost.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:91de04887b8b3a6303446497ac82f52dc2e5248d8c378df6f8818f2020a7fa86
3
+ size 666119
requirements.txt ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ annotated-types==0.7.0
2
+ anyio==4.11.0
3
+ click==8.3.0
4
+ fastapi==0.118.0
5
+ h11==0.16.0
6
+ idna==3.10
7
+ joblib==1.5.2
8
+ numpy==2.3.3
9
+ nvidia-nccl-cu12==2.28.3
10
+ pandas==2.3.3
11
+ pydantic==2.11.10
12
+ pydantic_core==2.33.2
13
+ python-dateutil==2.9.0.post0
14
+ python-multipart==0.0.20
15
+ pytz==2025.2
16
+ scikit-learn==1.7.2
17
+ scipy==1.16.2
18
+ six==1.17.0
19
+ sniffio==1.3.1
20
+ starlette==0.48.0
21
+ threadpoolctl==3.6.0
22
+ typing-inspection==0.4.2
23
+ typing_extensions==4.15.0
24
+ tzdata==2025.2
25
+ uvicorn==0.37.0
26
+ xgboost==3.0.5