alexdatamed commited on
Commit
2bd6c65
·
verified ·
1 Parent(s): 47ce2fe

Upload app.js

Browse files
Files changed (1) hide show
  1. app.js +452 -410
app.js CHANGED
@@ -1,471 +1,513 @@
1
- // Application data
2
- const appData = {
3
- "lifecycleStages": [
 
 
4
  {
5
  "id": 1,
6
- "name": "Problem Formulation",
7
- "icon": "💡",
8
- "description": "Transform real-world problems into data-driven questions",
9
- "example": "Healthcare: Predicting patient recovery time based on medical history",
10
- "keyPoints": ["Define business objectives", "Identify success metrics", "Translate to ML problem", "Set constraints and requirements"],
11
- "status": "completed"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  },
13
  {
14
- "id": 2,
15
- "name": "Data Collection",
16
- "icon": "📊",
17
- "description": "Gather relevant data from reliable sources",
18
- "sources": ["Clinical databases", "IoT sensors", "Public datasets", "APIs"],
19
- "compliance": {"HIPAA": true, "GDPR": true, "SOX": false},
20
- "status": "completed"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  },
22
  {
23
  "id": 3,
24
- "name": "Data Preprocessing",
25
- "icon": "🔧",
26
- "description": "Clean and standardize data for analysis",
27
- "metrics": {"missingValues": 12, "normalization": "StandardScaler", "categoricalEncoding": "OneHot", "outliers": 8},
28
- "status": "in-progress"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  },
30
  {
31
  "id": 4,
32
- "name": "Modeling",
33
- "icon": "🤖",
34
- "description": "Select and apply appropriate algorithms",
35
- "modelTypes": ["Classification", "Regression", "Clustering"],
36
- "algorithms": ["Random Forest", "Logistic Regression", "SVM", "Neural Networks"],
37
- "status": "pending"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  },
39
  {
40
  "id": 5,
41
- "name": "Evaluation",
42
- "icon": "📈",
43
- "description": "Assess model performance and reliability",
44
- "metrics": {"accuracy": 0.94, "precision": 0.91, "recall": 0.89, "f1Score": 0.90},
45
- "status": "pending"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  },
47
  {
48
  "id": 6,
49
- "name": "Deployment",
50
- "icon": "🚀",
51
- "description": "Deploy model to production environment",
52
- "pipeline": {"docker": true, "kubernetes": true, "cicd": "Jenkins", "api": "REST"},
53
- "status": "pending"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  },
55
  {
56
  "id": 7,
57
- "name": "Monitoring & Maintenance",
58
- "icon": "🔍",
59
- "description": "Monitor performance and maintain model accuracy",
60
- "alerts": {"dataDrift": false, "accuracyDecay": 2.1, "retrainingDue": "2025-07-15"},
61
- "status": "pending"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  }
63
- ],
64
- "industries": [
65
- {"name": "Healthcare", "examples": ["Patient recovery prediction", "Disease diagnosis", "Drug discovery"]},
66
- {"name": "Retail", "examples": ["Customer segmentation", "Demand forecasting", "Price optimization"]},
67
- {"name": "Finance", "examples": ["Fraud detection", "Credit scoring", "Algorithmic trading"]},
68
- {"name": "Manufacturing", "examples": ["Predictive maintenance", "Quality control", "Supply chain optimization"]},
69
- {"name": "Education", "examples": ["Student performance prediction", "Personalized learning", "Dropout prevention"]}
70
- ],
71
- "modelTypes": [
72
- {"name": "Logistic Regression", "type": "Classification", "accuracy": 0.85},
73
- {"name": "Random Forest", "type": "Classification", "accuracy": 0.92},
74
- {"name": "SVM", "type": "Classification", "accuracy": 0.88},
75
- {"name": "Neural Networks", "type": "Classification", "accuracy": 0.94},
76
- {"name": "Linear Regression", "type": "Regression", "rmse": 0.23},
77
- {"name": "K-Means", "type": "Clustering", "silhouette": 0.67}
78
- ],
79
- "complianceRequirements": {
80
- "Healthcare": ["HIPAA", "FDA", "HITECH"],
81
- "Finance": ["SOX", "GDPR", "PCI-DSS"],
82
- "Retail": ["GDPR", "CCPA"],
83
- "Manufacturing": ["ISO 27001", "GDPR"],
84
- "Education": ["FERPA", "COPPA"]
85
- }
86
  };
87
 
88
- // Global state
89
- let currentStage = null;
90
- let currentFilters = {
91
- industry: '',
92
- model: '',
93
- version: ''
 
 
 
 
 
 
 
 
 
94
  };
95
- let evaluationChart = null;
96
 
97
- // Initialize the application
98
  document.addEventListener('DOMContentLoaded', function() {
99
- initializeTimeline();
100
- initializeFilters();
101
- updateProgressSummary();
102
  });
103
 
