DavidBazaldua commited on
Commit
3a0687a
·
verified ·
1 Parent(s): 4b11952

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.js +72 -56
  2. index.html +91 -82
app.js CHANGED
@@ -187,68 +187,84 @@ function buildSpecialtyPct() {
187
  }
188
 
189
  /* ============================================================
190
- HUGGING FACE INFERENCE API INTEGRATION
191
- Model: pfizer-project-team/binary-segA-vs-segBC
192
  ============================================================ */
193
 
194
- const HF_API_URL = "https://api-inference.huggingface.co/models/pfizer-project-team/binary-segA-vs-segBC";
195
- // Note: If your model is private, you need a token. If it's public, you might not need it,
196
- // but it is highly recommended to avoid rate limits.
197
- const HF_TOKEN = "";
198
 
199
  async function runModelPrediction() {
200
- const resultDiv = document.getElementById('prediction-result');
201
- const predictBtn = document.getElementById('btn-predict');
202
-
203
- resultDiv.style.display = 'block';
204
- resultDiv.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Running inference on Hugging Face...';
205
- predictBtn.disabled = true;
206
-
207
- // ---------------------------------------------------------
208
- // CRITICAL FIX: The flattened tensor size is 86 weeks * 65 features
209
- // Total expected features per HCP = 5590
210
- // ---------------------------------------------------------
211
- const TENSOR_WEEKS = 86;
212
- const TENSOR_FEATURES = 65;
213
- const TOTAL_FEATURES = TENSOR_WEEKS * TENSOR_FEATURES; // 5590
214
-
215
- // Create an array of 5590 zeros to avoid shape mismatch errors
216
- const dummyFeatures = new Array(TOTAL_FEATURES).fill(0);
217
-
218
- // You can inject test values at specific indices if you know them.
219
- // For now, we send an array of 5590 zeros.
220
-
221
- const payload = {
222
- "inputs": [dummyFeatures]
223
- };
224
-
225
- try {
226
- const response = await fetch(HF_API_URL, {
227
- method: "POST",
228
- headers: {
229
- "Authorization": `Bearer ${userHfToken}`,
230
- "Content-Type": "application/json"
231
- },
232
- body: JSON.stringify(payload)
233
- });
234
-
235
- if (!response.ok) {
236
- const errorText = await response.text();
237
- throw new Error(`HTTP ${response.status}: ${errorText}`);
238
- }
239
 
240
- const data = await response.json();
241
- const predictionValue = Array.isArray(data) ? data[0] : JSON.stringify(data);
242
-
243
- resultDiv.innerHTML = `<i class="fas fa-check-circle" style="color: var(--accent-green);"></i> Model Output: <b>${predictionValue}</b>`;
244
-
245
- } catch (error) {
246
- console.error("HF Inference API Error:", error);
247
- resultDiv.innerHTML = `<i class="fas fa-exclamation-triangle" style="color: var(--accent-coral);"></i> Error: ${error.message}`;
248
- } finally {
249
- predictBtn.disabled = false;
 
 
 
 
 
 
 
250
  }
 
 
 
 
 
 
 
 
251
  }
252
 
253
  /* Init */
254
- document.addEventListener('DOMContentLoaded', () => { initTabs(); loadTab('tab-overview'); animateCounters(); });
 
 
 
 
 
 
 
 
 
 
 
187
  }
188
 
189
  /* ============================================================
190
+ CLIENT-SIDE INFERENCE ENGINE (PYODIDE)
191
+ Model: scikit-learn model loaded directly in browser
192
  ============================================================ */
193
 
194
+ let pyodideInstance = null;
 
 
 
195
 
