DavidBazaldua commited on
Commit
7e495a0
·
verified ·
1 Parent(s): 41b7a24

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.js +33 -14
  2. index.html +7 -7
app.js CHANGED
@@ -41,7 +41,7 @@ function loadTab(id){
41
  if(id==='tab-adoption'){buildAdoptionPct();buildAdoptionAbs();buildGrowthSignals();buildTrendBars();}
42
  if(id==='tab-competitive'){buildCompShare();buildCompRatio();buildScatterUC();}
43
  if(id==='tab-engagement'){buildEngagement();buildScatterEng();}
44
- if(id==='tab-opportunity'){buildOpportunityHist();buildOpportunityScatter();}
45
  if(id==='tab-specialty'){buildSpecialtyStack();buildSpecialtyPct();}
46
 
47
  }
@@ -99,25 +99,44 @@ function buildScatterEng(){const ctx=document.getElementById('chart-scatter-eng'
99
  new Chart(ctx,{type:'scatter',data:{datasets:[{label:'SEG_A',data:mk(400,5.28,0.0005),backgroundColor:CA+'66',pointRadius:3},{label:'SEG_B',data:mk(300,8.94,0.0018),backgroundColor:CB+'66',pointRadius:3},{label:'SEG_C',data:mk(200,8.71,0.0017),backgroundColor:CC+'66',pointRadius:3}]},options:{maintainAspectRatio:false,responsive:true,plugins:{legend:{position:'bottom'}},scales:{x:{title:{display:true,text:'Total Rep Visits (86 wks)'},grid:{color:'#f1f5f9'}},y:{title:{display:true,text:'Pfizer TRx / week'},grid:{color:'#f1f5f9'}}}}});}
100
 
101
  /* ==================== TAB 6: OPPORTUNITY ==================== */
102
- function buildOpportunityHist(){
103
- const bins=[1750,20,31,30,27,47,295,308,514,1108,890,790,752,754,650,466,258,105,51,8,14,10,18,13,17,27,33,20,16,10];
104
- const edges=[];for(let i=0;i<=30;i++)edges.push(0.295+i*(0.941-0.295)/30);
105
- const labels=edges.slice(0,-1).map((e,i)=>((e+edges[i+1])/2).toFixed(2));
106
- mkBar('chart-opp-hist',labels,[{data:bins,backgroundColor:CU+'cc',borderRadius:2,borderSkipped:false}],{plugins:{legend:{display:false}},x:{ticks:{maxTicksLimit:10,font:{size:10}}}});
107
- }
108
-
109
- function buildOpportunityScatter(){
110
  fetch('opportunity_data.json').then(r=>r.json()).then(data=>{
111
- const nv=data.noVisits.map(h=>({x:h.uc,y:h.sc,...h}));
112
- const cv=data.covered.map(h=>({x:h.uc,y:h.sc,...h}));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  const ctx=document.getElementById('chart-opp-scatter');if(!ctx)return;
114
  const chart=new Chart(ctx,{type:'scatter',data:{datasets:[
115
- {label:'No Rep Visits',data:nv,backgroundColor:RED+'99',pointRadius:4,pointStyle:'circle'},
116
- {label:'Covered',data:cv,backgroundColor:CB+'77',pointRadius:4,pointStyle:'rect'}
117
  ]},options:{maintainAspectRatio:false,responsive:true,
118
  plugins:{legend:{position:'bottom'},tooltip:{callbacks:{
119
  title:pts=>{const p=pts[0];return p.datasetIndex===0?'ID: '+p.raw.id:'Covered HCP';},
120
- label:p=>[`UC TRx: ${p.raw.x.toFixed(4)}/wk`,`Score: ${p.raw.y.toFixed(4)}`,p.raw.sp?`Specialty: ${p.raw.sp}`:'']
121
  }}},
122
  scales:{x:{title:{display:true,text:'UC TRx Mean (weekly)'},grid:{color:'#f1f5f9'}},y:{title:{display:true,text:'Opportunity Score'},grid:{color:'#f1f5f9'}}},
123
  onClick:(evt,els)=>{
 
41
  if(id==='tab-adoption'){buildAdoptionPct();buildAdoptionAbs();buildGrowthSignals();buildTrendBars();}
42
  if(id==='tab-competitive'){buildCompShare();buildCompRatio();buildScatterUC();}
43
  if(id==='tab-engagement'){buildEngagement();buildScatterEng();}
44
+ if(id==='tab-opportunity'){buildOpportunityCharts();}
45
  if(id==='tab-specialty'){buildSpecialtyStack();buildSpecialtyPct();}
46
 
47
  }
 
99
  new Chart(ctx,{type:'scatter',data:{datasets:[{label:'SEG_A',data:mk(400,5.28,0.0005),backgroundColor:CA+'66',pointRadius:3},{label:'SEG_B',data:mk(300,8.94,0.0018),backgroundColor:CB+'66',pointRadius:3},{label:'SEG_C',data:mk(200,8.71,0.0017),backgroundColor:CC+'66',pointRadius:3}]},options:{maintainAspectRatio:false,responsive:true,plugins:{legend:{position:'bottom'}},scales:{x:{title:{display:true,text:'Total Rep Visits (86 wks)'},grid:{color:'#f1f5f9'}},y:{title:{display:true,text:'Pfizer TRx / week'},grid:{color:'#f1f5f9'}}}}});}
100
 
101
  /* ==================== TAB 6: OPPORTUNITY ==================== */
102
+ function buildOpportunityCharts(){
 
 
 
 
 
 
 
103
  fetch('opportunity_data.json').then(r=>r.json()).then(data=>{
104
+ // Add a tiny random jitter to the y-axis (Score) so overlapping points are visible
105
+ // We keep the original 'uc' and 'sc' to show in tooltips
106
+ const jitter = () => (Math.random() - 0.5) * 0.04;
107
+ const nv=data.noVisits.map(h=>({x:h.uc, y:Math.max(0, h.sc + jitter()), ...h}));
108
+ const cv=data.covered.map(h=>({x:h.uc, y:Math.max(0, h.sc + jitter()), ...h}));
109
+
110
+ // Histogram
111
+ const all = [...data.noVisits, ...data.covered];
112
+ const scores = all.map(h=>h.sc);
113
+ const minSc = Math.min(...scores);
114
+ const maxSc = Math.max(...scores);
115
+
116
+ const numBins = 20;
117
+ const binWidth = (maxSc > minSc) ? (maxSc - minSc) / numBins : 1;
118
+ let edges = [];
119
+ for(let i=0; i<=numBins; i++) edges.push(minSc + i * binWidth);
120
+
121
+ let bins = Array(numBins).fill(0);
122
+ scores.forEach(s => {
123
+ let b = Math.floor((s - minSc) / binWidth);
124
+ if (b >= numBins) b = numBins - 1;
125
+ bins[b]++;
126
+ });
127
+
128
+ const histLabels = edges.slice(0, -1).map((e, i) => ((e + edges[i+1])/2).toFixed(2));
129
+ mkBar('chart-opp-hist', histLabels, [{data:bins, backgroundColor:CU+'cc', borderRadius:2, borderSkipped:false}], {plugins:{legend:{display:false}},x:{ticks:{maxTicksLimit:10,font:{size:10}}}});
130
+
131
+ // Scatter Plot
132
  const ctx=document.getElementById('chart-opp-scatter');if(!ctx)return;
133
  const chart=new Chart(ctx,{type:'scatter',data:{datasets:[
134
+ {label:'No Rep Visits',data:nv,backgroundColor:RED+'aa',pointRadius:4,pointStyle:'circle'},
135
+ {label:'Covered',data:cv,backgroundColor:CB+'88',pointRadius:4,pointStyle:'rect'}
136
  ]},options:{maintainAspectRatio:false,responsive:true,
137
  plugins:{legend:{position:'bottom'},tooltip:{callbacks:{
138
  title:pts=>{const p=pts[0];return p.datasetIndex===0?'ID: '+p.raw.id:'Covered HCP';},
139
+ label:p=>[`UC TRx: ${p.raw.uc.toFixed(4)}/wk`,`Score: ${p.raw.sc.toFixed(4)}`,p.raw.sp?`Specialty: ${p.raw.sp}`:'']
140
  }}},
141
  scales:{x:{title:{display:true,text:'UC TRx Mean (weekly)'},grid:{color:'#f1f5f9'}},y:{title:{display:true,text:'Opportunity Score'},grid:{color:'#f1f5f9'}}},
142
  onClick:(evt,els)=>{
index.html CHANGED
@@ -41,7 +41,7 @@
41
  <div class="grid-5">
42
  <div class="card kpi-card"><div class="kpi-top"><span class="kpi-label">Total HCPs</span><div class="kpi-icon" style="background:#f1f5f9;color:#64748b"><i class="fas fa-users"></i></div></div><div class="kpi-value" data-count="20931">0</div><div class="kpi-sub">86-week longitudinal panel</div></div>
43
  <div class="card kpi-card"><div class="kpi-top"><span class="kpi-label">Labeled Cohort</span><div class="kpi-icon" style="background:#eef6fc;color:var(--pfizer-blue)"><i class="fas fa-tag"></i></div></div><div class="kpi-value" data-count="11899">0</div><div class="kpi-sub">56.9% of total market</div></div>
44
- <div class="card kpi-card"><div class="kpi-top"><span class="kpi-label">Unlabeled Pool</span><div class="kpi-icon" style="background:#f5f3ff;color:var(--seg-unlabeled)"><i class="fas fa-search"></i></div></div><div class="kpi-value" data-count="9032">0</div><div class="kpi-sub">43.1% pending classification</div></div>
45
  <div class="card kpi-card"><div class="kpi-top"><span class="kpi-label">Feature Columns</span><div class="kpi-icon" style="background:#ecfaff;color:var(--pfizer-sky)"><i class="fas fa-database"></i></div></div><div class="kpi-value" data-count="191">0</div><div class="kpi-sub">7 feature blocks engineered</div></div>
46
  <div class="card kpi-card"><div class="kpi-top"><span class="kpi-label">Deep Learning Recall</span><div class="kpi-icon" style="background:#ecfdf5;color:var(--accent-green)"><i class="fas fa-brain"></i></div></div><div class="kpi-value" data-count="75" data-suffix="%">0</div><div class="kpi-sub">On minority class (SEG_C)</div></div>
47
  </div>
@@ -204,18 +204,18 @@
204
 
205
  <!-- ==================== TAB 6: UNLABELED OPPORTUNITY ==================== -->
206
  <div id="tab-opportunity" class="tab-content">
207
- <div class="section-header"><div class="section-icon"><i class="fas fa-crosshairs"></i></div><div><div class="section-title">Unlabeled HCP Opportunity</div><div class="section-subtitle">Prioritizing 9,032 unclassified HCPs for commercial outreach</div></div></div>
208
 
209
  <div class="grid-3" style="margin-bottom:24px">
210
- <div class="card tier-card tier-1"><div class="tier-value" style="color:var(--accent-green)">1,600</div><div class="tier-label">Tier 1 — Immediate</div><div class="tier-desc">Score ≥ 0.60. Highest prescribing + growth signals.</div></div>
211
- <div class="card tier-card tier-2"><div class="tier-value" style="color:var(--accent-amber)">5,643</div><div class="tier-label">Tier 2 — Validate</div><div class="tier-desc">Score 0.35–0.60. Moderate opportunity, needs validation.</div></div>
212
- <div class="card tier-card tier-3"><div class="tier-value" style="color:var(--text-muted)">1,789</div><div class="tier-label">Tier 3 — Monitor</div><div class="tier-desc">Score < 0.35. Low activity, monitor for emergence.</div></div>
213
  </div>
214
 
215
- <div class="alert-box alert-warning"><i class="fas fa-exclamation-triangle"></i><span><strong>Coverage Gap:</strong> 5,733 of 9,032 unlabeled HCPs (63.5%) have zero rep visits. Among Tier 1 (high-opportunity) HCPs, many prescribe actively but have never been contacted by a sales representative.</span></div>
216
 
217
  <div class="grid-2" style="margin-top:24px">
218
- <div class="card"><div class="chart-title">Opportunity Score Distribution (9,032 Unlabeled HCPs)</div><div class="chart-container" style="height:300px"><canvas id="chart-opp-hist"></canvas></div></div>
219
  <div class="card"><div class="chart-title">Click a red point to identify the HCP below ↓</div><div class="chart-container" style="height:300px"><canvas id="chart-opp-scatter"></canvas></div></div>
220
  </div>
221
 
 
41
  <div class="grid-5">
42
  <div class="card kpi-card"><div class="kpi-top"><span class="kpi-label">Total HCPs</span><div class="kpi-icon" style="background:#f1f5f9;color:#64748b"><i class="fas fa-users"></i></div></div><div class="kpi-value" data-count="20931">0</div><div class="kpi-sub">86-week longitudinal panel</div></div>
43
  <div class="card kpi-card"><div class="kpi-top"><span class="kpi-label">Labeled Cohort</span><div class="kpi-icon" style="background:#eef6fc;color:var(--pfizer-blue)"><i class="fas fa-tag"></i></div></div><div class="kpi-value" data-count="11899">0</div><div class="kpi-sub">56.9% of total market</div></div>
44
+ <div class="card kpi-card"><div class="kpi-top"><span class="kpi-label">Unlabeled Pool</span><div class="kpi-icon" style="background:#f5f3ff;color:var(--seg-unlabeled)"><i class="fas fa-search"></i></div></div><div class="kpi-value" data-count="633">0</div><div class="kpi-sub">Pending classification</div></div>
45
  <div class="card kpi-card"><div class="kpi-top"><span class="kpi-label">Feature Columns</span><div class="kpi-icon" style="background:#ecfaff;color:var(--pfizer-sky)"><i class="fas fa-database"></i></div></div><div class="kpi-value" data-count="191">0</div><div class="kpi-sub">7 feature blocks engineered</div></div>
46
  <div class="card kpi-card"><div class="kpi-top"><span class="kpi-label">Deep Learning Recall</span><div class="kpi-icon" style="background:#ecfdf5;color:var(--accent-green)"><i class="fas fa-brain"></i></div></div><div class="kpi-value" data-count="75" data-suffix="%">0</div><div class="kpi-sub">On minority class (SEG_C)</div></div>
47
  </div>
 
204
 
205
  <!-- ==================== TAB 6: UNLABELED OPPORTUNITY ==================== -->
206
  <div id="tab-opportunity" class="tab-content">
207
+ <div class="section-header"><div class="section-icon"><i class="fas fa-crosshairs"></i></div><div><div class="section-title">Unlabeled HCP Opportunity</div><div class="section-subtitle">Prioritizing 633 unclassified HCPs for commercial outreach</div></div></div>
208
 
209
  <div class="grid-3" style="margin-bottom:24px">
210
+ <div class="card tier-card tier-1"><div class="tier-value" style="color:var(--accent-green)">43</div><div class="tier-label">Tier 1 — Immediate</div><div class="tier-desc">Score ≥ 0.60. Highest prescribing + growth signals.</div></div>
211
+ <div class="card tier-card tier-2"><div class="tier-value" style="color:var(--accent-amber)">22</div><div class="tier-label">Tier 2 — Validate</div><div class="tier-desc">Score 0.35–0.60. Moderate opportunity, needs validation.</div></div>
212
+ <div class="card tier-card tier-3"><div class="tier-value" style="color:var(--text-muted)">568</div><div class="tier-label">Tier 3 — Monitor</div><div class="tier-desc">Score < 0.35. Low activity, monitor for emergence.</div></div>
213
  </div>
214
 
215
+ <div class="alert-box alert-warning"><i class="fas fa-exclamation-triangle"></i><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></div>
216
 
217
  <div class="grid-2" style="margin-top:24px">
218
+ <div class="card"><div class="chart-title">Opportunity Score Distribution (633 Unlabeled HCPs)</div><div class="chart-container" style="height:300px"><canvas id="chart-opp-hist"></canvas></div></div>
219
  <div class="card"><div class="chart-title">Click a red point to identify the HCP below ↓</div><div class="chart-container" style="height:300px"><canvas id="chart-opp-scatter"></canvas></div></div>
220
  </div>
221