theaniketgiri commited on
Commit
7271401
·
1 Parent(s): 7ca254c
.gitignore CHANGED
@@ -58,6 +58,7 @@ ENV/
58
  **/*.model
59
  **/*.json
60
  **/*.txt
 
61
  **/*.vocab
62
  **/*.merges
63
  **/*.model
 
58
  **/*.model
59
  **/*.json
60
  **/*.txt
61
+ !requirements.txt
62
  **/*.vocab
63
  **/*.merges
64
  **/*.model
analysis_api.py ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from fastapi.responses import FileResponse, HTMLResponse
4
+ from pydantic import BaseModel
5
+ from typing import List, Dict, Any, Optional
6
+ from datetime import datetime
7
+ import uvicorn
8
+ import os
9
+ from pathlib import Path
10
+ import json
11
+ import logging
12
+ from contextlib import asynccontextmanager
13
+
14
+ # Import the analyzer
15
+ from analyze_data_quality import DataQualityAnalyzer
16
+
17
+ # Setup logging
18
+ logging.basicConfig(level=logging.INFO)
19
+ logger = logging.getLogger(__name__)
20
+
21
+ # Global state
22
+ analyzer = None
23
+ last_analysis_time = None
24
+ analysis_results = None
25
+
26
+ @asynccontextmanager
27
+ async def lifespan(app: FastAPI):
28
+ # Startup
29
+ global analyzer
30
+ try:
31
+ analyzer = DataQualityAnalyzer()
32
+ logger.info("DataQualityAnalyzer initialized successfully")
33
+ except Exception as e:
34
+ logger.error(f"Error initializing analyzer: {str(e)}")
35
+ yield
36
+ # Shutdown
37
+ logger.info("Shutting down analysis API")
38
+
39
+ app = FastAPI(
40
+ title="Synthex Medical Text Analysis API",
41
+ description="""
42
+ API for analyzing medical text data quality. This API provides endpoints for:
43
+ - Running data quality analysis
44
+ - Checking analysis status
45
+ - Accessing generated plots
46
+ - Listing available datasets
47
+ """,
48
+ version="1.0.0",
49
+ lifespan=lifespan
50
+ )
51
+
52
+ # Add CORS middleware
53
+ app.add_middleware(
54
+ CORSMiddleware,
55
+ allow_origins=["*"],
56
+ allow_credentials=True,
57
+ allow_methods=["*"],
58
+ allow_headers=["*"],
59
+ )
60
+
61
+ class AnalysisResponse(BaseModel):
62
+ summary: Dict[str, Any]
63
+ datasets: Dict[str, Dict[str, Any]]
64
+ plots_available: List[str]
65
+ timestamp: str
66
+
67
+ class StatusResponse(BaseModel):
68
+ last_analysis: Optional[str]
69
+ summary: Optional[Dict[str, Any]]
70
+ is_analyzed: bool
71
+
72
+ @app.get("/", response_class=HTMLResponse)
73
+ async def root():
74
+ """API documentation and status page"""
75
+ return """
76
+ <html>
77
+ <head>
78
+ <title>Synthex Medical Text Analysis API</title>
79
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
80
+ <style>
81
+ body { padding: 20px; }
82
+ .container { max-width: 800px; }
83
+ </style>
84
+ </head>
85
+ <body>
86
+ <div class="container">
87
+ <h1>Synthex Medical Text Analysis API</h1>
88
+ <p class="lead">API for analyzing medical text data quality</p>
89
+
90
+ <h2>Available Endpoints</h2>
91
+ <ul>
92
+ <li><strong>POST /analyze</strong> - Run full data analysis</li>
93
+ <li><strong>GET /analysis/status</strong> - Check analysis status</li>
94
+ <li><strong>GET /plots/{plot_name}</strong> - Get specific plot</li>
95
+ <li><strong>GET /datasets</strong> - List available datasets</li>
96
+ </ul>
97
+
98
+ <h2>API Documentation</h2>
99
+ <p>For detailed API documentation, visit:</p>
100
+ <ul>
101
+ <li><a href="/docs">Swagger UI</a></li>
102
+ <li><a href="/redoc">ReDoc</a></li>
103
+ </ul>
104
+
105
+ <h2>Status</h2>
106
+ <p>API is running and ready to process requests.</p>
107
+ </div>
108
+ </body>
109
+ </html>
110
+ """
111
+
112
+ @app.post("/analyze", response_model=AnalysisResponse)
113
+ async def analyze_data():
114
+ """Run full data analysis and generate reports"""
115
+ global analyzer, last_analysis_time, analysis_results
116
+
117
+ if analyzer is None:
118
+ try:
119
+ analyzer = DataQualityAnalyzer()
120
+ except Exception as e:
121
+ raise HTTPException(status_code=500, detail=f"Failed to initialize analyzer: {str(e)}")
122
+
123
+ try:
124
+ # Run analysis
125
+ analyzer.analyze_all_datasets()
126
+ report = analyzer.generate_report()
127
+ analyzer.plot_metrics()
128
+
129
+ # Get list of generated plots
130
+ plots_dir = analyzer.data_dir.parent / "reports" / "plots"
131
+ plots_available = [f.name for f in plots_dir.glob("*.png")]
132
+
133
+ # Update global state
134
+ last_analysis_time = datetime.now().isoformat()
135
+ analysis_results = report
136
+
137
+ return AnalysisResponse(
138
+ summary=report["summary"],
139
+ datasets=report["datasets"],
140
+ plots_available=plots_available,
141
+ timestamp=last_analysis_time
142
+ )
143
+ except Exception as e:
144
+ raise HTTPException(status_code=500, detail=f"Analysis failed: {str(e)}")
145
+
146
+ @app.get("/analysis/status", response_model=StatusResponse)
147
+ async def get_analysis_status():
148
+ """Get the status of the last analysis run"""
149
+ if last_analysis_time is None:
150
+ return StatusResponse(
151
+ last_analysis=None,
152
+ summary=None,
153
+ is_analyzed=False
154
+ )
155
+
156
+ return StatusResponse(
157
+ last_analysis=last_analysis_time,
158
+ summary=analysis_results["summary"] if analysis_results else None,
159
+ is_analyzed=True
160
+ )
161
+
162
+ @app.get("/plots/{plot_name}")
163
+ async def get_plot(plot_name: str):
164
+ """Get a specific plot by name"""
165
+ plots_dir = Path("data/reports/plots")
166
+ plot_path = plots_dir / plot_name
167
+
168
+ if not plot_path.exists():
169
+ raise HTTPException(status_code=404, detail=f"Plot {plot_name} not found")
170
+
171
+ return FileResponse(plot_path)
172
+
173
+ @app.get("/datasets")
174
+ async def get_datasets():
175
+ """List all available datasets for analysis"""
176
+ if analyzer is None:
177
+ try:
178
+ analyzer = DataQualityAnalyzer()
179
+ except Exception as e:
180
+ raise HTTPException(status_code=500, detail=f"Failed to initialize analyzer: {str(e)}")
181
+
182
+ try:
183
+ datasets = []
184
+ for file_path in analyzer.data_dir.glob("*.json"):
185
+ datasets.append({
186
+ "name": file_path.stem,
187
+ "path": str(file_path),
188
+ "size": file_path.stat().st_size
189
+ })
190
+ return {"datasets": datasets}
191
+ except Exception as e:
192
+ raise HTTPException(status_code=500, detail=f"Failed to list datasets: {str(e)}")
193
+
194
+ if __name__ == "__main__":
195
+ uvicorn.run("analysis_api:app", host="0.0.0.0", port=8001, reload=True)
api.py CHANGED
@@ -1,20 +1,23 @@
1
  from fastapi import FastAPI, HTTPException