196
  async function runModelPrediction() {
197
+ const resultDiv = document.getElementById('prediction-result');
198
+ const predictBtn = document.getElementById('btn-predict');
199
+
200
+ // Handle UI state for loading (disable button and show spinner)
201
+ predictBtn.disabled = true;
202
+ resultDiv.style.display = 'block';
203
+ resultDiv.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Initializing Pyodide & loading model (may take a moment)...';
204
+
205
+ try {
206
+ // Initialize Pyodide
207
+ if (!pyodideInstance) {
208
+ pyodideInstance = await loadPyodide();
209
+ // Load the scikit-learn and numpy packages into the browser memory
210
+ await pyodideInstance.loadPackage(['scikit-learn', 'numpy']);
211
+ }
212
+
213
+ resultDiv.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Downloading model and running inference...';
214
+
215
+ // Use Python code (executed via JavaScript) to download the model directly from this URL
216
+ const pythonCode = `
217
+ import pyodide.http
218
+ import numpy as np
219
+ import joblib
220
+
221
+ url = "https://huggingface.co/pfizer-project-team/binary-segA-vs-segBC/resolve/main/sklearn_model.joblib"
222
+
223
+ # In Pyodide, we must use pyfetch to make HTTPS requests
224
+ response = await pyodide.http.pyfetch(url)
225
+ with open("sklearn_model.joblib", "wb") as f:
226
+ f.write(await response.bytes())
227
+
228
+ # Load the scikit-learn model
229
+ model = joblib.load("sklearn_model.joblib")
 
 
 
 
 
 
230
 
231
+ # The model expects a flattened tensor of exactly 5590 features (86 weeks * 65 features)
232
+ # Create a dummy numpy array of shape (1, 5590) of zeros for the prediction
233
+ dummy_features = np.zeros((1, 5590))
234
+
235
+ # Run the prediction using model.predict()
236
+ prediction = model.predict(dummy_features)
237
+ int(prediction[0])
238
+ `;
239
+
240
+ // Execute python code and wait for result
241
+ const result = await pyodideInstance.runPythonAsync(pythonCode);
242
+
243
+ // Return the result to JS and update UI
244
+ if (result === 1) {
245
+ resultDiv.innerHTML = '<i class="fas fa-check-circle" style="color: var(--accent-green);"></i> SEG_B/C (High Potential)';
246
+ } else {
247
+ resultDiv.innerHTML = '<i class="fas fa-circle" style="color: var(--text-muted);"></i> SEG_A (Traditionalist)';
248
  }
249
+
250
+ } catch (error) {
251
+ console.error("Pyodide Client-Side ML Error:", error);
252
+ resultDiv.innerHTML = `<i class="fas fa-exclamation-triangle" style="color: var(--accent-coral);"></i> Inference Error: ${error.message}`;
253
+ } finally {
254
+ // Re-enable the button
255
+ predictBtn.disabled = false;
256
+ }
257
  }
258
 
259
  /* Init */
260
+ document.addEventListener('DOMContentLoaded', () => {
261
+ initTabs();
262
+ loadTab('tab-overview');
263
+ animateCounters();
264
+
265
+ // Bind live prediction button
266
+ const predictBtn = document.getElementById('btn-predict');
267
+ if (predictBtn) {
268
+ predictBtn.addEventListener('click', runModelPrediction);
269
+ }
270
+ });
index.html CHANGED
@@ -11,6 +11,7 @@
11
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
12
 
13
  <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
 
14
  </head>
15
 
16
  <body>
@@ -424,112 +425,120 @@
424
 
425
  <!-- ==================== TAB 6: UNLABELED OPPORTUNITY ==================== -->
426
  <div id="tab-opportunity" class="tab-content">
427
-
428
- <div class="section-header">
429
- <div class="section-icon"><i class="fas fa-crosshairs"></i></div>
430
- <div>
431
- <div class="section-title">Unlabeled HCP Opportunity</div>
432
- <div class="section-subtitle">Prioritizing 633 unclassified HCPs for commercial outreach</div>
 
433
  </div>
434
- </div>
435
 
