adema5051 commited on
Commit
e66010b
·
verified ·
1 Parent(s): 036905e

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +214 -74
templates/index.html CHANGED
@@ -1,5 +1,6 @@
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">
@@ -11,7 +12,8 @@
11
  box-sizing: border-box;
12
  }
13
 
14
- html, body {
 
15
  height: 100%;
16
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
17
  }
@@ -41,15 +43,22 @@
41
  left: 0;
42
  width: 100%;
43
  height: 100%;
44
- background:
45
  radial-gradient(circle at 20% 50%, rgba(59, 130, 246, 0.15) 0%, transparent 50%),
46
  radial-gradient(circle at 80% 50%, rgba(139, 92, 246, 0.15) 0%, transparent 50%);
47
  animation: pulse 8s ease-in-out infinite;
48
  }
49
 
50
  @keyframes pulse {
51
- 0%, 100% { opacity: 1; }
52
- 50% { opacity: 0.5; }
 
 
 
 
 
 
 
53
  }
54
 
55
  .page-wrapper {
@@ -80,7 +89,9 @@
80
  }
81
 
82
  @keyframes shine {
83
- to { background-position: 200% center; }
 
 
84
  }
85
 
86
  .hero-header .subtitle {
@@ -196,6 +207,7 @@
196
  opacity: 0;
197
  transform: translateY(20px);
198
  }
 
199
  to {
200
  opacity: 1;
201
  transform: translateY(0);
@@ -342,6 +354,36 @@
342
  cursor: not-allowed;
343
  }
344
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  /* Checkbox styling */
346
  .checkbox-row {
347
  display: flex;
@@ -408,7 +450,7 @@
408
  left: -100%;
409
  width: 100%;
410
  height: 100%;
411
- background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
412
  transition: left 0.5s;
413
  }
414
 
@@ -449,7 +491,9 @@
449
  }
450
 
451
  @keyframes spin {
452
- to { transform: rotate(360deg); }
 
 
453
  }
454
 
455
  .loading-state p {
@@ -489,23 +533,27 @@
489
  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
490
  }
491
 