2
  from fastapi.middleware.cors import CORSMiddleware
3
  from pydantic import BaseModel
4
- from typing import List, Optional
5
  import uvicorn
6
  import sys
7
  import os
 
 
8
 
9
  # Add src directory to Python path
10
  sys.path.append(os.path.join(os.path.dirname(__file__), 'src'))
11
 
12
- # Import the medical generator
13
  from src.generation.medical_generator import MedicalTextGenerator, DEFAULT_GEMINI_API_KEY
 
14
 
15
  app = FastAPI(
16
  title="Synthex Medical Text Generator API",
17
- description="API for generating synthetic medical records",
18
  version="1.0.0"
19
  )
20
 
@@ -27,8 +30,9 @@ app.add_middleware(
27
  allow_headers=["*"], # Allows all headers
28
  )
29
 
30
- # Initialize the generator
31
  generator = None
 
32
 
33
  class GenerationRequest(BaseModel):
34
  record_type: str
@@ -41,13 +45,19 @@ class GenerationResponse(BaseModel):
41
  records: List[dict]
42
  total_generated: int
43
 
 
 
 
 
 
44
  @app.on_event("startup")
45
  async def startup_event():
46
- global generator
47
  try:
48
  generator = MedicalTextGenerator()
 
49
  except Exception as e:
50
- print(f"Error initializing generator: {str(e)}")
51
 
52
  @app.get("/")
53
  async def root():
@@ -91,5 +101,43 @@ async def get_record_types():
91
  ]
92
  }
93
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  if __name__ == "__main__":
95
  uvicorn.run("api:app", host="0.0.0.0", port=8000, reload=True)
 
1
  from fastapi import FastAPI, HTTPException
2
  from fastapi.middleware.cors import CORSMiddleware
3
  from pydantic import BaseModel
4
+ from typing import List, Optional, Dict, Any
5
  import uvicorn
6
  import sys
7
  import os
8
+ from pathlib import Path
9
+ from fastapi.responses import FileResponse
10
 
11
  # Add src directory to Python path
12
  sys.path.append(os.path.join(os.path.dirname(__file__), 'src'))
13
 
14
+ # Import the medical generator and data analyzer
15
  from src.generation.medical_generator import MedicalTextGenerator, DEFAULT_GEMINI_API_KEY
16
+ from analyze_data_quality import DataQualityAnalyzer
17
 
18
  app = FastAPI(
19
  title="Synthex Medical Text Generator API",
20
+ description="API for generating synthetic medical records and analyzing data quality",
21
  version="1.0.0"
22
  )
23
 
 
30
  allow_headers=["*"], # Allows all headers
31
  )
32
 
33
+ # Initialize the generator and analyzer
34
  generator = None
35
+ analyzer = None
36
 
37
  class GenerationRequest(BaseModel):
38
  record_type: str
 
45
  records: List[dict]
46
  total_generated: int
47
 
48
+ class AnalysisResponse(BaseModel):
49
+ summary: Dict[str, Any]
50
+ datasets: Dict[str, Dict[str, Any]]
51
+ plots_available: List[str]
52
+
53
  @app.on_event("startup")
54
  async def startup_event():
55
+ global generator, analyzer
56
  try:
57
  generator = MedicalTextGenerator()
58
+ analyzer = DataQualityAnalyzer()
59
  except Exception as e:
60
+ print(f"Error initializing services: {str(e)}")
61
 
62
  @app.get("/")
63
  async def root():
 
101
  ]
102
  }
103
 
104
+ @app.post("/analyze", response_model=AnalysisResponse)
105
+ async def analyze_data():
106
+ global analyzer
107
+
108
+ if analyzer is None:
109
+ try:
110
+ analyzer = DataQualityAnalyzer()
111
+ except Exception as e:
112
+ raise HTTPException(status_code=500, detail=f"Failed to initialize analyzer: {str(e)}")
113
+
114
+ try:
115
+ # Run analysis
116
+ analyzer.analyze_all_datasets()
117
+ report = analyzer.generate_report()
118
+ analyzer.plot_metrics()
119
+
120
+ # Get list of generated plots
121
+ plots_dir = analyzer.data_dir.parent / "reports" / "plots"
122
+ plots_available = [f.name for f in plots_dir.glob("*.png")]
123
+
124
+ return AnalysisResponse(
125
+ summary=report["summary"],
126
+ datasets=report["datasets"],
127
+ plots_available=plots_available
128
+ )
129
+ except Exception as e:
130
+ raise HTTPException(status_code=500, detail=f"Analysis failed: {str(e)}")
131
+
132
+ @app.get("/analysis/plots/{plot_name}")
133
+ async def get_plot(plot_name: str):
134
+ plots_dir = Path("data/reports/plots")
135
+ plot_path = plots_dir / plot_name
136
+
137
+ if not plot_path.exists():
138
+ raise HTTPException(status_code=404, detail=f"Plot {plot_name} not found")
139
+
140
+ return FileResponse(plot_path)
141
+
142
  if __name__ == "__main__":