436
- <div class="grid-2" style="align-items: start; margin-bottom: 24px;">
437
-
438
- <div>
439
- <div class="grid-3" style="margin-bottom: 24px;">
440
- <div class="card tier-card tier-1">
441
- <div class="tier-value" style="color:var(--accent-green)">43</div>
442
- <div class="tier-label">Tier 1 — Immediate</div>
443
- <div class="tier-desc">Score &ge; 0.60. Highest prescribing + growth signals.</div>
444
- </div>
445
- <div class="card tier-card tier-2">
446
- <div class="tier-value" style="color:var(--accent-amber)">22</div>
447
- <div class="tier-label">Tier 2 — Validate</div>
448
- <div class="tier-desc">Score 0.35–0.60. Moderate opportunity, needs validation.</div>
449
- </div>
450
- <div class="card tier-card tier-3">
451
- <div class="tier-value" style="color:var(--text-muted)">568</div>
452
- <div class="tier-label">Tier 3 — Monitor</div>
453
- <div class="tier-desc">Score &lt; 0.35. Low activity, monitor for emergence.</div>
 
454
  </div>
455
- </div>
456
 
457
- <div class="alert-box alert-warning">
458
- <i class="fas fa-exclamation-triangle"></i>
459
- <span><strong>Coverage Gap:</strong> 347 of 633 unlabeled HCPs (54.8%) have zero rep visits. Among Tier 1 (high-opportunity) HCPs, many prescribe actively but have never been contacted by a sales representative.</span>
 
 
 
460
  </div>
461
- </div>
462
 
463
- <div class="card" id="inference-card" style="height: 100%;">
464
- <div class="chart-title">Live Model Prediction: Segment Classification</div>
465
- <div class="chart-container" style="height: auto; padding: 15px 0;">
466
- <p style="font-size: 14px; color: var(--text-secondary); margin-bottom: 25px;">
467
- Test the live Hugging Face model (SEG_A vs SEG_BC) with sample HCP data.
468
- </p>
469
- <button id="btn-predict" class="tab-btn active" style="width: 100%; justify-content: center; border-radius: 8px; padding: 12px;">
470
- <i class="fas fa-brain"></i> Run Live Prediction
471
- </button>
472
- <div id="prediction-result" style="margin-top: 20px; font-weight: 600; color: var(--pfizer-deep); font-size: 16px; background: var(--bg-active); padding: 12px; border-radius: 8px; display: none;">
 
 
 
 
473
  </div>
474
  </div>
 
475
  </div>
476
-
477
- </div> <div class="grid-2" style="margin-top:24px">
478
- <div class="card">
479
- <div class="chart-title">Opportunity Score Distribution (633 Unlabeled HCPs)</div>
480
- <div class="chart-container" style="height:300px"><canvas id="chart-opp-hist"></canvas></div>
481
- </div>
482
- <div class="card">
483
- <div class="chart-title">Click a red point to identify the HCP below ↓</div>
484
- <div class="chart-container" style="height:300px"><canvas id="chart-opp-scatter"></canvas></div>
485
  </div>
486
- </div>
487
 
488
- <div id="hcp-detail-panel" class="card" style="margin-top:24px;display:none;border-left:4px solid var(--accent-coral)">
489
- <div class="section-header" style="margin-bottom:16px">
490
- <div class="section-icon" style="background:#fef2f2;color:var(--accent-coral)">
491
- <i class="fas fa-user-md"></i>
 
 
 
 
 
 
492
  </div>
493
- <div>
494
- <div class="section-title" id="hcp-detail-title">HCP Selected</div>
495
- <div class="section-subtitle">Zero rep visits — high opportunity for outreach</div>
 
 
496
  </div>
497
  </div>
498
- <div class="grid-5" id="hcp-detail-grid"></div>
499
- <div class="alert-box alert-warning" style="margin-top:16px">
500
- <i class="fas fa-bullhorn"></i>
501
- <span><strong>Action Required:</strong> This HCP has never been visited by a sales representative yet shows significant UC prescribing activity. Recommend scheduling an initial detail call.</span>
502
- </div>
503
- </div>
504
 