104
- // Initialize timeline with stages
105
- function initializeTimeline() {
106
- const timelineStages = document.getElementById('timelineStages');
107
 
108
- appData.lifecycleStages.forEach((stage, index) => {
109
- const stageElement = createTimelineStage(stage, index);
110
- timelineStages.appendChild(stageElement);
111
- });
 
 
 
 
 
 
 
112
  }
113
 
114
- // Create timeline stage element
115
- function createTimelineStage(stage, index) {
116
- const stageDiv = document.createElement('div');
117
- stageDiv.className = `timeline-stage timeline-stage--${index % 2 === 0 ? 'left' : 'right'} timeline-stage--${stage.status}`;
118
- stageDiv.setAttribute('data-stage-id', stage.id);
119
-
120
- stageDiv.innerHTML = `
121
- <div class="timeline-stage__card">
122
- <h3 class="timeline-stage__title">${stage.name}</h3>
123
- <p class="timeline-stage__description">${stage.description}</p>
124
- <span class="timeline-stage__status status--${stage.status}">${stage.status.replace('-', ' ')}</span>
125
- </div>
126
- <div class="timeline-stage__node">${stage.icon}</div>
127
- `;
128
-
129
- stageDiv.addEventListener('click', () => selectStage(stage.id));
130
-
131
- return stageDiv;
132
  }
133
 
134
- // Initialize filters
135
- function initializeFilters() {
136
- const industryFilter = document.getElementById('industryFilter');
137
- const modelFilter = document.getElementById('modelFilter');
138
- const versionFilter = document.getElementById('versionFilter');
139
-
140
- industryFilter.addEventListener('change', (e) => {
141
- currentFilters.industry = e.target.value;
142
- applyFilters();
143
- });
144
-
145
- modelFilter.addEventListener('change', (e) => {
146
- currentFilters.model = e.target.value;
147
- applyFilters();
148
- });
149
 
150
- versionFilter.addEventListener('change', (e) => {
151
- currentFilters.version = e.target.value;
152
- applyFilters();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  });
154
  }
155
 
156
- // Apply filters (placeholder for future filtering logic)
157
- function applyFilters() {
158
- // This could filter content based on selected industry, model, etc.
159
- console.log('Filters applied:', currentFilters);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  }
161
 