492
- .risk-very-high {
493
  background: linear-gradient(135deg, #dc2626, #b91c1c);
494
  box-shadow: 0 4px 15px rgba(220, 38, 38, 0.4);
495
  }
496
- .risk-high {
 
497
  background: linear-gradient(135deg, #ea580c, #c2410c);
498
  box-shadow: 0 4px 15px rgba(234, 88, 12, 0.4);
499
  }
500
- .risk-moderate {
 
501
  background: linear-gradient(135deg, #ca8a04, #a16207);
502
  box-shadow: 0 4px 15px rgba(202, 138, 4, 0.4);
503
  }
504
- .risk-low {
 
505
  background: linear-gradient(135deg, #16a34a, #15803d);
506
  box-shadow: 0 4px 15px rgba(22, 163, 74, 0.4);
507
  }
508
- .risk-very-low {
 
509
  background: linear-gradient(135deg, #0891b2, #0e7490);
510
  box-shadow: 0 4px 15px rgba(8, 145, 178, 0.4);
511
  }
@@ -627,15 +675,17 @@
627
  position: relative;
628
  }
629
 
630
- .confidence-high {
631
  background: linear-gradient(90deg, #16a34a, #22c55e);
632
  box-shadow: 0 0 20px rgba(34, 197, 94, 0.4);
633
  }
634
- .confidence-moderate-fill {
 
635
  background: linear-gradient(90deg, #ca8a04, #fbbf24);
636
  box-shadow: 0 0 20px rgba(251, 191, 36, 0.4);
637
  }
638
- .confidence-low-fill {
 
639
  background: linear-gradient(90deg, #ea580c, #f97316);
640
  box-shadow: 0 0 20px rgba(249, 115, 22, 0.4);
641
  }
@@ -823,8 +873,15 @@
823
 
824
  /* Pulse animation for predicted height */
825
  @keyframes height-pulse {
826
- 0%, 100% { background: rgba(0, 0, 0, 0.3); }
827
- 50% { background: rgba(59, 130, 246, 0.3); }
 
 
 
 
 
 
 
828
  }
829
 
830
  .height-pulse {
@@ -883,17 +940,22 @@
883
  }
884
  </style>
885
  </head>
 
886
  <body>
887
  <div class="hero-background"></div>
888
-
889
  <div class="page-wrapper">
890
- <header class="hero-header">
891
-
892
- <div>
893
- <h1>
 
 
 
 
894
  Flood Vulnerability Assessment
895
  </h1>
896
- <p class="subtitle">
897
  Global building-level assessment
898
  </p>
899
  </div>
@@ -925,13 +987,15 @@
925
  <div class="form-grid">
926
  <div class="input-card">
927
  <label for="latitude">Latitude</label>
928
- <input type="text" id="latitude" name="latitude" inputmode="text" pattern="-?[0-9]*[.,]?[0-9]*" required placeholder="40.7128">
 
929
  <p class="helper-text">Range: -90 to 90</p>
930
  </div>
931
 
932
  <div class="input-card">
933
  <label for="longitude">Longitude</label>
934
- <input type="text" id="longitude" name="longitude" inputmode="text" pattern="-?[0-9]*[.,]?[0-9]*" autocomplete="off" required placeholder="-74.0060">
 
935
  <p class="helper-text">Range: -180 to 180</p>
936
  </div>
937
 
@@ -939,14 +1003,23 @@
939
  <label for="height">Building Height</label>
940
  <div class="height-group">
941
  <input type="number" id="height" step="any" value="0" placeholder="5.0">
942
- <button type="button" class="predict-height-btn" data-lat-id="latitude" data-lon-id="longitude" data-height-id="height" data-error-id="basic-error">Predict</button>
 
 
943
  </div>
944
- <p class="helper-text">Meters above ground (0 = ground level)</p>
 
 
 
 
 
 
945
  </div>
946
 
947
  <div class="input-card">
948
  <label for="basement">Basement Depth</label>
949
- <input type="text" id="basement" name="basement" value="0" max="0" inputmode="text" pattern="-?[0-9]*[.,]?[0-9]*" placeholder="-2.0">
 
950
  <p class="helper-text">Meters below ground (negative values)</p>
951
  </div>
952
  </div>
@@ -976,26 +1049,31 @@
976
  <div class="form-grid">
977
  <div class="input-card">
978
  <label for="latitude2">Latitude</label>
979
- <input type="text" id="latitude2" name="latitude2" pattern="-?[0-9]*[.,]?[0-9]*" autocomplete="off" required placeholder="40.7128">
 
980
  </div>
981
 
982
  <div class="input-card">
983
  <label for="longitude2">Longitude</label>
984
- <input type="text" id="longitude2" name="longitude2" pattern="-?[0-9]*[.,]?[0-9]*" autocomplete="off" required placeholder="-74.0060">
 
985
  </div>
986
 
987
  <div class="input-card">
988
  <label for="height2">Building Height</label>
989
  <div class="height-group">
990
  <input type="number" id="height2" step="any" value="0">
991
- <button type="button" class="predict-height-btn" data-lat-id="latitude2" data-lon-id="longitude2" data-height-id="height2" data-error-id="explained-error">Predict</button>
 
 
992
  </div>
993
  <p class="helper-text">Meters above ground</p>
994
  </div>
995
 
996
  <div class="input-card">
997
  <label for="basement2">Basement Depth</label>
998
- <input type="text" id="basement2" name="basement2" inputmode="text" pattern="-?[0-9]*[.,]?[0-9]*" step="0.1" value="0" max="0">
 
999
  <p class="helper-text">Meters below ground (negative)</p>
1000
  </div>
1001
  </div>
@@ -1025,26 +1103,31 @@
1025
  <div class="form-grid">
1026
  <div class="input-card">
1027
  <label for="latitude3">Latitude</label>
1028
- <input type="text" id="latitude3" name="latitude3" pattern="-?[0-9]*[.,]?[0-9]*" autocomplete="off" required placeholder="40.7128">
 
1029
  </div>
1030
 
1031
  <div class="input-card">
1032
  <label for="longitude3">Longitude</label>
1033
- <input type="text" id="longitude3" name="longitude3" pattern="-?[0-9]*[.,]?[0-9]*" autocomplete="off" required placeholder="-74.0060">
 
1034
  </div>
1035
 
1036
  <div class="input-card">
1037
  <label for="height3">Building Height</label>
1038
  <div class="height-group">
1039
  <input type="number" id="height3" step="any" value="0">
1040
- <button type="button" class="predict-height-btn" data-lat-id="latitude3" data-lon-id="longitude3" data-height-id="height3" data-error-id="multihazard-error">Predict</button>
 
 
1041
  </div>
1042
  <p class="helper-text">Meters above ground</p>
1043
  </div>
1044
 
1045
  <div class="input-card">
1046
  <label for="basement3">Basement Depth</label>
1047
- <input type="text" id="basement3" name="basement3" inputmode="text" pattern="-?[0-9]*[.,]?[0-9]*" step="0.1" value="0" max="0">
 
1048
  <p class="helper-text">Meters below ground (negative)</p>
1049
  </div>
1050
  </div>
@@ -1083,7 +1166,8 @@
1083
  <div class="input-card">
1084
  <label for="csvFile">CSV File</label>
1085
  <input type="file" id="csvFile" accept=".csv">
1086
- <p class="helper-text">Required columns: latitude, longitude in decimal degrees (e.g. 29.17, -95.31). Optional: height(meters), basement (should be negative).</p>
 
1087
  </div>
1088
  </div>
1089
 
@@ -1202,34 +1286,34 @@
1202
  document.querySelectorAll('.nav-link').forEach(link => {
1203
  link.classList.remove('active');
1204
  });
1205
-
1206
  document.getElementById(tabName + '-card').classList.add('active');
1207
  event.target.classList.add('active');
1208
  }
1209
-
1210
  async function assessLocation(event, endpoint, resultsId) {
1211
  event.preventDefault();
1212
-
1213
  const tabName = resultsId.split('-')[0];
1214
  const suffix = endpoint === '/assess' ? '' : (endpoint === '/explain' ? '2' : '3');
1215
  const latitude = parseFloat(document.getElementById('latitude' + suffix).value);
1216
  const longitude = parseFloat(document.getElementById('longitude' + suffix).value);
1217
  const height = parseFloat(document.getElementById('height' + suffix).value) || 0;
1218
  const basement = parseFloat(document.getElementById('basement' + suffix).value) || 0;
1219
-
1220
  document.getElementById(tabName + '-loading').style.display = 'block';
1221
  document.getElementById(resultsId).style.display = 'none';
1222
  document.getElementById(tabName + '-error').style.display = 'none';
1223
-
1224
  try {
1225
  const response = await fetch(endpoint, {
1226
  method: 'POST',
1227
  headers: { 'Content-Type': 'application/json' },
1228
  body: JSON.stringify({ latitude, longitude, height, basement })
1229
  });
1230
-
1231
  const data = await response.json();
1232
-
1233
  if (data.status === 'success') {
1234
  displayResults(data, resultsId, endpoint);
1235
  } else {
@@ -1242,7 +1326,7 @@
1242
  document.getElementById(tabName + '-loading').style.display = 'none';
1243
  }
1244
  }
1245
-
1246
  function formatFlag(flag) {
1247
  const flagMessages = {
1248
  'missing_elevation': 'Elevation data unavailable',
@@ -1255,20 +1339,20 @@
1255
  };
1256
  return flagMessages[flag] || flag.replace(/_/g, ' ');
1257
  }
1258
-
1259
  function displayResults(data, resultsId, endpoint) {
1260
  const resultsDiv = document.getElementById(resultsId);
1261
  const assessment = data.assessment;
1262
-
1263
  let html = '<div class="results-header">';
1264
  html += '<h2>Assessment Complete</h2>';
1265
-
1266
  const riskClass = 'risk-' + assessment.risk_level.replace(/_/g, '-');
1267
  html += `<div class="risk-badge ${riskClass}">${assessment.risk_level.replace(/_/g, ' ')}</div>`;
1268
  html += '</div>';
1269
-
1270
  html += '<div class="stats-grid">';
1271
-
1272
  if (assessment.confidence_interval) {
1273
  const ci = assessment.confidence_interval;
1274
  html += `
@@ -1288,14 +1372,14 @@
1288
  </div>
1289
  `;
1290
  }
1291
-
1292
  html += `
1293
  <div class="stat-card">
1294
  <div class="stat-label">Elevation</div>
1295
  <div class="stat-value">${assessment.elevation_m}m</div>
1296
  </div>
1297
  `;
1298
-
1299
  if (assessment.distance_to_water_m !== null) {
1300
  html += `
1301
  <div class="stat-card">
@@ -1304,9 +1388,9 @@
1304
  </div>
1305
  `;
1306
  }
1307
-
1308
  html += '</div>';
1309
-
1310
  if (assessment.uncertainty_analysis) {
1311
  const ua = assessment.uncertainty_analysis;
1312
  const confidenceValue = parseFloat(ua.confidence) || 0;
@@ -1332,13 +1416,13 @@
1332
  </p>
1333
  </div>
1334
  `;
1335
-
1336
  if (ua.data_quality_flags && ua.data_quality_flags.length > 0) {
1337
- const criticalFlags = ua.data_quality_flags.filter(flag =>
1338
- flag === 'steep_terrain_dem_error_high' ||
1339
  flag === 'coastal_surge_risk_not_modeled'
1340
  );
1341
-
1342
  if (criticalFlags.length > 0) {
1343
  html += '<div class="quality-warning"><h4>⚠ Data Quality Notes</h4><ul>';
1344
  criticalFlags.forEach(flag => {
@@ -1348,7 +1432,7 @@
1348
  }
1349
  }
1350
  }
1351
-
1352
  html += '<div class="detail-section"><h3>Terrain Analysis</h3>';
1353
  html += `
1354
  <div class="metric-row">
@@ -1369,7 +1453,7 @@
1369
  </div>
1370
  `;
1371
  html += '</div>';
1372
-
1373
  if (assessment.hazard_breakdown) {
1374
  const hb = assessment.hazard_breakdown;
1375
  html += '<div class="detail-section"><h3>Hazard Breakdown</h3>';
@@ -1392,13 +1476,13 @@
1392
  html += `<p style="margin-top: 1.5rem;"><strong>Dominant Hazard:</strong> ${assessment.dominant_hazard.replace(/_/g, ' ').toUpperCase()}</p>`;
1393
  html += '</div>';
1394
  }
1395
-
1396
  if (data.explanation) {
1397
  const exp = data.explanation;
1398
  html += '<div class="explanation-section">';
1399
  html += '<h3>Risk Factor Analysis</h3>';
1400
  html += `<p style="margin-bottom: 1.5rem; color: #cbd5e1;"><strong>Top Risk Driver:</strong> ${exp.top_risk_driver}</p>`;
1401
-
1402
  exp.explanations.forEach(factor => {
1403
  html += `
1404
  <div class="factor-item">
@@ -1410,30 +1494,85 @@
1410
  </div>
1411
  `;
1412
  });
1413
-
1414
  html += '</div>';
1415
  }
1416
-
1417
  resultsDiv.innerHTML = html;
1418
  resultsDiv.style.display = 'block';
1419
  }
1420
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1421
  async function uploadBatch() {
1422
  const fileInput = document.getElementById('csvFile');
1423
  const file = fileInput.files[0];
1424
-
1425
  if (!file) {
1426
  alert('Please select a CSV file');
1427
  return;
1428
  }
1429
-
1430
  document.getElementById('batch-loading').style.display = 'block';
1431
  document.getElementById('batch-results').style.display = 'none';
1432
  document.getElementById('batch-error').style.display = 'none';
1433
-
1434
  const formData = new FormData();
1435
  formData.append('file', file);
1436
-
1437
  try {
1438
  const mode = document.getElementById('batchMode').value;
1439
  const usePredicted = document.getElementById('usePredictedHeight').checked;
@@ -1448,20 +1587,20 @@
1448
  method: 'POST',
1449
  body: formData
1450
  });
1451
-
1452
  if (response.ok) {
1453
  const blob = await response.blob();
1454
  const url = window.URL.createObjectURL(blob);
1455
  const a = document.createElement('a');
1456
  a.href = url;
1457
- const filename = mode === 'multihazard'
1458
- ? 'multihazard_results.csv'
1459
  : 'vulnerability_results.csv';
1460
  a.download = filename;
1461
  document.body.appendChild(a);
1462
  a.click();
1463
  window.URL.revokeObjectURL(url);
1464
-
1465
  document.getElementById('batch-results').innerHTML = `
1466
  <div class="results-header">
1467
  <h2>✓ Processing Complete</h2>
@@ -1481,4 +1620,5 @@
1481
  }
1482
  </script>
1483
  </body>
1484
- </html>
 
 
1
  <!DOCTYPE html>
2
  <html lang="en">
3
+
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 
12
  box-sizing: border-box;
13
  }
14
 
15
+ html,
16
+ body {
17
  height: 100%;
18
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
19
  }
 
43
  left: 0;
44
  width: 100%;
45
  height: 100%;
46
+ background:
47
  radial-gradient(circle at 20% 50%, rgba(59, 130, 246, 0.15) 0%, transparent 50%),
48
  radial-gradient(circle at 80% 50%, rgba(139, 92, 246, 0.15) 0%, transparent 50%);
49
  animation: pulse 8s ease-in-out infinite;
50
  }
51
 
52
  @keyframes pulse {
53
+
54
+ 0%,
55
+ 100% {
56
+ opacity: 1;
57
+ }
58
+
59
+ 50% {
60
+ opacity: 0.5;
61
+ }
62
  }
63
 
64
  .page-wrapper {
 
89
  }
90
 
91
  @keyframes shine {
92
+ to {
93
+ background-position: 200% center;
94
+ }
95
  }
96
 
97
  .hero-header .subtitle {
 
207
  opacity: 0;
208
  transform: translateY(20px);
209
  }
210
+
211
  to {
212
  opacity: 1;
213
  transform: translateY(0);
 
354
  cursor: not-allowed;
355
  }
356
 
357
+ .gba-height-btn {
358
+ margin-top: 0.4rem;
359
+ align-self: flex-start;
360
+
361
+ background: rgba(96, 165, 250, 0.08);
362
+ border: 1px solid rgba(96, 165, 250, 0.25);
363
+ border-radius: 8px;
364
+
365
+ padding: 0.25rem 0.6rem;
366
+ font-size: 0.7em;
367
+ font-weight: 500;
368
+
369
+ color: #93c5fd;
370
+ cursor: pointer;
371
+
372
+ transition: background 0.2s ease, border 0.2s ease, color 0.2s ease;
373
+ }
374
+
375
+ .gba-height-btn:hover {
376
+ background: rgba(96, 165, 250, 0.18);
377
+ border-color: rgba(96, 165, 250, 0.5);
378
+ color: #bfdbfe;
379
+ }
380
+
381
+
382
+ .gba-height-btn:disabled {
383
+ opacity: 0.4;
384
+ cursor: not-allowed;
385
+ }
386
+
387
  /* Checkbox styling */
388
  .checkbox-row {
389
  display: flex;
 
450
  left: -100%;
451
  width: 100%;
452
  height: 100%;
453
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
454
  transition: left 0.5s;
455
  }
456
 
 
491
  }
492
 
493
  @keyframes spin {
494
+ to {
495
+ transform: rotate(360deg);
496
+ }
497
  }
498
 
499
  .loading-state p {
 
533
  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
534
  }
535
 
536
+ .risk-very-high {
537
  background: linear-gradient(135deg, #dc2626, #b91c1c);
538
  box-shadow: 0 4px 15px rgba(220, 38, 38, 0.4);
539
  }
540
+
541
+ .risk-high {
542
  background: linear-gradient(135deg, #ea580c, #c2410c);
543
  box-shadow: 0 4px 15px rgba(234, 88, 12, 0.4);
544
  }
545
+
546
+ .risk-moderate {
547
  background: linear-gradient(135deg, #ca8a04, #a16207);
548
  box-shadow: 0 4px 15px rgba(202, 138, 4, 0.4);
549
  }
550
+
551
+ .risk-low {
552
  background: linear-gradient(135deg, #16a34a, #15803d);
553
  box-shadow: 0 4px 15px rgba(22, 163, 74, 0.4);
554
  }
555
+
556
+ .risk-very-low {
557
  background: linear-gradient(135deg, #0891b2, #0e7490);
558
  box-shadow: 0 4px 15px rgba(8, 145, 178, 0.4);
559
  }
 
675
  position: relative;
676
  }
677
 
678
+ .confidence-high {
679
  background: linear-gradient(90deg, #16a34a, #22c55e);
680
  box-shadow: 0 0 20px rgba(34, 197, 94, 0.4);
681
  }
682
+
683
+ .confidence-moderate-fill {
684
  background: linear-gradient(90deg, #ca8a04, #fbbf24);
685
  box-shadow: 0 0 20px rgba(251, 191, 36, 0.4);
686
  }
687
+
688
+ .confidence-low-fill {
689
  background: linear-gradient(90deg, #ea580c, #f97316);
690
  box-shadow: 0 0 20px rgba(249, 115, 22, 0.4);
691
  }
 
873
 
874
  /* Pulse animation for predicted height */
875
  @keyframes height-pulse {
876
+
877
+ 0%,
878
+ 100% {
879
+ background: rgba(0, 0, 0, 0.3);
880
+ }
881
+
882
+ 50% {
883
+ background: rgba(59, 130, 246, 0.3);
884
+ }
885
  }
886
 
887
  .height-pulse {
 
940
  }
941
  </style>
942
  </head>
943
+
944
  <body>
945
  <div class="hero-background"></div>
946
+
947
  <div class="page-wrapper">
948
+ <header class="hero-header" style="position: relative; padding-top: 40px; text-align: center;">
949
+
950
+ <div style="position: absolute; top: 7%; left: 1%; font-size: 3.2vw; line-height: 0.8;">
951
+ 💦💧🌊
952
+ </div>
953
+
954
+ <div style="display: inline-block; text-align: center; max-width: 90%; margin: 0 auto;">
955
+ <h1 style="margin: 0; font-size: 3rem; line-height: 1.2;">
956
  Flood Vulnerability Assessment
957
  </h1>
958
+ <p class="subtitle" style="margin: 0.2rem 0 0 0; font-size: 1rem; line-height: 1.2;">
959
  Global building-level assessment
960
  </p>
961
  </div>
 
987
  <div class="form-grid">
988
  <div class="input-card">
989
  <label for="latitude">Latitude</label>
990
+ <input type="text" id="latitude" name="latitude" inputmode="text"
991
+ pattern="-?[0-9]*[.,]?[0-9]*" required placeholder="40.7128">
992
  <p class="helper-text">Range: -90 to 90</p>
993
  </div>
994
 
995
  <div class="input-card">
996
  <label for="longitude">Longitude</label>
997
+ <input type="text" id="longitude" name="longitude" inputmode="text"
998
+ pattern="-?[0-9]*[.,]?[0-9]*" autocomplete="off" required placeholder="-74.0060">
999
  <p class="helper-text">Range: -180 to 180</p>
1000
  </div>
1001
 
 
1003
  <label for="height">Building Height</label>
1004
  <div class="height-group">
1005
  <input type="number" id="height" step="any" value="0" placeholder="5.0">
1006
+ <button type="button" class="predict-height-btn" data-lat-id="latitude"
1007
+ data-lon-id="longitude" data-height-id="height"
1008
+ data-error-id="basic-error">Predict</button>
1009
  </div>
1010
+ <button type="button" class="gba-height-btn" data-lat-id="latitude"
1011
+ data-lon-id="longitude" data-height-id="height" data-error-id="basic-error">
1012
+ Get height from Global Building Atlas
1013
+ </button>
1014
+ <p class="helper-text">
1015
+ Meters above ground (0 = ground level)
1016
+ </p>
1017
  </div>
1018
 
1019
  <div class="input-card">
1020
  <label for="basement">Basement Depth</label>
1021
+ <input type="text" id="basement" name="basement" value="0" max="0" inputmode="text"
1022
+ pattern="-?[0-9]*[.,]?[0-9]*" placeholder="-2.0">
1023
  <p class="helper-text">Meters below ground (negative values)</p>
1024
  </div>
1025
  </div>
 
1049
  <div class="form-grid">
1050
  <div class="input-card">
1051
  <label for="latitude2">Latitude</label>
1052
+ <input type="text" id="latitude2" name="latitude2" pattern="-?[0-9]*[.,]?[0-9]*"
1053
+ autocomplete="off" required placeholder="40.7128">
1054
  </div>
1055
 
1056
  <div class="input-card">
1057
  <label for="longitude2">Longitude</label>
1058
+ <input type="text" id="longitude2" name="longitude2" pattern="-?[0-9]*[.,]?[0-9]*"
1059
+ autocomplete="off" required placeholder="-74.0060">
1060
  </div>
1061
 
1062
  <div class="input-card">
1063
  <label for="height2">Building Height</label>
1064
  <div class="height-group">
1065
  <input type="number" id="height2" step="any" value="0">
1066
+ <button type="button" class="predict-height-btn" data-lat-id="latitude2"
1067
+ data-lon-id="longitude2" data-height-id="height2"
1068
+ data-error-id="explained-error">Predict</button>
1069
  </div>
1070
  <p class="helper-text">Meters above ground</p>
1071
  </div>
1072
 
1073
  <div class="input-card">
1074
  <label for="basement2">Basement Depth</label>
1075
+ <input type="text" id="basement2" name="basement2" inputmode="text"
1076
+ pattern="-?[0-9]*[.,]?[0-9]*" step="0.1" value="0" max="0">
1077
  <p class="helper-text">Meters below ground (negative)</p>
1078
  </div>
1079
  </div>
 
1103
  <div class="form-grid">
1104
  <div class="input-card">
1105
  <label for="latitude3">Latitude</label>
1106
+ <input type="text" id="latitude3" name="latitude3" pattern="-?[0-9]*[.,]?[0-9]*"
1107
+ autocomplete="off" required placeholder="40.7128">
1108
  </div>
1109
 
1110
  <div class="input-card">
1111
  <label for="longitude3">Longitude</label>
1112
+ <input type="text" id="longitude3" name="longitude3" pattern="-?[0-9]*[.,]?[0-9]*"
1113
+ autocomplete="off" required placeholder="-74.0060">
1114
  </div>
1115
 
1116
  <div class="input-card">
1117
  <label for="height3">Building Height</label>
1118
  <div class="height-group">
1119
  <input type="number" id="height3" step="any" value="0">
1120
+ <button type="button" class="predict-height-btn" data-lat-id="latitude3"
1121
+ data-lon-id="longitude3" data-height-id="height3"
1122
+ data-error-id="multihazard-error">Predict</button>
1123
  </div>
1124
  <p class="helper-text">Meters above ground</p>
1125
  </div>
1126
 
1127
  <div class="input-card">
1128
  <label for="basement3">Basement Depth</label>
1129
+ <input type="text" id="basement3" name="basement3" inputmode="text"
1130
+ pattern="-?[0-9]*[.,]?[0-9]*" step="0.1" value="0" max="0">
1131
  <p class="helper-text">Meters below ground (negative)</p>
1132
  </div>
1133
  </div>
 
1166
  <div class="input-card">
1167
  <label for="csvFile">CSV File</label>
1168
  <input type="file" id="csvFile" accept=".csv">
1169
+ <p class="helper-text">Required columns: latitude, longitude in decimal degrees (e.g. 29.17,
1170
+ -95.31). Optional: height(meters), basement (should be negative).</p>
1171
  </div>
1172
  </div>
1173
 
 
1286
  document.querySelectorAll('.nav-link').forEach(link => {
1287
  link.classList.remove('active');
1288
  });
1289
+
1290
  document.getElementById(tabName + '-card').classList.add('active');
1291
  event.target.classList.add('active');
1292
  }
1293
+
1294
  async function assessLocation(event, endpoint, resultsId) {
1295
  event.preventDefault();
1296
+
1297
  const tabName = resultsId.split('-')[0];
1298
  const suffix = endpoint === '/assess' ? '' : (endpoint === '/explain' ? '2' : '3');
1299
  const latitude = parseFloat(document.getElementById('latitude' + suffix).value);
1300
  const longitude = parseFloat(document.getElementById('longitude' + suffix).value);
1301
  const height = parseFloat(document.getElementById('height' + suffix).value) || 0;
1302
  const basement = parseFloat(document.getElementById('basement' + suffix).value) || 0;
1303
+
1304
  document.getElementById(tabName + '-loading').style.display = 'block';
1305
  document.getElementById(resultsId).style.display = 'none';
1306
  document.getElementById(tabName + '-error').style.display = 'none';
1307
+
1308
  try {
1309
  const response = await fetch(endpoint, {
1310
  method: 'POST',
1311
  headers: { 'Content-Type': 'application/json' },
1312
  body: JSON.stringify({ latitude, longitude, height, basement })
1313
  });
1314
+
1315
  const data = await response.json();
1316
+
1317
  if (data.status === 'success') {
1318
  displayResults(data, resultsId, endpoint);
1319
  } else {
 
1326
  document.getElementById(tabName + '-loading').style.display = 'none';
1327
  }
1328
  }
1329
+
1330
  function formatFlag(flag) {
1331
  const flagMessages = {
1332
  'missing_elevation': 'Elevation data unavailable',
 
1339
  };
1340
  return flagMessages[flag] || flag.replace(/_/g, ' ');
1341
  }
1342
+
1343
  function displayResults(data, resultsId, endpoint) {
1344
  const resultsDiv = document.getElementById(resultsId);
1345
  const assessment = data.assessment;
1346
+
1347
  let html = '<div class="results-header">';
1348
  html += '<h2>Assessment Complete</h2>';
1349
+
1350
  const riskClass = 'risk-' + assessment.risk_level.replace(/_/g, '-');
1351
  html += `<div class="risk-badge ${riskClass}">${assessment.risk_level.replace(/_/g, ' ')}</div>`;
1352
  html += '</div>';
1353
+
1354
  html += '<div class="stats-grid">';
1355
+
1356
  if (assessment.confidence_interval) {
1357
  const ci = assessment.confidence_interval;
1358
  html += `
 
1372
  </div>
1373
  `;
1374
  }
1375
+
1376
  html += `
1377
  <div class="stat-card">
1378
  <div class="stat-label">Elevation</div>
1379
  <div class="stat-value">${assessment.elevation_m}m</div>
1380
  </div>
1381
  `;
1382
+
1383
  if (assessment.distance_to_water_m !== null) {
1384
  html += `
1385
  <div class="stat-card">
 
1388
  </div>
1389
  `;
1390
  }
1391
+
1392
  html += '</div>';
1393
+
1394
  if (assessment.uncertainty_analysis) {
1395
  const ua = assessment.uncertainty_analysis;
1396
  const confidenceValue = parseFloat(ua.confidence) || 0;
 
1416
  </p>
1417
  </div>
1418
  `;
1419
+
1420
  if (ua.data_quality_flags && ua.data_quality_flags.length > 0) {
1421
+ const criticalFlags = ua.data_quality_flags.filter(flag =>
1422
+ flag === 'steep_terrain_dem_error_high' ||
1423
  flag === 'coastal_surge_risk_not_modeled'
1424
  );
1425
+
1426
  if (criticalFlags.length > 0) {
1427
  html += '<div class="quality-warning"><h4>⚠ Data Quality Notes</h4><ul>';
1428
  criticalFlags.forEach(flag => {
 
1432
  }
1433
  }
1434
  }
1435
+
1436
  html += '<div class="detail-section"><h3>Terrain Analysis</h3>';
1437
  html += `
1438
  <div class="metric-row">
 
1453
  </div>
1454
  `;
1455
  html += '</div>';
1456
+
1457
  if (assessment.hazard_breakdown) {
1458
  const hb = assessment.hazard_breakdown;
1459
  html += '<div class="detail-section"><h3>Hazard Breakdown</h3>';
 
1476
  html += `<p style="margin-top: 1.5rem;"><strong>Dominant Hazard:</strong> ${assessment.dominant_hazard.replace(/_/g, ' ').toUpperCase()}</p>`;
1477
  html += '</div>';
1478
  }
1479
+
1480
  if (data.explanation) {
1481
  const exp = data.explanation;
1482
  html += '<div class="explanation-section">';
1483
  html += '<h3>Risk Factor Analysis</h3>';
1484
  html += `<p style="margin-bottom: 1.5rem; color: #cbd5e1;"><strong>Top Risk Driver:</strong> ${exp.top_risk_driver}</p>`;
1485
+
1486
  exp.explanations.forEach(factor => {
1487
  html += `
1488
  <div class="factor-item">
 
1494
  </div>
1495
  `;
1496
  });
1497
+
1498
  html += '</div>';
1499
  }
1500
+
1501
  resultsDiv.innerHTML = html;
1502
  resultsDiv.style.display = 'block';
1503
  }
1504
+
1505
+ async function getHeightFromGBA(latId, lonId, heightId, errorId, button) {
1506
+ const lat = parseFloat(document.getElementById(latId).value);
1507
+ const lon = parseFloat(document.getElementById(lonId).value);
1508
+
1509
+ const errorBox = document.getElementById(errorId);
1510
+ if (errorBox) errorBox.textContent = '';
1511
+
1512
+ if (Number.isNaN(lat) || Number.isNaN(lon)) {
1513
+ if (errorBox) errorBox.textContent = 'Please enter valid latitude and longitude.';
1514
+ return;
1515
+ }
1516
+
1517
+ const originalText = button.textContent;
1518
+ button.disabled = true;
1519
+ button.textContent = 'Fetching...';
1520
+
1521
+ try {
1522
+ const resp = await fetch('/get_height_gba', {
1523
+ method: 'POST',
1524
+ headers: { 'Content-Type': 'application/json' },
1525
+ body: JSON.stringify({ latitude: lat, longitude: lon, height: 0, basement: 0 })
1526
+ });
1527
+
1528
+ const data = await resp.json();
1529
+ if (!resp.ok) {
1530
+ const msg = data?.detail || 'Failed to get GBA height';
1531
+ throw new Error(msg);
1532
+ }
1533
+
1534
+ const h = data.predicted_height;
1535
+ if (h === null || h === undefined) {
1536
+ throw new Error('No height returned');
1537
+ }
1538
+
1539
+ document.getElementById(heightId).value = Number(h).toFixed(2);
1540
+ } catch (e) {
1541
+ if (errorBox) errorBox.textContent = String(e.message || e);
1542
+ } finally {
1543
+ button.disabled = false;
1544
+ button.textContent = originalText;
1545
+ }
1546
+ }
1547
+
1548
+ document.querySelectorAll('.gba-height-btn').forEach(btn => {
1549
+ btn.addEventListener('click', () => {
1550
+ getHeightFromGBA(
1551
+ btn.dataset.latId,
1552
+ btn.dataset.lonId,
1553
+ btn.dataset.heightId,
1554
+ btn.dataset.errorId,
1555
+ btn
1556
+ );
1557
+ });
1558
+ });
1559
+
1560
  async function uploadBatch() {
1561
  const fileInput = document.getElementById('csvFile');
1562
  const file = fileInput.files[0];
1563
+
1564
  if (!file) {
1565
  alert('Please select a CSV file');
1566
  return;
1567
  }
1568
+
1569
  document.getElementById('batch-loading').style.display = 'block';
1570
  document.getElementById('batch-results').style.display = 'none';
1571
  document.getElementById('batch-error').style.display = 'none';
1572
+
1573
  const formData = new FormData();
1574
  formData.append('file', file);
1575
+
1576
  try {
1577
  const mode = document.getElementById('batchMode').value;
1578
  const usePredicted = document.getElementById('usePredictedHeight').checked;
 
1587
  method: 'POST',
1588
  body: formData
1589
  });
1590
+
1591
  if (response.ok) {
1592
  const blob = await response.blob();
1593
  const url = window.URL.createObjectURL(blob);
1594
  const a = document.createElement('a');
1595
  a.href = url;
1596
+ const filename = mode === 'multihazard'
1597
+ ? 'multihazard_results.csv'
1598
  : 'vulnerability_results.csv';
1599
  a.download = filename;
1600
  document.body.appendChild(a);
1601
  a.click();
1602
  window.URL.revokeObjectURL(url);
1603
+
1604
  document.getElementById('batch-results').innerHTML = `
1605
  <div class="results-header">
1606
  <h2>✓ Processing Complete</h2>
 
1620
  }
1621
  </script>
1622
  </body>
1623
+
1624
+ </html>