505
- </div> ```
506
 
507
 
508
- <!-- ==================== TAB 7: SPECIALTY MIX ==================== -->
509
- <div id="tab-specialty" class="tab-content">
510
- <div class="section-header">
511
- <div class="section-icon"><i class="fas fa-stethoscope"></i></div>
512
- <div>
513
- <div class="section-title">Specialty & Demographics</div>
514
- <div class="section-subtitle">HCP specialty distribution across segments</div>
515
- </div>
516
  </div>
 
517
 
518
- <div class="grid-2">
519
- <div class="card">
520
- <div class="chart-title">HCPs by Specialty and Segment (Stacked)</div>
521
- <div class="chart-container" style="height:300px"><canvas id="chart-spec-stack"></canvas></div>
522
- </div>
523
- <div class="card">
524
- <div class="chart-title">Specialty Composition (% within each specialty)</div>
525
- <div class="chart-container" style="height:300px"><canvas id="chart-spec-pct"></canvas></div>
526
- </div>
527
  </div>
528
  </div>
 
529
 
530
 
531
 
532
- <script src="app.js"></script>
533
  </body>
534
 
535
  </html>
 
11
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
12
 
13
  <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
14
+ <script src="https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.js"></script>
15
  </head>
16
 
17
  <body>
 
425
 
426
  <!-- ==================== TAB 6: UNLABELED OPPORTUNITY ==================== -->
427
  <div id="tab-opportunity" class="tab-content">
428
+
429
+ <div class="section-header">
430
+ <div class="section-icon"><i class="fas fa-crosshairs"></i></div>
431
+ <div>
432
+ <div class="section-title">Unlabeled HCP Opportunity</div>
433
+ <div class="section-subtitle">Prioritizing 633 unclassified HCPs for commercial outreach</div>
434
+ </div>
435
  </div>
 
436
 
437
+ <div class="grid-2" style="align-items: start; margin-bottom: 24px;">
438
+
439
+ <div>
440
+ <div class="grid-3" style="margin-bottom: 24px;">
441
+ <div class="card tier-card tier-1">
442
+ <div class="tier-value" style="color:var(--accent-green)">39</div>
443
+ <div class="tier-label">Tier 1 — Immediate</div>
444
+ <div class="tier-desc">Score &ge; 0.60. Highest prescribing + growth signals.</div>
445
+ </div>
446
+ <div class="card tier-card tier-2">
447
+ <div class="tier-value" style="color:var(--accent-amber)">14</div>
448
+ <div class="tier-label">Tier 2 — Validate</div>
449
+ <div class="tier-desc">Score 0.35–0.60. Moderate opportunity, needs validation.</div>
450
+ </div>
451
+ <div class="card tier-card tier-3">
452
+ <div class="tier-value" style="color:var(--text-muted)">580</div>
453
+ <div class="tier-label">Tier 3 — Monitor</div>
454
+ <div class="tier-desc">Score &lt; 0.35. Low activity, monitor for emergence.</div>
455
+ </div>
456
  </div>
 
457
 
458
+ <div class="alert-box alert-warning">
459
+ <i class="fas fa-exclamation-triangle"></i>
460
+ <span><strong>Coverage Gap:</strong> 347 of 633 unlabeled HCPs (54.8%) have zero rep visits. Among
461
+ Tier 1 (high-opportunity) HCPs, many prescribe actively but have never been contacted by a sales
462
+ representative.</span>
463
+ </div>
464
  </div>
 
465
 
466
+ <div class="card" id="inference-card" style="height: 100%;">
467
+ <div class="chart-title">Live Model Prediction: Segment Classification</div>
468
+ <div class="chart-container" style="height: auto; padding: 15px 0;">
469
+ <p style="font-size: 14px; color: var(--text-secondary); margin-bottom: 25px;">
470
+ Test the live Hugging Face model (SEG_A vs SEG_BC) with sample HCP data.
471
+ </p>
472
+ <button id="btn-predict"
473
+ style="width: 100%; justify-content: center; border-radius: 8px; padding: 12px; background: var(--pfizer-blue); color: white; border: none; cursor: pointer; font-weight: 600; font-size: 14px; display: flex; align-items: center; gap: 8px; transition: opacity 0.2s;"
474
+ onmouseover="this.style.opacity=0.9" onmouseout="this.style.opacity=1">
475
+ <i class="fas fa-brain"></i> Run Live Prediction
476
+ </button>
477
+ <div id="prediction-result"
478
+ style="margin-top: 20px; font-weight: 600; color: var(--pfizer-deep); font-size: 16px; background: var(--bg-active); padding: 12px; border-radius: 8px; display: none;">
479
+ </div>
480
  </div>
481
  </div>
482
+
483
  </div>
484
+ <div class="grid-2" style="margin-top:24px">
485
+ <div class="card">
486
+ <div class="chart-title">Opportunity Score Distribution (633 Unlabeled HCPs)</div>
487
+ <div class="chart-container" style="height:300px"><canvas id="chart-opp-hist"></canvas></div>
488
+ </div>
489
+ <div class="card">
490
+ <div class="chart-title">Click a red point to identify the HCP below ↓</div>
491
+ <div class="chart-container" style="height:300px"><canvas id="chart-opp-scatter"></canvas></div>
492
+ </div>
493
  </div>
 
494
 
495
+ <div id="hcp-detail-panel" class="card"
496
+ style="margin-top:24px;display:none;border-left:4px solid var(--accent-coral)">
497
+ <div class="section-header" style="margin-bottom:16px">
498
+ <div class="section-icon" style="background:#fef2f2;color:var(--accent-coral)">
499
+ <i class="fas fa-user-md"></i>
500
+ </div>
501
+ <div>
502
+ <div class="section-title" id="hcp-detail-title">HCP Selected</div>
503
+ <div class="section-subtitle">Zero rep visits — high opportunity for outreach</div>
504
+ </div>
505
  </div>
506
+ <div class="grid-5" id="hcp-detail-grid"></div>
507
+ <div class="alert-box alert-warning" style="margin-top:16px">
508
+ <i class="fas fa-bullhorn"></i>
509
+ <span><strong>Action Required:</strong> This HCP has never been visited by a sales representative yet
510
+ shows significant UC prescribing activity. Recommend scheduling an initial detail call.</span>
511
  </div>
512
  </div>
 
 
 
 
 
 
513
 
514
+ </div> ```
515
 
516
 
517
+ <!-- ==================== TAB 7: SPECIALTY MIX ==================== -->
518
+ <div id="tab-specialty" class="tab-content">
519
+ <div class="section-header">
520
+ <div class="section-icon"><i class="fas fa-stethoscope"></i></div>
521
+ <div>
522
+ <div class="section-title">Specialty & Demographics</div>
523
+ <div class="section-subtitle">HCP specialty distribution across segments</div>
 
524
  </div>
525
+ </div>
526
 
527
+ <div class="grid-2">
528
+ <div class="card">
529
+ <div class="chart-title">HCPs by Specialty and Segment (Stacked)</div>
530
+ <div class="chart-container" style="height:300px"><canvas id="chart-spec-stack"></canvas></div>
531
+ </div>
532
+ <div class="card">
533
+ <div class="chart-title">Specialty Composition (% within each specialty)</div>
534
+ <div class="chart-container" style="height:300px"><canvas id="chart-spec-pct"></canvas></div>
 
535
  </div>
536
  </div>
537
+ </div>
538
 
539
 
540
 
541
+ <script src="app.js"></script>
542
  </body>
543
 
544
  </html>