162
- // Select a stage and show its details
163
- function selectStage(stageId) {
164
- // Remove active class from all stages
165
- document.querySelectorAll('.timeline-stage').forEach(stage => {
166
- stage.classList.remove('timeline-stage--active');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  });
168
 
169
- // Add active class to selected stage
170
- const selectedStage = document.querySelector(`[data-stage-id="${stageId}"]`);
171
- if (selectedStage) {
172
- selectedStage.classList.add('timeline-stage--active');
173
- }
174
 
175
- // Find stage data
176
- const stage = appData.lifecycleStages.find(s => s.id === stageId);
177
- if (stage) {
178
- currentStage = stage;
179
- showStageDetails(stage);
180
- }
181
- }
182
-
183
- // Show detailed information for a stage
184
- function showStageDetails(stage) {
185
- const stageDetail = document.getElementById('stageDetail');
186
-
187
- let content = `
188
- <div class="stage-content">
189
- <div class="stage-header">
190
- <div class="stage-icon">${stage.icon}</div>
191
- <div>
192
- <h2 class="stage-title">${stage.name}</h2>
193
- <p class="stage-description">${stage.description}</p>
194
- </div>
195
- </div>
196
- <div class="stage-sections">
197
- `;
198
-
199
- // Generate content based on stage type
200
- switch(stage.id) {
201
- case 1: // Problem Formulation
202
- content += `
203
- <div class="stage-section">
204
- <h4>Real-world Example</h4>
205
- <p>${stage.example}</p>
206
- </div>
207
- <div class="stage-section">
208
- <h4>Key Activities</h4>
209
- <ul class="key-points">
210
- ${stage.keyPoints.map(point => `<li>${point}</li>`).join('')}
211
- </ul>
212
- </div>
213
- `;
214
- break;
215
-
216
- case 2: // Data Collection
217
- content += `
218
- <div class="stage-section">
219
- <h4>Data Sources</h4>
220
- <ul class="key-points">
221
- ${stage.sources.map(source => `<li>${source}</li>`).join('')}
222
- </ul>
223
- </div>
224
- <div class="stage-section">
225
- <h4>Compliance Status</h4>
226
- <div class="compliance-badges">
227
- ${Object.entries(stage.compliance).map(([key, value]) =>
228
- `<span class="compliance-badge compliance-badge--${value ? 'active' : 'inactive'}">${key}</span>`
229
- ).join('')}
230
- </div>
231
- </div>
232
- `;
233
- break;
234
-
235
- case 3: // Data Preprocessing
236
- content += `
237
- <div class="stage-section">
238
- <h4>Data Quality Metrics</h4>
239
- <div class="metrics-grid">
240
- <div class="metric-item">
241
- <span class="metric-label">Missing Values</span>
242
- <span class="metric-value">${stage.metrics.missingValues}%</span>
243
- </div>
244
- <div class="metric-item">
245
- <span class="metric-label">Normalization</span>
246
- <span class="metric-value">${stage.metrics.normalization}</span>
247
- </div>
248
- <div class="metric-item">
249
- <span class="metric-label">Categorical Encoding</span>
250
- <span class="metric-value">${stage.metrics.categoricalEncoding}</span>
251
- </div>
252
- <div class="metric-item">
253
- <span class="metric-label">Outliers Detected</span>
254
- <span class="metric-value">${stage.metrics.outliers}</span>
255
- </div>
256
- </div>
257
- </div>
258
- `;
259
- break;
260
-
261
- case 4: // Modeling
262
- content += `
263
- <div class="stage-section">
264
- <h4>Select Model Type</h4>
265
- <div class="model-selector">
266
- ${stage.modelTypes.map(type =>
267
- `<div class="model-option" data-model-type="${type}">${type}</div>`
268
- ).join('')}
269
- </div>
270
- <div id="modelOutput" class="mt-8"></div>
271
- </div>
272
- `;
273
- break;
274
-
275
- case 5: // Evaluation
276
- content += `
277
- <div class="stage-section">
278
- <h4>Performance Metrics</h4>
279
- <div class="metrics-grid">
280
- <div class="metric-item">
281
- <span class="metric-label">Accuracy</span>
282
- <span class="metric-value">${(stage.metrics.accuracy * 100).toFixed(1)}%</span>
283
- </div>
284
- <div class="metric-item">
285
- <span class="metric-label">Precision</span>
286
- <span class="metric-value">${(stage.metrics.precision * 100).toFixed(1)}%</span>
287
- </div>
288
- <div class="metric-item">
289
- <span class="metric-label">Recall</span>
290
- <span class="metric-value">${(stage.metrics.recall * 100).toFixed(1)}%</span>
291
- </div>
292
- <div class="metric-item">
293
- <span class="metric-label">F1-Score</span>
294
- <span class="metric-value">${(stage.metrics.f1Score * 100).toFixed(1)}%</span>
295
- </div>
296
- </div>
297
- <div class="chart-container">
298
- <canvas id="evaluationChart"></canvas>
299
- </div>
300
- </div>
301
- `;
302
- break;
303
-
304
- case 6: // Deployment
305
- content += `
306
- <div class="stage-section">
307
- <h4>Infrastructure Status</h4>
308
- <div class="metrics-grid">
309
- <div class="metric-item">
310
- <span class="metric-label">Docker</span>
311
- <span class="metric-value">${stage.pipeline.docker ? '✓ Ready' : '✗ Not Ready'}</span>
312
- </div>
313
- <div class="metric-item">
314
- <span class="metric-label">Kubernetes</span>
315
- <span class="metric-value">${stage.pipeline.kubernetes ? '✓ Ready' : '✗ Not Ready'}</span>
316
- </div>
317
- <div class="metric-item">
318
- <span class="metric-label">CI/CD</span>
319
- <span class="metric-value">${stage.pipeline.cicd}</span>
320
- </div>
321
- <div class="metric-item">
322
- <span class="metric-label">API Type</span>
323
- <span class="metric-value">${stage.pipeline.api}</span>
324
- </div>
325
- </div>
326
- </div>
327
- `;
328
- break;
329
-
330
- case 7: // Monitoring & Maintenance
331
- content += `
332
- <div class="stage-section">
333
- <h4>System Health</h4>
334
- <div class="alert alert--${stage.alerts.dataDrift ? 'warning' : 'success'}">
335
- <strong>Data Drift:</strong> ${stage.alerts.dataDrift ? 'Detected - Review required' : 'No drift detected'}
336
- </div>
337
- <div class="alert alert--warning">
338
- <strong>Accuracy Decay:</strong> ${stage.alerts.accuracyDecay}% decline detected
339
- </div>
340
- <div class="alert alert--info">
341
- <strong>Next Retraining:</strong> Scheduled for ${stage.alerts.retrainingDue}
342
- </div>
343
- </div>
344
- `;
345
- break;
346
- }
347
 
348
- content += `
349
- </div>
350
- </div>
351
- `;
352
 
353
- stageDetail.innerHTML = content;
 
 
354
 
355
- // Add event listeners for interactive elements
356
- if (stage.id === 4) {
357
- setupModelSelector();
358
- } else if (stage.id === 5) {
359
- setTimeout(() => createEvaluationChart(stage.metrics), 100);
360
- }
 
 
361
  }
362
 
363
- // Setup model selector for modeling stage
364
- function setupModelSelector() {
365
- document.querySelectorAll('.model-option').forEach(option => {
366
- option.addEventListener('click', function() {
367
- // Remove selected class from all options
368
- document.querySelectorAll('.model-option').forEach(opt => {
369
- opt.classList.remove('model-option--selected');
370
- });
371
-
372
- // Add selected class to clicked option
373
- this.classList.add('model-option--selected');
374
-
375
- // Show sample output
376
- const modelType = this.getAttribute('data-model-type');
377
- showModelOutput(modelType);
378
- });
 
 
 
 
 
 
 
379
  });
380
  }