143
  uvicorn.run("api:app", host="0.0.0.0", port=8000, reload=True)
data/reports/plots/sample_distribution.png CHANGED
src/web/index.html CHANGED
@@ -11,7 +11,7 @@
11
  background-color: #f8f9fa;
12
  }
13
  .container {
14
- max-width: 800px;
15
  background-color: white;
16
  padding: 30px;
17
  border-radius: 10px;
@@ -29,58 +29,126 @@
29
  text-align: center;
30
  margin: 20px 0;
31
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  </style>
33
  </head>
34
  <body>
35
  <div class="container">
36
  <h1 class="mb-4">Synthex Medical Text Generator</h1>
37
 
38
- <div class="mb-3">
39
- <label for="recordType" class="form-label">Record Type</label>
40
- <select class="form-select" id="recordType">
41
- <option value="clinical_note">Clinical Note</option>
42
- <option value="discharge_summary">Discharge Summary</option>
43
- <option value="lab_report">Lab Report</option>
44
- <option value="prescription">Prescription</option>
45
- <option value="patient_intake">Patient Intake</option>
46
- </select>
47
- </div>
48
 
49
- <div class="mb-3">
50
- <label for="quantity" class="form-label">Quantity</label>
51
- <input type="number" class="form-control" id="quantity" value="1" min="1" max="10">
52
- </div>
 
 
 
 
 
 
 
 
 
 
53
 
54
- <div class="mb-3 form-check">
55
- <input type="checkbox" class="form-check-input" id="useGemini">
56
- <label class="form-check-label" for="useGemini">Use Gemini (if available)</label>
57
- </div>
58
 
59
- <div class="mb-3 form-check">
60
- <input type="checkbox" class="form-check-input" id="includeMetadata" checked>
61
- <label class="form-check-label" for="includeMetadata">Include Metadata</label>
62
- </div>
63
 
64
- <button class="btn btn-primary" onclick="generateRecords()">Generate Records</button>
 
 
 
65
 
66
- <div class="loading" id="loading">
67
- <div class="spinner-border text-primary" role="status">
68
- <span class="visually-hidden">Loading...</span>
 
 
 
 
 
 
 
69
  </div>
70
- <p class="mt-2">Generating records...</p>
71
- </div>
72
 
73
- <div id="result" class="result-box"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  </div>
75
 
 
76
  <script>
 
77
  async function generateRecords() {
78
  const recordType = document.getElementById('recordType').value;
79
  const quantity = parseInt(document.getElementById('quantity').value);
80
  const useGemini = document.getElementById('useGemini').checked;
81
  const includeMetadata = document.getElementById('includeMetadata').checked;
82
 
83
- // Show loading
84
  document.getElementById('loading').style.display = 'block';
85
  document.getElementById('result').innerHTML = '';
86
 
@@ -101,7 +169,6 @@
101
 
102
  const data = await response.json();
103
 
104
- // Format and display results
105
  let resultHtml = '<h3>Generated Records:</h3>';
106
  data.records.forEach(record => {
107
  resultHtml += `
@@ -122,6 +189,100 @@
122
  document.getElementById('loading').style.display = 'none';
123
  }
124
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  </script>
126
  </body>
127
  </html>
 
11
  background-color: #f8f9fa;
12
  }
13
  .container {
14
+ max-width: 1200px;
15
  background-color: white;
16
  padding: 30px;
17
  border-radius: 10px;
 
29
  text-align: center;
30
  margin: 20px 0;
31
  }
32
+ .nav-tabs {
33
+ margin-bottom: 20px;
34
+ }
35
+ .plot-container {
36
+ margin-top: 20px;
37
+ text-align: center;
38
+ }
39
+ .plot-container img {
40
+ max-width: 100%;
41
+ height: auto;
42
+ border-radius: 5px;
43
+ box-shadow: 0 0 5px rgba(0,0,0,0.1);
44
+ }
45
+ .stats-card {
46
+ margin-bottom: 15px;
47
+ }
48
  </style>
49
  </head>
50
  <body>
51
  <div class="container">
52
  <h1 class="mb-4">Synthex Medical Text Generator</h1>
53
 
54
+ <!-- Navigation Tabs -->
55
+ <ul class="nav nav-tabs" id="myTab" role="tablist">
56
+ <li class="nav-item" role="presentation">
57
+ <button class="nav-link active" id="generation-tab" data-bs-toggle="tab" data-bs-target="#generation" type="button" role="tab">Generation</button>
58
+ </li>
59
+ <li class="nav-item" role="presentation">
60
+ <button class="nav-link" id="analysis-tab" data-bs-toggle="tab" data-bs-target="#analysis" type="button" role="tab">Analysis</button>
61
+ </li>
62
+ </ul>
 
63
 
64
+ <!-- Tab Content -->
65
+ <div class="tab-content" id="myTabContent">
66
+ <!-- Generation Tab -->
67
+ <div class="tab-pane fade show active" id="generation" role="tabpanel">
68
+ <div class="mb-3">
69
+ <label for="recordType" class="form-label">Record Type</label>
70
+ <select class="form-select" id="recordType">
71
+ <option value="clinical_note">Clinical Note</option>
72
+ <option value="discharge_summary">Discharge Summary</option>
73
+ <option value="lab_report">Lab Report</option>
74
+ <option value="prescription">Prescription</option>
75
+ <option value="patient_intake">Patient Intake</option>
76
+ </select>
77
+ </div>
78
 
79
+ <div class="mb-3">
80
+ <label for="quantity" class="form-label">Quantity</label>
81
+ <input type="number" class="form-control" id="quantity" value="1" min="1" max="10">
82
+ </div>
83
 
84
+ <div class="mb-3 form-check">
85
+ <input type="checkbox" class="form-check-input" id="useGemini">
86
+ <label class="form-check-label" for="useGemini">Use Gemini (if available)</label>
87
+ </div>
88
 
89
+ <div class="mb-3 form-check">
90
+ <input type="checkbox" class="form-check-input" id="includeMetadata" checked>
91
+ <label class="form-check-label" for="includeMetadata">Include Metadata</label>
92
+ </div>
93
 
94
+ <button class="btn btn-primary" onclick="generateRecords()">Generate Records</button>
95
+
96
+ <div class="loading" id="loading">
97
+ <div class="spinner-border text-primary" role="status">
98
+ <span class="visually-hidden">Loading...</span>
99
+ </div>
100
+ <p class="mt-2">Generating records...</p>
101
+ </div>
102
+
103
+ <div id="result" class="result-box"></div>
104
  </div>
 
 
105
 
106
+ <!-- Analysis Tab -->
107
+ <div class="tab-pane fade" id="analysis" role="tabpanel">
108
+ <div class="row mb-4">
109
+ <div class="col">
110
+ <button class="btn btn-primary" onclick="runAnalysis()">Run Analysis</button>
111
+ <button class="btn btn-secondary" onclick="checkStatus()">Check Status</button>
112
+ </div>
113
+ </div>
114
+
115
+ <div class="loading" id="analysisLoading">
116
+ <div class="spinner-border text-primary" role="status">
117
+ <span class="visually-hidden">Loading...</span>
118
+ </div>
119
+ <p class="mt-2">Running analysis...</p>
120
+ </div>
121
+
122
+ <!-- Analysis Results -->
123
+ <div id="analysisResult">
124
+ <!-- Summary Stats -->
125
+ <div class="row mb-4" id="summaryStats"></div>
126
+
127
+ <!-- Dataset List -->
128
+ <div class="mb-4">
129
+ <h3>Available Datasets</h3>
130
+ <div id="datasetList" class="list-group"></div>
131
+ </div>
132
+
133
+ <!-- Plots -->
134
+ <div class="mb-4">
135
+ <h3>Analysis Plots</h3>
136
+ <div id="plotGallery" class="row"></div>
137
+ </div>
138
+ </div>
139
+ </div>
140
+ </div>
141
  </div>
142
 
143
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
144
  <script>
145
+ // Generation Functions
146
  async function generateRecords() {
147
  const recordType = document.getElementById('recordType').value;
148
  const quantity = parseInt(document.getElementById('quantity').value);
149
  const useGemini = document.getElementById('useGemini').checked;
150
  const includeMetadata = document.getElementById('includeMetadata').checked;
151
 
 
152
  document.getElementById('loading').style.display = 'block';
153
  document.getElementById('result').innerHTML = '';
154
 
 
169
 
170
  const data = await response.json();
171
 
 
172
  let resultHtml = '<h3>Generated Records:</h3>';
173
  data.records.forEach(record => {
174
  resultHtml += `
 
189
  document.getElementById('loading').style.display = 'none';
190
  }
191
  }
192
+
193
+ // Analysis Functions
194
+ async function runAnalysis() {
195
+ document.getElementById('analysisLoading').style.display = 'block';
196
+ document.getElementById('analysisResult').style.display = 'none';
197
+
198
+ try {
199
+ const response = await fetch('http://localhost:8001/analyze', {
200
+ method: 'POST'
201
+ });
202
+ const data = await response.json();
203
+ displayAnalysisResults(data);
204
+ } catch (error) {
205
+ alert(`Error running analysis: ${error.message}`);
206
+ } finally {
207
+ document.getElementById('analysisLoading').style.display = 'none';
208
+ document.getElementById('analysisResult').style.display = 'block';
209
+ }
210
+ }
211
+
212
+ async function checkStatus() {
213
+ try {
214
+ const response = await fetch('http://localhost:8001/analysis/status');
215
+ const data = await response.json();
216
+ if (data.is_analyzed) {
217
+ displayAnalysisResults(data);
218
+ } else {
219
+ alert('No analysis has been run yet.');
220
+ }
221
+ } catch (error) {
222
+ alert(`Error checking status: ${error.message}`);
223
+ }
224
+ }
225
+
226
+ function displayAnalysisResults(data) {
227
+ // Display summary stats
228
+ const summaryHtml = `
229
+ <div class="col-md-4">
230
+ <div class="card stats-card">
231
+ <div class="card-body">
232
+ <h5 class="card-title">Total Samples</h5>
233
+ <p class="card-text">${data.summary.total_samples}</p>
234
+ </div>
235
+ </div>
236
+ </div>
237
+ <div class="col-md-4">
238
+ <div class="card stats-card">
239
+ <div class="card-body">
240
+ <h5 class="card-title">Total Datasets</h5>
241
+ <p class="card-text">${data.summary.total_datasets}</p>
242
+ </div>
243
+ </div>
244
+ </div>
245
+ <div class="col-md-4">
246
+ <div class="card stats-card">
247
+ <div class="card-body">
248
+ <h5 class="card-title">Last Updated</h5>
249
+ <p class="card-text">${new Date(data.timestamp).toLocaleString()}</p>
250
+ </div>
251
+ </div>
252
+ </div>
253
+ `;
254
+ document.getElementById('summaryStats').innerHTML = summaryHtml;
255
+
256
+ // Display datasets
257
+ const datasetList = document.getElementById('datasetList');
258
+ datasetList.innerHTML = '';
259
+ Object.entries(data.datasets).forEach(([name, stats]) => {
260
+ datasetList.innerHTML += `
261
+ <div class="list-group-item">
262
+ <h5>${name}</h5>
263
+ <p>Samples: ${stats.total_samples}</p>
264
+ ${stats.abstract ? `<p>Avg Abstract Length: ${stats.abstract.avg_length.toFixed(1)}</p>` : ''}
265
+ </div>
266
+ `;
267
+ });
268
+
269
+ // Display plots
270
+ const plotGallery = document.getElementById('plotGallery');
271
+ plotGallery.innerHTML = '';
272
+ data.plots_available.forEach(plotName => {
273
+ plotGallery.innerHTML += `
274
+ <div class="col-md-6 mb-4">
275
+ <div class="plot-container">
276
+ <img src="http://localhost:8001/plots/${plotName}" alt="${plotName}" class="img-fluid">
277
+ <p class="mt-2">${plotName.replace(/_/g, ' ').replace('.png', '')}</p>
278
+ </div>
279
+ </div>
280
+ `;
281
+ });
282
+ }
283
+
284
+ // Initial status check
285
+ checkStatus();
286
  </script>
287
  </body>
288
  </html>