381
 
382
- // Show model output based on selected type
383
- function showModelOutput(modelType) {
384
- const outputDiv = document.getElementById('modelOutput');
385
- const relevantModels = appData.modelTypes.filter(model => model.type === modelType);
386
-
387
- let output = `
388
- <h5>Available ${modelType} Models:</h5>
389
- <div class="model-selector">
390
- `;
391
-
392
- relevantModels.forEach(model => {
393
- const metric = model.accuracy ? `Accuracy: ${(model.accuracy * 100).toFixed(1)}%` :
394
- model.rmse ? `RMSE: ${model.rmse}` :
395
- `Silhouette Score: ${model.silhouette}`;
396
 
397
- output += `
398
- <div class="model-option">
399
- <strong>${model.name}</strong><br>
400
- <small>${metric}</small>
401
- </div>
402
- `;
 
 
 
 
 
403
  });
404
 
405
- output += '</div>';
406
- outputDiv.innerHTML = output;
 
 
 
407
  }
408
 
409
- // Create evaluation chart
410
- function createEvaluationChart(metrics) {
411
- const ctx = document.getElementById('evaluationChart');
412
- if (!ctx) return;
413
-
414
- // Destroy existing chart if it exists
415
- if (evaluationChart) {
416
- evaluationChart.destroy();
417
  }
418
 
419
- evaluationChart = new Chart(ctx, {
420
- type: 'bar',
421
- data: {
422
- labels: ['Accuracy', 'Precision', 'Recall', 'F1-Score'],
423
- datasets: [{
424
- label: 'Performance Metrics',
425
- data: [metrics.accuracy, metrics.precision, metrics.recall, metrics.f1Score],
426
- backgroundColor: ['#1FB8CD', '#FFC185', '#B4413C', '#5D878F'],
427
- borderColor: ['#1FB8CD', '#FFC185', '#B4413C', '#5D878F'],
428
- borderWidth: 2,
429
- borderRadius: 8
430
- }]
431
- },
432
- options: {
433
- responsive: true,
434
- maintainAspectRatio: false,
435
- plugins: {
436
- legend: {
437
- display: false
438
- },
439
- tooltip: {
440
- callbacks: {
441
- label: function(context) {
442
- return `${context.label}: ${(context.parsed.y * 100).toFixed(1)}%`;
443
- }
444
- }
445
- }
446
- },
447
- scales: {
448
- y: {
449
- beginAtZero: true,
450
- max: 1,
451
- ticks: {
452
- callback: function(value) {
453
- return (value * 100).toFixed(0) + '%';
454
- }
455
- }
456
- }
457
- }
458
  }
459
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
  }
461
 
462
- // Update progress summary
463
- function updateProgressSummary() {
464
- const completed = appData.lifecycleStages.filter(stage => stage.status === 'completed').length;
465
- const inProgress = appData.lifecycleStages.filter(stage => stage.status === 'in-progress').length;
466
- const pending = appData.lifecycleStages.filter(stage => stage.status === 'pending').length;
467
-
468
- document.getElementById('completedCount').textContent = completed;
469
- document.getElementById('inProgressCount').textContent = inProgress;
470
- document.getElementById('pendingCount').textContent = pending;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
471
  }
 
1
+ // Дані про робочий процес науки про дані
2
+ const workflowData = {
3
+ "title": "Робочий процес науки про дані",
4
+ "introduction": "Кожен проект з науки про дані включає кілька важливих етапів. Ця інтерактивна панель допоможе вам зрозуміти кожен етап робочого процесу та його важливість.",
5
+ "steps": [
6
  {
7
  "id": 1,
8
+ "title": "Формулювання проблеми",
9
+ "shortTitle": "Проблема",
10
+ "description": "Кожен проект з науки про дані починається з визначення проблеми. Незалежно від того, чи це прогнозування результатів пацієнтів або оптимізація бізнес-операцій, переклад реальної проблеми у питання, що базується на даних, є вирішальним.",
11
+ "keyPoints": [
12
+ "Визначення конкретної проблеми, яку потрібно вирішити",
13
+ "Формулювання питань, на які потрібно відповісти за допомогою даних",
14
+ "Вибір підходу до вирішення (машинне навчання, статистичний аналіз тощо)"
15
+ ],
16
+ "example": "У галузі охорони здоров'я, прогнозування часу відновлення пацієнта на основі медичної історії. Формулювання проблеми: лікарям потрібен інструмент для прогнозування тривалості відновлення пацієнта після лікування.",
17
+ "tips": [
18
+ "Завжди консультуйтеся з експертами в предметній області",
19
+ "Переконайтеся, що проблема може бути вирішена за допомогою даних",
20
+ "Визначте чіткі критерії успіху"
21
+ ],
22
+ "quiz": [
23
+ {
24
+ "question": "Що є першим кроком у проекті науки про дані?",
25
+ "options": [
26
+ "Збір даних",
27
+ "Визначення проблеми",
28
+ "Побудова моделі",
29
+ "Оцінка результатів"
30
+ ],
31
+ "answer": 1
32
+ }
33
+ ]
34
  },
35
  {
36
+ "id": 2,
37
+ "title": "Збір даних",
38
+ "shortTitle": "Дані",
39
+ "description": "Наступним кроком є збір відповідних даних з надійних джерел. Це може включати бази даних, публічні набори даних або дані, зібрані через сенсори та додатки.",
40
+ "keyPoints": [
41
+ "Визначення потрібних даних для вирішення проблеми",
42
+ "Ідентифікація джерел даних",
43
+ "Розуміння регуляторних вимог та етичних міркувань"
44
+ ],
45
+ "example": "Збір клінічних записів з баз даних охорони здоров'я для аналізу часу відновлення пацієнтів.",
46
+ "tips": [
47
+ "Переконайтеся, що ви маєте дозвіл на використання даних",
48
+ "Перевірте якість та надійність джерел даних",
49
+ "Зберігайте дані безпечно та відповідно до норм"
50
+ ],
51
+ "quiz": [
52
+ {
53
+ "question": "Що Ендрю Нг назвав 'їжею для ШІ'?",
54
+ "options": [
55
+ "Алгоритми",
56
+ "Дані",
57
+ "Обчислювальні потужності",
58
+ "Програмний код"
59
+ ],
60
+ "answer": 1
61
+ }
62
+ ]
63
  },
64
  {
65
  "id": 3,
66
+ "title": "Підготовка даних",
67
+ "shortTitle": "Підготовка",
68
+ "description": "Сирі дані часто містять невідповідності, відсутні значення або викиди. Підготовка даних забезпечує, що дані чисті та стандартизовані.",
69
+ "keyPoints": [
70
+ "Очищення даних від невідповідностей",
71
+ "Обробка відсутніх значень",
72
+ "Нормалізація та стандартизація даних"
73
+ ],
74
+ "example": "Очищення клінічних даних для забезпечення послідовності в аналізі.",
75
+ "tips": [
76
+ "Документуйте всі зміни, внесені до вихідних даних",
77
+ "Використовуйте автоматизовані інструменти для очищення великих наборів даних",
78
+ "Перевірте дані після очищення, щоб переконатися, що вони все ще відображають реальність"
79
+ ],
80
+ "quiz": [
81
+ {
82
+ "question": "Які проблеми вирішує етап підготовки даних?",
83
+ "options": [
84
+ "Невідповідності, відсутні значення, викиди",
85
+ "Формулювання проблеми та вибір алгоритму",
86
+ "Вибір метрик оцінки",
87
+ "Розгортання моделі в виробництво"
88
+ ],
89
+ "answer": 0
90
+ }
91
+ ]
92
  },
93
  {
94
  "id": 4,
95
+ "title": "Моделювання",
96
+ "shortTitle": "Модель",
97
+ "description": "Вибір правильного алгоритму є критичним. В залежності від проблеми, ми можемо використовувати регресію, класифікацію або кластеризацію, тощо.",
98
+ "keyPoints": [
99
+ "Вибір алгоритму на основі типу проблеми",
100
+ "Оцінка доступних даних (кількість та якість)",
101
+ "Врахування обчислювальних ресурсів та часу навчання"
102
+ ],
103
+ "example": "Вибір алгоритму машинного навчання для прогнозування часу відновлення пацієнта на основі історичних даних.",
104
+ "tips": [
105
+ "Почніть з простих моделей перед переходом до складніших",
106
+ "Розділіть дані на тренувальні та тестові набори",
107
+ "Експериментуйте з різними алгоритмами для порівняння результатів"
108
+ ],
109
+ "quiz": [
110
+ {
111
+ "question": "Які фактори слід враховувати при виборі алгоритму?",
112
+ "options": [
113
+ "Тільки тип проблеми",
114
+ "Тільки обчислювальні ресурси",
115
+ "Тип проблеми, доступні дані та обчислювальні ресурси",
116
+ "Тільки розмір набору даних"
117
+ ],
118
+ "answer": 2
119
+ }
120
+ ]
121
  },
122
  {
123
  "id": 5,
124
+ "title": "Оцінювання",
125
+ "shortTitle": "Оцінка",
126
+ "description": "Після навчання, оцінка моделі є важливою для забезпечення точності та надійності.",
127
+ "keyPoints": [
128
+ "Вибір відповідних метрик для оцінки моделі",
129
+ "Порівняння результатів з бізнес-цілями",
130
+ "Виявлення областей для покращення"
131
+ ],
132
+ "example": "Оцінка моделі для виявлення ранніх симптомів захворювання за допомогою метрик точності, чутливості та специфічності.",
133
+ "tips": [
134
+ "Використовуйте метрики, що відповідають бізнес-цілям",
135
+ "Проведіть крос-валідацію для надійності оцінки",
136
+ "Порівняйте результати з базовими моделями"
137
+ ],
138
+ "quiz": [
139
+ {
140
+ "question": "Які метрики можуть використовуватися для оцінки моделі?",
141
+ "options": [
142
+ "Тільки точність (accuracy)",
143
+ "Точність, повнота, F1-оцінка та інші",
144
+ "Тільки час навчання",
145
+ "Тільки розмір моделі"
146
+ ],
147
+ "answer": 1
148
+ }
149
+ ]
150
  },
151
  {
152
  "id": 6,
153
+ "title": "Розгортання",
154
+ "shortTitle": "Розгортання",
155
+ "description": "Розгортання це процес інтеграції навченої моделі в виробниче середовище, роблячи її доступною для реального використання. Цей крок є вирішальним для забезпечення того, щоб прогнози моделі були постійно доступні та надійні для кінцевих користувачів або автоматизованих систем.",
156
+ "keyPoints": [
157
+ "Контейнеризація моделі для портативності",
158
+ "Створення API для доступу до моделі",
159
+ "Налаштування безперервної інтеграції та доставки (CI/CD)",
160
+ "Забезпечення масштабованості та доступності"
161
+ ],
162
+ "example": "Розгортання моделі прогнозування часу відновлення пацієнта в хмарній платформі (AWS, Azure) та інтеграція з системою електронних медичних карт через API.",
163
+ "tips": [
164
+ "Тестуйте модель в середовищі, подібному до виробничого",
165
+ "Впровадіть моніторинг для відстеження продуктивності",
166
+ "Плануйте для масштабування від початку"
167
+ ],
168
+ "quiz": [
169
+ {
170
+ "question": "Що таке контейнеризація в контексті розгортання моделі?",
171
+ "options": [
172
+ "Упаковка моделі в фізичний контейнер для транспортування",
173
+ "Упаковка моделі, залежностей і середовища в програмний контейнер (як Docker)",
174
+ "Обмеження доступу до моделі",
175
+ "Зменшення розміру моделі"
176
+ ],
177
+ "answer": 1
178
+ }
179
+ ]
180
  },
181
  {
182
  "id": 7,
183
+ "title": "Моніторинг та підтримка",
184
+ "shortTitle": "Моніторинг",
185
+ "description": "Моделі потребують постійного моніторингу, щоб залишатися точними та ефективними. Зміни в розподілі даних можуть погіршити продуктивність з часом.",
186
+ "keyPoints": [
187
+ "Відстеження продуктивності моделі в реальному часі",
188
+ "Виявлення зміщення даних (data drift)",
189
+ "Регулярне перенавчання моделі з новими даними",
190
+ "Автоматизація розгортання оновлених моделей"
191
+ ],
192
+ "example": "Використання інструментів моніторингу, таких як Prometheus і Grafana, для відстеження точності моделі прогнозування часу відновлення пацієнта та виявлення будь-яких змін у розподілі даних.",
193
+ "tips": [
194
+ "Встановіть сповіщення для виявлення зниження продуктивності",
195
+ "Плануйте регулярні огляди моделі",
196
+ "Документуйте всі зміни та оновлення"
197
+ ],
198
+ "quiz": [
199
+ {
200
+ "question": "Чому важливий моніторинг моделі після розгортання?",
201
+ "options": [
202
+ "Він не важливий, якщо модель добре навчена",
203
+ "Тільки для задоволення регуляторних вимог",
204
+ "Для виявлення зміщення даних та підтримки продуктивності моделі",
205
+ "Тільки для економії обчислювальних ресурсів"
206
+ ],
207
+ "answer": 2
208
+ }
209
+ ]
210
  }
211
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  };
213
 
214
+ // Глобальні змінні стану
215
+ let currentStepId = null;
216
+ let visitedSteps = new Set();
217
+ let selectedQuizOption = null;
218
+ let quizAnswered = false;
219
+
220
+ // Іконки для етапів
221
+ const stepIcons = {
222
+ 1: '❓',
223
+ 2: '📊',
224
+ 3: '🔧',
225
+ 4: '🤖',
226
+ 5: '📈',
227
+ 6: '🚀',
228
+ 7: '👁️'
229
  };
 
230
 
231
+ // Ініціалізація додатка
232
  document.addEventListener('DOMContentLoaded', function() {
233
+ initializeApp();
234
+ setupEventListeners();
 
235
  });
236
 
237
+ function initializeApp() {
238
+ // Встановлення заголовку та вступу
239
+ document.getElementById('introText').textContent = workflowData.introduction;
240
 
241
+ // Рендер бічної панелі
242
+ renderSidebar();
243
+
244
+ // Рендер візуалізації робочого процесу
245
+ renderWorkflowDiagram();
246
+
247
+ // Оновлення прогресу
248
+ updateProgress();
249
+
250
+ // Приховати деталі етапу спочатку
251
+ document.getElementById('stepDetailsSection').classList.remove('active');
252
  }
253
 
254
+ function setupEventListeners() {
255
+ // Кнопки навігації
256
+ document.getElementById('nextStepBtn').addEventListener('click', () => navigateStep(1));
257
+ document.getElementById('prevStepBtn').addEventListener('click', () => navigateStep(-1));
258
+
259
+ // Кнопка скидання прогресу
260
+ document.getElementById('resetProgressBtn').addEventListener('click', resetProgress);
261
+
262
+ // Кнопка перевірки відповіді
263
+ document.getElementById('checkAnswerBtn').addEventListener('click', checkQuizAnswer);
 
 
 
 
 
 
 
 
264
  }
265
 
266
+ function renderSidebar() {
267
+ const sidebarList = document.getElementById('sidebarStepsList');
268
+ sidebarList.innerHTML = '';
 
 
 
 
 
 
 
 
 
 
 
 
269
 
270
+ workflowData.steps.forEach(step => {
271
+ const listItem = document.createElement('li');
272
+
273
+ const link = document.createElement('a');
274
+ link.href = '#';
275
+ link.className = 'step-link';
276
+ link.dataset.stepId = step.id;
277
+
278
+ const stepNumber = document.createElement('span');
279
+ stepNumber.className = 'step-number';
280
+ stepNumber.textContent = step.id;
281
+
282
+ const stepTitle = document.createElement('span');
283
+ stepTitle.textContent = step.shortTitle;
284
+
285
+ link.appendChild(stepNumber);
286
+ link.appendChild(stepTitle);
287
+
288
+ link.addEventListener('click', (e) => {
289
+ e.preventDefault();
290
+ showStepDetails(step.id);
291
+ });
292
+
293
+ listItem.appendChild(link);
294
+ sidebarList.appendChild(listItem);
295
  });
296
  }
297
 
298
+ function renderWorkflowDiagram() {
299
+ const diagram = document.getElementById('workflowDiagram');
300
+ diagram.innerHTML = '';
301
+
302
+ workflowData.steps.forEach(step => {
303
+ const stepElement = document.createElement('div');
304
+ stepElement.className = 'workflow-step';
305
+ stepElement.dataset.stepId = step.id;
306
+
307
+ const icon = document.createElement('div');
308
+ icon.className = 'step-icon';
309
+ icon.textContent = stepIcons[step.id] || '⚡';
310
+
311
+ const title = document.createElement('h3');
312
+ title.textContent = step.shortTitle;
313
+
314
+ stepElement.appendChild(icon);
315
+ stepElement.appendChild(title);
316
+
317
+ stepElement.addEventListener('click', () => {
318
+ showStepDetails(step.id);
319
+ });
320
+
321
+ diagram.appendChild(stepElement);
322
+ });
323
  }
324
 
325
+ function showStepDetails(stepId) {
326
+ const step = workflowData.steps.find(s => s.id === stepId);
327
+ if (!step) return;
328
+
329
+ currentStepId = stepId;
330
+ visitedSteps.add(stepId);
331
+
332
+ // Оновити активний стан
333
+ updateActiveStates();
334
+
335
+ // Заповнити деталі етапу
336
+ document.getElementById('stepTitle').textContent = step.title;
337
+ document.getElementById('stepDescription').textContent = step.description;
338
+
339
+ // Ключові моменти
340
+ const keyPointsList = document.getElementById('stepKeyPoints');
341
+ keyPointsList.innerHTML = '';
342
+ step.keyPoints.forEach(point => {
343
+ const li = document.createElement('li');
344
+ li.textContent = point;
345
+ keyPointsList.appendChild(li);
346
  });
347
 
348
+ // Приклад
349
+ document.getElementById('stepExample').textContent = step.example;
 
 
 
350
 
351
+ // Поради
352
+ const tipsList = document.getElementById('stepTips');
353
+ tipsList.innerHTML = '';
354
+ step.tips.forEach(tip => {
355
+ const li = document.createElement('li');
356
+ li.textContent = tip;
357
+ tipsList.appendChild(li);
358
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
359
 
360
+ // Вікторина
361
+ setupQuiz(step.quiz[0]);
 
 
362
 
363
+ // Показати панель деталей
364
+ document.getElementById('introSection').style.display = 'none';
365
+ document.getElementById('stepDetailsSection').classList.add('active');
366
 
367
+ // Оновити кнопки навігації
368
+ updateNavigationButtons();
369
+
370
+ // Оновити прогрес
371
+ updateProgress();
372
+
373
+ // Прокрутити до верху
374
+ document.getElementById('stepDetailsSection').scrollIntoView({ behavior: 'smooth' });
375
  }
376
 
377
+ function updateActiveStates() {
378
+ // Оновити бічну панель
379
+ document.querySelectorAll('.step-link').forEach(link => {
380
+ const stepId = parseInt(link.dataset.stepId);
381
+ link.classList.remove('active');
382
+ if (visitedSteps.has(stepId)) {
383
+ link.classList.add('visited');
384
+ }
385
+ if (stepId === currentStepId) {
386
+ link.classList.add('active');
387
+ }
388
+ });
389
+
390
+ // Оновити діаграму
391
+ document.querySelectorAll('.workflow-step').forEach(step => {
392
+ const stepId = parseInt(step.dataset.stepId);
393
+ step.classList.remove('active');
394
+ if (visitedSteps.has(stepId)) {
395
+ step.classList.add('visited');
396
+ }
397
+ if (stepId === currentStepId) {
398
+ step.classList.add('active');
399
+ }
400
  });
401
  }
402
 
403
+ function setupQuiz(quiz) {
404
+ document.getElementById('quizQuestion').textContent = quiz.question;
405
+
406
+ const optionsContainer = document.getElementById('quizOptions');
407
+ optionsContainer.innerHTML = '';
408
+
409
+ quiz.options.forEach((option, index) => {
410
+ const optionElement = document.createElement('div');
411
+ optionElement.className = 'quiz-option';
412
+ optionElement.textContent = option;
413
+ optionElement.dataset.index = index;
 
 
 
414
 
415
+ optionElement.addEventListener('click', () => {
416
+ if (quizAnswered) return;
417
+
418
+ document.querySelectorAll('.quiz-option').forEach(opt => {
419
+ opt.classList.remove('selected');
420
+ });
421
+ optionElement.classList.add('selected');
422
+ selectedQuizOption = index;
423
+ });
424
+
425
+ optionsContainer.appendChild(optionElement);
426
  });
427
 
428
+ // Скинути стан вікторини
429
+ selectedQuizOption = null;
430
+ quizAnswered = false;
431
+ document.getElementById('quizResult').classList.remove('show', 'correct', 'incorrect');
432
+ document.getElementById('checkAnswerBtn').style.display = 'block';
433
  }
434
 
435
+ function checkQuizAnswer() {
436
+ if (selectedQuizOption === null) {
437
+ alert('Будь ласка, оберіть відповідь');
438
+ return;
 
 
 
 
439
  }
440
 
441
+ const step = workflowData.steps.find(s => s.id === currentStepId);
442
+ const quiz = step.quiz[0];
443
+ const isCorrect = selectedQuizOption === quiz.answer;
444
+
445
+ // Показати результат
446
+ const resultElement = document.getElementById('quizResult');
447
+ resultElement.classList.add('show');
448
+
449
+ if (isCorrect) {
450
+ resultElement.classList.add('correct');
451
+ resultElement.textContent = '✅ Правильно! Відмінна робота!';
452
+ } else {
453
+ resultElement.classList.add('incorrect');
454
+ resultElement.textContent = `❌ Неправильно. Правильна відповідь: ${quiz.options[quiz.answer]}`;
455
+ }
456
+
457
+ // Підсвітити правильну та неправильну відповіді
458
+ document.querySelectorAll('.quiz-option').forEach((option, index) => {
459
+ if (index === quiz.answer) {
460
+ option.classList.add('correct');
461
+ } else if (index === selectedQuizOption && !isCorrect) {
462
+ option.classList.add('incorrect');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463
  }
464
  });
465
+
466
+ quizAnswered = true;
467
+ document.getElementById('checkAnswerBtn').style.display = 'none';
468
+ }
469
+
470
+ function navigateStep(direction) {
471
+ if (!currentStepId) return;
472
+
473
+ const currentIndex = workflowData.steps.findIndex(s => s.id === currentStepId);
474
+ const newIndex = currentIndex + direction;
475
+
476
+ if (newIndex >= 0 && newIndex < workflowData.steps.length) {
477
+ showStepDetails(workflowData.steps[newIndex].id);
478
+ }
479
+ }
480
+
481
+ function updateNavigationButtons() {
482
+ const currentIndex = workflowData.steps.findIndex(s => s.id === currentStepId);
483
+
484
+ document.getElementById('prevStepBtn').disabled = currentIndex === 0;
485
+ document.getElementById('nextStepBtn').disabled = currentIndex === workflowData.steps.length - 1;
486
  }
487
 
488
+ function updateProgress() {
489
+ const totalSteps = workflowData.steps.length;
490
+ const completedSteps = visitedSteps.size;
491
+ const percentage = (completedSteps / totalSteps) * 100;
492
+
493
+ document.getElementById('progressFill').style.width = `${percentage}%`;
494
+ document.getElementById('progressText').textContent = `${completedSteps}/${totalSteps} етапів вивчено`;
495
+ }
496
+
497
+ function resetProgress() {
498
+ if (confirm('Ви впевнені, що хочете скинути свій прогрес?')) {
499
+ visitedSteps.clear();
500
+ currentStepId = null;
501
+
502
+ // Приховати деталі етапу та показати вступ
503
+ document.getElementById('stepDetailsSection').classList.remove('active');
504
+ document.getElementById('introSection').style.display = 'block';
505
+
506
+ // Оновити стани
507
+ updateActiveStates();
508
+ updateProgress();
509
+
510
+ // Прокрутити до верху
511
+ window.scrollTo({ top: 0, behavior: 'smooth' });
512
+ }
513
  }