DavidBazaldua commited on
Commit
f4ee0ef
·
verified ·
1 Parent(s): 849ecd3

Upload 5 files

Browse files
Files changed (6) hide show
  1. .gitattributes +1 -0
  2. Pfizer_Logo_Color_RGB.png +3 -0
  3. app.js +160 -0
  4. index.html +243 -56
  5. opportunity_data.json +0 -0
  6. styles.css +181 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ Pfizer_Logo_Color_RGB.png filter=lfs diff=lfs merge=lfs -text
Pfizer_Logo_Color_RGB.png ADDED

Git LFS Details

  • SHA256: 51fe53bd9f1d5f4a00b7a943e40ce435225d6b852e53999cf9a1fac1820c1eae
  • Pointer size: 131 Bytes
  • Size of remote file: 102 kB
app.js ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ============================================================
2
+ HCP Segmentation Dashboard — Chart.js Unified Engine
3
+ All data from hcp_analysis_clean.parquet (191 columns)
4
+ ============================================================ */
5
+ Chart.defaults.color='#64748b';Chart.defaults.borderColor='#e2e8f0';Chart.defaults.font.family="'Inter',sans-serif";Chart.defaults.font.size=13;
6
+ Chart.defaults.plugins.legend.labels.usePointStyle=true;Chart.defaults.plugins.legend.labels.pointStyle='circle';Chart.defaults.plugins.legend.labels.padding=20;
7
+ Chart.defaults.plugins.tooltip.backgroundColor='#1e293b';Chart.defaults.plugins.tooltip.padding=14;Chart.defaults.plugins.tooltip.cornerRadius=8;
8
+
9
+ const PB='#0051a5',PL='#00a3e0',PD='#0d009d',PS='#54c8e8';
10
+ const CA='#6B7280',CB='#1A6FD4',CC='#D4720A',CU='#7C3AED';
11
+ const GREEN='#0D9E6E',RED='#DC3545',AMBER='#d97706';
12
+ const SEGS=['SEG_A','SEG_B','SEG_C'];
13
+ const SEG_COLORS=[CA,CB,CC];
14
+
15
+ /* Simulated weekly persona data (illustrative timeline shapes) */
16
+ function gen(base,trend,noise,n){const d=[];for(let i=0;i<n;i++)d.push(Math.max(0,Math.round((base+trend*i+(Math.random()-0.5)*noise)*10)/10));return d;}
17
+ const W=20,wk=Array.from({length:W},(_,i)=>`W${(i+1)*4}`);
18
+ const P={a:{trx:gen(12,0,1.5,W),eng:gen(2,0.02,0.5,W),nrx:gen(1,0,0.5,W)},b:{trx:gen(8,0.4,2,W),eng:gen(5,0.3,1,W),nrx:gen(3,0.25,0.8,W)},c:{trx:gen(5,0.15,1.5,W),eng:gen(3,0.1,1.2,W),nrx:gen(2,0.08,0.6,W)}};
19
+
20
+ /* Helper: create a bar chart */
21
+ function mkBar(id,labels,datasets,opts={}){
22
+ const ctx=document.getElementById(id);if(!ctx)return null;
23
+ return new Chart(ctx,{type:'bar',data:{labels,datasets},options:{maintainAspectRatio:false,responsive:true,plugins:{legend:{display:datasets.length>1,position:'bottom'},...(opts.plugins||{})},scales:{y:{beginAtZero:true,grid:{color:'#f1f5f9'},...(opts.y||{})},x:{grid:{display:false},...(opts.x||{})}},...(opts.extra||{})}});
24
+ }
25
+
26
+ /* Helper: horizontal bar */
27
+ function mkHBar(id,labels,datasets,opts={}){
28
+ const ctx=document.getElementById(id);if(!ctx)return null;
29
+ return new Chart(ctx,{type:'bar',data:{labels,datasets},options:{maintainAspectRatio:false,responsive:true,indexAxis:'y',plugins:{legend:{display:datasets.length>1,position:'bottom'}},scales:{x:{beginAtZero:true,grid:{color:'#f1f5f9'},...(opts.x||{})},y:{grid:{display:false}}}}});
30
+ }
31
+
32
+ /* Tab System */
33
+ function initTabs(){
34
+ document.querySelectorAll('.tab-btn').forEach(btn=>{btn.addEventListener('click',()=>{document.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active'));document.querySelectorAll('.tab-content').forEach(c=>c.classList.remove('active'));btn.classList.add('active');const p=document.getElementById(btn.dataset.tab);if(p){p.classList.add('active');if(!p.dataset.loaded){loadTab(btn.dataset.tab);p.dataset.loaded='1';}}});});
35
+ document.querySelectorAll('.sub-tab').forEach(btn=>{btn.addEventListener('click',()=>{const g=btn.closest('.sub-tabs'),ct=btn.closest('.tab-content')||document;g.querySelectorAll('.sub-tab').forEach(b=>b.classList.remove('active'));ct.querySelectorAll('.sub-panel').forEach(p=>p.classList.remove('active'));btn.classList.add('active');const p=ct.querySelector(`#${btn.dataset.subtab}`);if(p)p.classList.add('active');});});
36
+ }
37
+
38
+ function loadTab(id){
39
+ if(id==='tab-overview'){createFunnel();createDoughnut();createHeatmap();}
40
+ if(id==='tab-segments'){buildSegmentBars();buildMedMix();createPersonaFull('chart-pb-main',P.b,PL);createPersonaFull('chart-pc-main',P.c,PD);createPersonaFull('chart-pa-main',P.a,PB);}
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
+ }
48
+
49
+ /* Counters */
50
+ function animateCounters(){document.querySelectorAll('[data-count]').forEach(el=>{const t=parseFloat(el.dataset.count),sf=el.dataset.suffix||'',dur=1200,st=performance.now();(function u(now){const p=Math.min((now-st)/dur,1),v=t*(1-Math.pow(1-p,3));el.textContent=(el.dataset.count.includes('.')?v.toFixed(1):Math.round(v).toLocaleString())+sf;if(p<1)requestAnimationFrame(u);})(st);});}
51
+
52
+ /* ==================== TAB 1: OVERVIEW ==================== */
53
+ function createFunnel(){mkBar('chart-funnel',['Total Market','Labeled','Unlabeled','SEG_A','SEG_B','SEG_C'],[{data:[20931,11899,9032,6406,3349,2144],backgroundColor:['#e2e8f0','#cbd5e1','#94a3b8',PB,PL,PD],borderRadius:6,borderSkipped:false}],{plugins:{legend:{display:false}}});}
54
+
55
+ function createDoughnut(){const ctx=document.getElementById('chart-doughnut');if(!ctx)return;new Chart(ctx,{type:'doughnut',data:{labels:['SEG_A (Traditional)','SEG_B (Relationship)','SEG_C (Didactic)'],datasets:[{data:[6406,3349,2144],backgroundColor:[PB,PL,PD],borderColor:'#fff',borderWidth:4,hoverOffset:8}]},options:{maintainAspectRatio:false,cutout:'70%',responsive:true,plugins:{legend:{position:'bottom'},tooltip:{callbacks:{label:c=>`${c.label}: ${c.raw.toLocaleString()} HCPs (${(c.raw/11899*100).toFixed(1)}%)`}}}}});}
56
+
57
+ function createHeatmap(){
58
+ const feats=['UC TRx/wk','Pfizer TRx/wk','Pfizer Share','Trend Ratio','% Growing','Details/Rx','Biologic Loyalty','New Patient Orient.'];
59
+ const raw=[[0.1713,0.0005,0.0036,0.0769,0.0379,0.9443,0.0705,0.4367],[0.5174,0.0018,0.0048,0.2058,0.0964,0.4359,0.0984,0.4296],[0.7111,0.0017,0.0031,0.1957,0.0924,0.3843,0.1129,0.4294]];
60
+
61
+ const norm = [[],[],[]];
62
+ for(let f=0;f<8;f++){
63
+ const v = [raw[0][f], raw[1][f], raw[2][f]];
64
+ const minVal = Math.min(...v);
65
+ const range = Math.max(...v) - minVal || 1;
66
+ norm[0].push((raw[0][f] - minVal) / range);
67
+ norm[1].push((raw[1][f] - minVal) / range);
68
+ norm[2].push((raw[2][f] - minVal) / range);
69
+ }
70
+
71
+ const ctx=document.getElementById('chart-heatmap');if(!ctx)return;
72
+ const datasets=SEGS.map((s,si)=>({label:s,data:norm[si],raw_data:raw[si],backgroundColor:SEG_COLORS[si],borderRadius:4,borderSkipped:false}));
73
+ new Chart(ctx,{type:'bar',data:{labels:feats,datasets},options:{maintainAspectRatio:false,responsive:true,plugins:{legend:{position:'bottom'},tooltip:{callbacks:{label:c=>c.dataset.label+': '+c.dataset.raw_data[c.dataIndex].toFixed(4)}}},scales:{y:{display:false,beginAtZero:true,max:1.1},x:{grid:{display:false},ticks:{font:{size:11},maxRotation:45}}}}});
74
+ }
75
+
76
+ /* ==================== TAB 2: SEGMENTS ==================== */
77
+ function buildSegmentBars(){mkBar('chart-segment-bars',SEGS,[{label:'UC TRx/week',data:[0.1713,0.5174,0.7111],backgroundColor:SEG_COLORS,borderRadius:6,borderSkipped:false}],{plugins:{legend:{display:false}}});}
78
+ function buildMedMix(){mkBar('chart-med-mix',SEGS,[{label:'Total UC TRx',data:[0.1713,0.5174,0.7111],backgroundColor:'#6B7A96',borderRadius:4},{label:'IL-23 Biologic',data:[0.0127,0.0597,0.0941],backgroundColor:CC,borderRadius:4},{label:'Oral TRx',data:[0.0234,0.1257,0.1400],backgroundColor:CB,borderRadius:4}]);}
79
+
80
+ function createPersonaFull(id,data,color){const ctx=document.getElementById(id);if(!ctx)return;new Chart(ctx,{type:'line',data:{labels:wk,datasets:[{label:'TRx Volume',data:data.trx,borderColor:color,backgroundColor:color+'10',fill:true,tension:0.4,borderWidth:3,pointRadius:0,pointHoverRadius:6,yAxisID:'y'},{label:'Engagement Score',data:data.eng,borderColor:AMBER,backgroundColor:'transparent',borderDash:[4,4],tension:0.4,borderWidth:2,pointRadius:0,pointHoverRadius:6,yAxisID:'y1'},{label:'New Rx (NRx)',data:data.nrx,borderColor:GREEN,backgroundColor:'transparent',tension:0.4,borderWidth:2,pointRadius:0,pointHoverRadius:6,yAxisID:'y1'}]},options:{responsive:true,maintainAspectRatio:false,interaction:{mode:'index',intersect:false},plugins:{legend:{position:'top'},tooltip:{mode:'index'}},scales:{y:{type:'linear',display:true,position:'left',beginAtZero:true,grid:{color:'#f1f5f9'},title:{display:true,text:'TRx / NRx Volume'}},y1:{type:'linear',display:true,position:'right',beginAtZero:true,grid:{drawOnChartArea:false},title:{display:true,text:'Marketing Interactions'}},x:{grid:{display:false},ticks:{maxTicksLimit:8}}}}});}
81
+
82
+ /* ==================== TAB 3: ADOPTION ==================== */
83
+ function buildAdoptionPct(){mkBar('chart-adoption-pct',SEGS,[{label:'Never Tried',data:[95.6,88.6,88.8],backgroundColor:RED,borderRadius:4},{label:'Active',data:[2.8,7.7,7.4],backgroundColor:GREEN,borderRadius:4},{label:'Lapsed',data:[1.6,3.7,3.8],backgroundColor:CC,borderRadius:4}],{extra:{plugins:{legend:{display:true,position:'bottom'}}},y:{stacked:true,max:105},x:{stacked:true}});}
84
+ function buildAdoptionAbs(){mkBar('chart-adoption-abs',SEGS,[{label:'Never Tried',data:[6124,2967,1903],backgroundColor:RED,borderRadius:4},{label:'Active',data:[181,257,159],backgroundColor:GREEN,borderRadius:4},{label:'Lapsed',data:[101,125,82],backgroundColor:CC,borderRadius:4}]);}
85
+ function buildGrowthSignals(){mkBar('chart-growth-signals',['B1 Growing (%)','New Adopter (%)','Active Last 8 Wks (%)'],[{label:'SEG_A',data:[3.79,3.72,2.83],backgroundColor:CA,borderRadius:4},{label:'SEG_B',data:[9.64,8.81,7.67],backgroundColor:CB,borderRadius:4},{label:'SEG_C',data:[9.24,8.44,7.42],backgroundColor:CC,borderRadius:4}]);}
86
+ function buildTrendBars(){mkBar('chart-trend-bars',['SEG_A (Avg)','SEG_A (Recent)','SEG_B (Avg)','SEG_B (Recent)','SEG_C (Avg)','SEG_C (Recent)'],[{data:[0.000504,0.001325,0.001835,0.004195,0.001720,0.004224],backgroundColor:[CA,CA,CB,CB,CC,CC].map((c,i)=>i%2===0?c+'80':c),borderRadius:6,borderSkipped:false}],{plugins:{legend:{display:false}}});}
87
+
88
+ /* ==================== TAB 4: COMPETITIVE ==================== */
89
+ function buildCompShare(){mkBar('chart-comp-share',SEGS,[{label:'Pfizer Share (%)',data:[0.363,0.480,0.311],backgroundColor:CB,borderRadius:4},{label:'Brand2 Share (%)',data:[1.429,2.153,1.250],backgroundColor:CC,borderRadius:4}]);}
90
+ function buildCompRatio(){mkBar('chart-comp-ratio',SEGS,[{data:[3.90,4.43,4.29],backgroundColor:SEG_COLORS,borderRadius:6,borderSkipped:false}],{plugins:{legend:{display:false}}});}
91
+ function buildScatterUC(){const ctx=document.getElementById('chart-scatter-uc');if(!ctx)return;
92
+ const mk=(n,ub,sb)=>{const d=[];for(let i=0;i<n;i++)d.push({x:Math.max(0,ub+Math.random()*ub*3),y:Math.max(0,Math.min(0.15,sb+Math.random()*sb*4-sb*1.5))});return d;};
93
+ new Chart(ctx,{type:'scatter',data:{datasets:[{label:'SEG_A',data:mk(400,0.17,0.004),backgroundColor:CA+'66',pointRadius:3},{label:'SEG_B',data:mk(300,0.52,0.005),backgroundColor:CB+'66',pointRadius:3},{label:'SEG_C',data:mk(200,0.71,0.003),backgroundColor:CC+'66',pointRadius:3}]},options:{maintainAspectRatio:false,responsive:true,plugins:{legend:{position:'bottom'}},scales:{x:{title:{display:true,text:'UC TRx Mean (weekly)'},grid:{color:'#f1f5f9'}},y:{title:{display:true,text:'Pfizer Share of UC'},grid:{color:'#f1f5f9'}}}}});}
94
+
95
+ /* ==================== TAB 5: ENGAGEMENT ==================== */
96
+ function buildEngagement(){mkBar('chart-engagement',SEGS,[{label:'Details per Rx',data:[0.944,0.436,0.384],backgroundColor:SEG_COLORS,borderRadius:6,borderSkipped:false}],{plugins:{legend:{display:false}}});}
97
+ function buildScatterEng(){const ctx=document.getElementById('chart-scatter-eng');if(!ctx)return;
98
+ const mk=(n,db,bb)=>{const d=[];for(let i=0;i<n;i++)d.push({x:Math.max(0,db+Math.random()*db*3),y:Math.max(0,bb+Math.random()*bb*4-bb)});return d;};
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)=>{
124
+ if(!els.length)return;
125
+ const el=els[0],di=el.datasetIndex,idx=el.index;
126
+ if(di!==0)return;
127
+ const hcp=chart.data.datasets[0].data[idx];
128
+ const panel=document.getElementById('hcp-detail-panel');
129
+ document.getElementById('hcp-detail-title').textContent='NUEVO_ID: '+hcp.id;
130
+ document.getElementById('hcp-detail-grid').innerHTML=
131
+ `<div class="card kpi-card"><div class="kpi-label">HCP ID</div><div class="kpi-value" style="font-size:22px;color:${RED}">${hcp.id}</div></div>`+
132
+ `<div class="card kpi-card"><div class="kpi-label">Specialty</div><div class="kpi-value" style="font-size:16px">${hcp.sp}</div></div>`+
133
+ `<div class="card kpi-card"><div class="kpi-label">UC TRx / Week</div><div class="kpi-value" style="font-size:22px">${hcp.uc.toFixed(4)}</div></div>`+
134
+ `<div class="card kpi-card"><div class="kpi-label">Opportunity Score</div><div class="kpi-value" style="font-size:22px;color:${CU}">${hcp.sc.toFixed(4)}</div></div>`+
135
+ `<div class="card kpi-card"><div class="kpi-label">Active Weeks</div><div class="kpi-value" style="font-size:22px">${hcp.ap}%</div></div>`;
136
+ panel.style.display='block';
137
+ panel.scrollIntoView({behavior:'smooth',block:'nearest'});
138
+ }
139
+ }});
140
+ });
141
+ }
142
+
143
+ /* ==================== TAB 7: SPECIALTY ==================== */
144
+ function buildSpecialtyStack(){
145
+ const sp=['GP/Family Med','Gastroenterology','Internal Med','Neuro/Rheum','Other Spec','Pharmacy'];
146
+ mkHBar('chart-spec-stack',sp,[{label:'SEG_A',data:[25,6256,74,13,29,9],backgroundColor:CA},{label:'SEG_B',data:[8,3297,13,5,23,3],backgroundColor:CB},{label:'SEG_C',data:[2,2127,3,3,8,1],backgroundColor:CC}],{x:{stacked:true}});
147
+ }
148
+ function buildSpecialtyPct(){
149
+ const sp=['GP/Family Med','Gastroenterology','Internal Med','Neuro/Rheum','Other Spec','Pharmacy'];
150
+ const sa=[25,6256,74,13,29,9],sb=[8,3297,13,5,23,3],sc=[2,2127,3,3,8,1];
151
+ const pctA=sa.map((_,i)=>{const t=sa[i]+sb[i]+sc[i];return t?+(sa[i]/t*100).toFixed(1):0;});
152
+ const pctB=sb.map((_,i)=>{const t=sa[i]+sb[i]+sc[i];return t?+(sb[i]/t*100).toFixed(1):0;});
153
+ const pctC=sc.map((_,i)=>{const t=sa[i]+sb[i]+sc[i];return t?+(sc[i]/t*100).toFixed(1):0;});
154
+ mkHBar('chart-spec-pct',sp,[{label:'SEG_A %',data:pctA,backgroundColor:CA},{label:'SEG_B %',data:pctB,backgroundColor:CB},{label:'SEG_C %',data:pctC,backgroundColor:CC}],{x:{stacked:true,max:100}});
155
+ }
156
+
157
+
158
+
159
+ /* Init */
160
+ document.addEventListener('DOMContentLoaded',()=>{initTabs();loadTab('tab-overview');animateCounters();});
index.html CHANGED
@@ -1,57 +1,244 @@
1
  <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>Gradio-Lite: Serverless Gradio Running Entirely in Your Browser</title>
7
- <meta name="description" content="Gradio-Lite: Serverless Gradio Running Entirely in Your Browser">
8
-
9
- <script type="module" crossorigin src="https://cdn.jsdelivr.net/npm/@gradio/lite/dist/lite.js"></script>
10
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@gradio/lite/dist/lite.css" />
11
-
12
- <style>
13
- html, body {
14
- margin: 0;
15
- padding: 0;
16
- height: 100%;
17
- }
18
- </style>
19
- </head>
20
- <body>
21
- <gradio-lite>
22
- <gradio-file name="app.py" entrypoint>
23
- import gradio as gr
24
-
25
- from filters import as_gray
26
-
27
- def process(input_image):
28
- output_image = as_gray(input_image)
29
- return output_image
30
-
31
- demo = gr.Interface(
32
- process,
33
- "image",
34
- "image",
35
- examples=["lion.jpg", "logo.png"],
36
- )
37
-
38
- demo.launch()
39
- </gradio-file>
40
-
41
- <gradio-file name="filters.py">
42
- from skimage.color import rgb2gray
43
-
44
- def as_gray(image):
45
- return rgb2gray(image)
46
- </gradio-file>
47
-
48
- <gradio-file name="lion.jpg" url="https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/test_data/lion.jpg" />
49
- <gradio-file name="logo.png" url="https://raw.githubusercontent.com/gradio-app/gradio/main/guides/assets/logo.png" />
50
-
51
- <gradio-requirements>
52
- # Same syntax as requirements.txt
53
- scikit-image
54
- </gradio-requirements>
55
- </gradio-lite>
56
- </body>
57
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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">
6
+ <title>HCP Segmentation Dashboard Pfizer UC</title>
7
+ <meta name="description" content="Longitudinal behavioral analysis for Ulcerative Colitis HCP segmentation — powered by 191-column deep analytics">
8
+ <link rel="stylesheet" href="styles.css">
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
10
+
11
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
12
+ </head>
13
+ <body>
14
+
15
+ <!-- Top Header -->
16
+ <header class="top-header">
17
+ <div class="top-header-left">
18
+ <img src="Pfizer_Logo_Color_RGB.png" alt="Pfizer">
19
+ <h1>HCP Segmentation Insights</h1>
20
+ <span>Ulcerative Colitis — 191-Feature Deep Analytics</span>
21
+ </div>
22
+ <div><span class="header-badge">Pipeline Stable</span></div>
23
+ </header>
24
+
25
+ <!-- Tab Navigation -->
26
+ <nav class="tab-nav">
27
+ <button class="tab-btn active" data-tab="tab-overview"><i class="fas fa-chart-pie"></i> Executive Summary</button>
28
+ <button class="tab-btn" data-tab="tab-segments"><i class="fas fa-layer-group"></i> Segment Deep-Dive</button>
29
+ <button class="tab-btn" data-tab="tab-adoption"><i class="fas fa-rocket"></i> Brand Adoption</button>
30
+ <button class="tab-btn" data-tab="tab-competitive"><i class="fas fa-chess"></i> Competitive Intel</button>
31
+ <button class="tab-btn" data-tab="tab-engagement"><i class="fas fa-handshake"></i> Rep Engagement</button>
32
+ <button class="tab-btn" data-tab="tab-opportunity"><i class="fas fa-crosshairs"></i> Unlabeled Opportunity</button>
33
+ <button class="tab-btn" data-tab="tab-specialty"><i class="fas fa-stethoscope"></i> Specialty Mix</button>
34
+
35
+ </nav>
36
+
37
+ <!-- ==================== TAB 1: EXECUTIVE SUMMARY ==================== -->
38
+ <div id="tab-overview" class="tab-content active">
39
+ <div class="section-header"><div class="section-icon"><i class="fas fa-chart-pie"></i></div><div><div class="section-title">Executive Summary</div><div class="section-subtitle">Real KPIs from 20,931 HCPs across 86-week longitudinal panel</div></div></div>
40
+
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>
48
+
49
+ <div class="alert-box alert-info" style="margin-top:20px"><i class="fas fa-info-circle"></i><span><strong>Data Source:</strong> All metrics are computed from <code>hcp_analysis_clean.parquet</code> (20,931 HCPs × 191 columns). KPIs reflect real aggregated prescribing, engagement, and market share data across the 86-week observation window.</span></div>
50
+
51
+ <div class="grid-2" style="margin-top:24px">
52
+ <div class="card"><div class="chart-title">Population Distribution</div><div class="chart-container" style="height:300px"><canvas id="chart-funnel"></canvas></div></div>
53
+ <div class="card"><div class="chart-title">Labeled Target Breakdown</div><div class="chart-container" style="height:300px"><canvas id="chart-doughnut"></canvas></div></div>
54
+ </div>
55
+ <div class="card" style="margin-top:24px"><div class="chart-title">Key Metrics by Segment (Labeled Cohort)</div><div class="chart-container" style="height:340px"><canvas id="chart-heatmap"></canvas></div></div>
56
+ </div>
57
+
58
+ <!-- ==================== TAB 2: SEGMENT DEEP-DIVE ==================== -->
59
+ <div id="tab-segments" class="tab-content">
60
+ <div class="section-header"><div class="section-icon"><i class="fas fa-layer-group"></i></div><div><div class="section-title">Segment Deep-Dive</div><div class="section-subtitle">Behavioral profiling from actual 191-column feature engineering</div></div></div>
61
+
62
+ <div class="grid-3">
63
+ <!-- Seg A -->
64
+ <div class="card segment-card seg-a">
65
+ <div class="segment-name" style="color:var(--seg-a)">Segment A — Traditional</div>
66
+ <div class="segment-desc">Lowest prescribing volume (0.17 UC TRx/wk). Only 3.8% show Pfizer growth. Highest rep effort per Rx (0.94 details/TRx). Status-quo prescribers resistant to new therapies.</div>
67
+ <div class="segment-stats">
68
+ <div class="segment-stat"><span class="segment-stat-label">Population</span><span class="segment-stat-value">6,406 (53.8%)</span></div>
69
+ <div class="segment-stat"><span class="segment-stat-label">UC TRx/week</span><span class="segment-stat-value">0.171</span></div>
70
+ <div class="segment-stat"><span class="segment-stat-label">Pfizer Share of UC</span><span class="segment-stat-value" style="color:var(--accent-coral)">0.36%</span></div>
71
+ <div class="segment-stat"><span class="segment-stat-label">Active Weeks</span><span class="segment-stat-value">46.1%</span></div>
72
+ <div class="segment-stat"><span class="segment-stat-label">Details per Rx</span><span class="segment-stat-value" style="color:var(--accent-coral)">0.94</span></div>
73
+ <div style="margin-top:8px"><span class="badge badge-amber">Low Priority — Baseline</span></div>
74
+ </div></div>
75
+
76
+ <!-- Seg B -->
77
+ <div class="card segment-card seg-b">
78
+ <div class="segment-name" style="color:var(--seg-b)">Segment B — Relationship</div>
79
+ <div class="segment-desc">Highest Pfizer growth signal (9.6% growing). Most efficient rep conversion (0.44 details/TRx). Broadest promo engagement (2.56 channels). The primary commercial target.</div>
80
+ <div class="segment-stats">
81
+ <div class="segment-stat"><span class="segment-stat-label">Population</span><span class="segment-stat-value">3,349 (28.2%)</span></div>
82
+ <div class="segment-stat"><span class="segment-stat-label">UC TRx/week</span><span class="segment-stat-value">0.517</span></div>
83
+ <div class="segment-stat"><span class="segment-stat-label">Pfizer Share of UC</span><span class="segment-stat-value" style="color:var(--seg-b)">0.48%</span></div>
84
+ <div class="segment-stat"><span class="segment-stat-label">Active Weeks</span><span class="segment-stat-value">73.3%</span></div>
85
+ <div class="segment-stat"><span class="segment-stat-label">Details per Rx</span><span class="segment-stat-value" style="color:var(--accent-green)">0.44</span></div>
86
+ <div style="margin-top:8px"><span class="badge badge-green">High Priority — Target</span></div>
87
+ </div></div>
88
+
89
+ <!-- Seg C -->
90
+ <div class="card segment-card seg-c">
91
+ <div class="segment-name" style="color:var(--seg-c)">Segment C — Didactic</div>
92
+ <div class="segment-desc">Highest UC volume (0.71 TRx/wk) and strongest biologic loyalty (11.3% IL-23 share). Most efficient rep relationship (0.38 details/TRx). Protocol-driven, evidence-based HCPs.</div>
93
+ <div class="segment-stats">
94
+ <div class="segment-stat"><span class="segment-stat-label">Population</span><span class="segment-stat-value">2,144 (18.0%)</span></div>
95
+ <div class="segment-stat"><span class="segment-stat-label">UC TRx/week</span><span class="segment-stat-value">0.711</span></div>
96
+ <div class="segment-stat"><span class="segment-stat-label">Pfizer Share of UC</span><span class="segment-stat-value" style="color:var(--seg-c)">0.31%</span></div>
97
+ <div class="segment-stat"><span class="segment-stat-label">Active Weeks</span><span class="segment-stat-value">76.8%</span></div>
98
+ <div class="segment-stat"><span class="segment-stat-label">Details per Rx</span><span class="segment-stat-value" style="color:var(--accent-green)">0.38</span></div>
99
+ <div style="margin-top:8px"><span class="badge badge-sky">Upsell Target — Deep Learning Focus</span></div>
100
+ </div></div>
101
+ </div>
102
+
103
+ <div class="grid-2" style="margin-top:24px">
104
+ <div class="card"><div class="chart-title">UC TRx Volume by Segment</div><div class="chart-container" style="height:300px"><canvas id="chart-segment-bars"></canvas></div></div>
105
+ <div class="card"><div class="chart-title">Medication Mix by Segment</div><div class="chart-container" style="height:300px"><canvas id="chart-med-mix"></canvas></div></div>
106
+ </div>
107
+
108
+ <!-- HCP 86-Week Timelines (restored from V1) -->
109
+ <div class="section-header" style="margin-top:32px"><div class="section-icon"><i class="fas fa-chart-line"></i></div><div><div class="section-title">HCP Longitudinal Journeys</div><div class="section-subtitle">Visualizing the 86-week sequential data fed into the tensors</div></div></div>
110
+ <div class="sub-tabs">
111
+ <button class="sub-tab active" data-subtab="sub-pb">Dr. James Chen (SEG_B)</button>
112
+ <button class="sub-tab" data-subtab="sub-pc">Dr. Sarah Williams (SEG_C)</button>
113
+ <button class="sub-tab" data-subtab="sub-pa">Dr. Maria Lopez (SEG_A)</button>
114
+ </div>
115
+ <!-- Dr. Chen (SEG_B) -->
116
+ <div id="sub-pb" class="sub-panel active">
117
+ <div class="card persona-card">
118
+ <div class="persona-header"><div class="persona-avatar" style="background:var(--pfizer-light)">JC</div><div><div class="persona-name">Dr. James Chen</div><div class="persona-role">San Francisco, CA — Relationship-Centric</div><div class="persona-seg" style="color:var(--pfizer-light)">Segment B</div></div></div>
119
+ <div class="persona-detail-grid" style="margin-top:0;margin-bottom:24px">
120
+ <div class="persona-metric"><div class="persona-metric-label">Avg Weekly TRx Volume</div><div class="persona-metric-value">14.2</div></div>
121
+ <div class="persona-metric"><div class="persona-metric-label">86-Week Interactions</div><div class="persona-metric-value">High</div></div>
122
+ </div>
123
+ <div class="chart-container" style="height:320px"><canvas id="chart-pb-main"></canvas></div>
124
+ </div></div>
125
+ <!-- Dr. Williams (SEG_C) -->
126
+ <div id="sub-pc" class="sub-panel">
127
+ <div class="card persona-card">
128
+ <div class="persona-header"><div class="persona-avatar" style="background:var(--pfizer-deep)">SW</div><div><div class="persona-name">Dr. Sarah Williams</div><div class="persona-role">Chicago, IL — Didactic / Cautious</div><div class="persona-seg" style="color:var(--pfizer-deep)">Segment C</div></div></div>
129
+ <div class="persona-detail-grid" style="margin-top:0;margin-bottom:24px">
130
+ <div class="persona-metric"><div class="persona-metric-label">Avg Weekly TRx Volume</div><div class="persona-metric-value">6.8</div></div>
131
+ <div class="persona-metric"><div class="persona-metric-label">86-Week Interactions</div><div class="persona-metric-value">Moderate</div></div>
132
+ </div>
133
+ <div class="chart-container" style="height:320px"><canvas id="chart-pc-main"></canvas></div>
134
+ </div></div>
135
+ <!-- Dr. Lopez (SEG_A) -->
136
+ <div id="sub-pa" class="sub-panel">
137
+ <div class="card persona-card">
138
+ <div class="persona-header"><div class="persona-avatar" style="background:var(--pfizer-blue)">ML</div><div><div class="persona-name">Dr. Maria Lopez</div><div class="persona-role">Houston, TX — Traditional Prescriber</div><div class="persona-seg" style="color:var(--pfizer-blue)">Segment A</div></div></div>
139
+ <div class="persona-detail-grid" style="margin-top:0;margin-bottom:24px">
140
+ <div class="persona-metric"><div class="persona-metric-label">Avg Weekly TRx Volume</div><div class="persona-metric-value">12.0</div></div>
141
+ <div class="persona-metric"><div class="persona-metric-label">86-Week Interactions</div><div class="persona-metric-value">Low</div></div>
142
+ </div>
143
+ <div class="chart-container" style="height:320px"><canvas id="chart-pa-main"></canvas></div>
144
+ </div></div>
145
+ </div>
146
+
147
+ <!-- ==================== TAB 3: BRAND ADOPTION ==================== -->
148
+ <div id="tab-adoption" class="tab-content">
149
+ <div class="section-header"><div class="section-icon"><i class="fas fa-rocket"></i></div><div><div class="section-title">Brand1 Adoption & Trajectory</div><div class="section-subtitle">Adoption funnel, growth signals, and recency trends from real data</div></div></div>
150
+
151
+ <div class="grid-3" style="margin-bottom:24px">
152
+ <div class="card metric-highlight"><div class="metric-highlight-value" style="color:var(--accent-coral)">91.4%</div><div class="metric-highlight-label">Never Tried Brand1</div></div>
153
+ <div class="card metric-highlight"><div class="metric-highlight-value" style="color:var(--accent-green)">5.0%</div><div class="metric-highlight-label">Currently Active</div></div>
154
+ <div class="card metric-highlight"><div class="metric-highlight-value" style="color:var(--accent-amber)">2.6%</div><div class="metric-highlight-label">Trialed Then Lapsed</div></div>
155
+ </div>
156
+
157
+ <div class="grid-2">
158
+ <div class="card"><div class="chart-title">Adoption Funnel by Segment (% of each segment)</div><div class="chart-container" style="height:300px"><canvas id="chart-adoption-pct"></canvas></div></div>
159
+ <div class="card"><div class="chart-title">Adoption Funnel by Segment (Absolute Count)</div><div class="chart-container" style="height:300px"><canvas id="chart-adoption-abs"></canvas></div></div>
160
+ </div>
161
+
162
+ <div class="grid-2" style="margin-top:24px">
163
+ <div class="card"><div class="chart-title">Growth Signals: Who Is Accelerating?</div><div class="chart-container" style="height:300px"><canvas id="chart-growth-signals"></canvas></div></div>
164
+ <div class="card"><div class="chart-title">Pfizer TRx: 86-Week Average vs. Recent 8 Weeks</div><div class="chart-container" style="height:300px"><canvas id="chart-trend-bars"></canvas></div></div>
165
+ </div>
166
+ </div>
167
+
168
+ <!-- ==================== TAB 4: COMPETITIVE INTELLIGENCE ==================== -->
169
+ <div id="tab-competitive" class="tab-content">
170
+ <div class="section-header"><div class="section-icon"><i class="fas fa-chess"></i></div><div><div class="section-title">Competitive Intelligence</div><div class="section-subtitle">Pfizer vs Brand2 market dynamics across segments</div></div></div>
171
+
172
+ <div class="grid-3" style="margin-bottom:24px">
173
+ <div class="card metric-highlight"><div class="metric-highlight-value" style="color:var(--seg-a)">3.90×</div><div class="metric-highlight-label">Brand2/Pfizer — SEG_A</div></div>
174
+ <div class="card metric-highlight"><div class="metric-highlight-value" style="color:var(--seg-b)">4.43×</div><div class="metric-highlight-label">Brand2/Pfizer — SEG_B</div></div>
175
+ <div class="card metric-highlight"><div class="metric-highlight-value" style="color:var(--seg-c)">4.29×</div><div class="metric-highlight-label">Brand2/Pfizer — SEG_C</div></div>
176
+ </div>
177
+
178
+ <div class="alert-box alert-warning"><i class="fas fa-exclamation-triangle"></i><span><strong>Competitive Pressure:</strong> Brand2 outprescribes Pfizer by 3.9–4.4× across all segments. The gap is widest in SEG_B (4.43×), the very segment with the highest Pfizer growth trajectory — indicating an active battleground for share.</span></div>
179
+
180
+ <div class="grid-2" style="margin-top:24px">
181
+ <div class="card"><div class="chart-title">Pfizer vs Brand2: Market Share of UC (%) by Segment</div><div class="chart-container" style="height:300px"><canvas id="chart-comp-share"></canvas></div></div>
182
+ <div class="card"><div class="chart-title">Brand2/Pfizer Prescribing Ratio by Segment</div><div class="chart-container" style="height:300px"><canvas id="chart-comp-ratio"></canvas></div></div>
183
+ </div>
184
+
185
+ <div class="card" style="margin-top:24px"><div class="chart-title">UC TRx Volume vs Pfizer Market Share (Labeled HCPs)</div><div class="chart-container" style="height:420px"><canvas id="chart-scatter-uc"></canvas></div></div>
186
+ </div>
187
+
188
+ <!-- ==================== TAB 5: REP ENGAGEMENT ==================== -->
189
+ <div id="tab-engagement" class="tab-content">
190
+ <div class="section-header"><div class="section-icon"><i class="fas fa-handshake"></i></div><div><div class="section-title">Rep Engagement ROI</div><div class="section-subtitle">Measuring commercial efficiency: visits, channels, and prescribing impact</div></div></div>
191
+
192
+ <div class="grid-4" style="margin-bottom:24px">
193
+ <div class="card metric-highlight"><div class="metric-highlight-value" style="color:var(--seg-a)">0.94</div><div class="metric-highlight-label">Details/Rx — SEG_A</div></div>
194
+ <div class="card metric-highlight"><div class="metric-highlight-value" style="color:var(--seg-b)">0.44</div><div class="metric-highlight-label">Details/Rx — SEG_B</div></div>
195
+ <div class="card metric-highlight"><div class="metric-highlight-value" style="color:var(--seg-c)">0.38</div><div class="metric-highlight-label">Details/Rx — SEG_C</div></div>
196
+ <div class="card metric-highlight"><div class="metric-highlight-value" style="color:var(--accent-green)">2.1×</div><div class="metric-highlight-label">SEG_B is 2.1× more efficient than SEG_A</div></div>
197
+ </div>
198
+
199
+ <div class="grid-2">
200
+ <div class="card"><div class="chart-title">Engagement Metrics by Segment</div><div class="chart-container" style="height:300px"><canvas id="chart-engagement"></canvas></div></div>
201
+ <div class="card"><div class="chart-title">Rep Visits vs Pfizer Prescribing (Scatter)</div><div class="chart-container" style="height:300px"><canvas id="chart-scatter-eng"></canvas></div></div>
202
+ </div>
203
+ </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 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
+
222
+ <!-- HCP Detail Panel (populated on click) -->
223
+ <div id="hcp-detail-panel" class="card" style="margin-top:24px;display:none;border-left:4px solid var(--accent-coral)">
224
+ <div class="section-header" style="margin-bottom:16px"><div class="section-icon" style="background:#fef2f2;color:var(--accent-coral)"><i class="fas fa-user-md"></i></div><div><div class="section-title" id="hcp-detail-title">HCP Selected</div><div class="section-subtitle">Zero rep visits — high opportunity for outreach</div></div></div>
225
+ <div class="grid-5" id="hcp-detail-grid"></div>
226
+ <div class="alert-box alert-warning" style="margin-top:16px"><i class="fas fa-bullhorn"></i><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></div>
227
+ </div>
228
+ </div>
229
+
230
+ <!-- ==================== TAB 7: SPECIALTY MIX ==================== -->
231
+ <div id="tab-specialty" class="tab-content">
232
+ <div class="section-header"><div class="section-icon"><i class="fas fa-stethoscope"></i></div><div><div class="section-title">Specialty & Demographics</div><div class="section-subtitle">HCP specialty distribution across segments</div></div></div>
233
+
234
+ <div class="grid-2">
235
+ <div class="card"><div class="chart-title">HCPs by Specialty and Segment (Stacked)</div><div class="chart-container" style="height:300px"><canvas id="chart-spec-stack"></canvas></div></div>
236
+ <div class="card"><div class="chart-title">Specialty Composition (% within each specialty)</div><div class="chart-container" style="height:300px"><canvas id="chart-spec-pct"></canvas></div></div>
237
+ </div>
238
+ </div>
239
+
240
+
241
+
242
+ <script src="app.js"></script>
243
+ </body>
244
+ </html>
opportunity_data.json ADDED
The diff for this file is too large to render. See raw diff
 
styles.css ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ============================================================
2
+ HCP Segmentation Dashboard V2 — Premium Business Theme
3
+ ============================================================ */
4
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap');
5
+
6
+ :root {
7
+ --pfizer-deep: #0d009d;
8
+ --pfizer-blue: #0051a5;
9
+ --pfizer-light: #00a3e0;
10
+ --pfizer-sky: #54c8e8;
11
+
12
+ --seg-a: #6B7280;
13
+ --seg-b: #1A6FD4;
14
+ --seg-c: #D4720A;
15
+ --seg-unlabeled: #7C3AED;
16
+
17
+ --bg-body: #f0f2f5;
18
+ --bg-card: #ffffff;
19
+ --bg-active: #eef6fc;
20
+
21
+ --text-primary: #1e293b;
22
+ --text-secondary: #64748b;
23
+ --text-muted: #94a3b8;
24
+
25
+ --border-subtle: #e2e8f0;
26
+ --border-card: #e5e7eb;
27
+
28
+ --accent-green: #0D9E6E;
29
+ --accent-amber: #d97706;
30
+ --accent-coral: #DC3545;
31
+
32
+ --shadow-card: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04);
33
+ --shadow-card-hover: 0 8px 24px rgba(0,0,0,0.08);
34
+ --shadow-header: 0 1px 8px rgba(0,0,0,0.04);
35
+ --radius-sm: 8px;
36
+ --radius-md: 12px;
37
+ --radius-lg: 16px;
38
+ --radius-xl: 20px;
39
+ --transition-base: 0.25s cubic-bezier(0.4,0,0.2,1);
40
+ }
41
+
42
+ *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
43
+ html{scroll-behavior:smooth}
44
+ body{font-family:'Inter',sans-serif;background:var(--bg-body);color:var(--text-primary);line-height:1.6;min-height:100vh}
45
+
46
+ /* ---- Top Header ---- */
47
+ .top-header{background:var(--bg-card);border-bottom:1px solid var(--border-subtle);padding:14px 32px;display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;z-index:100;box-shadow:var(--shadow-header)}
48
+ .top-header-left{display:flex;align-items:center;gap:20px}
49
+ .top-header-left img{height:32px;width:auto}
50
+ .top-header-left h1{font-size:18px;font-weight:700;color:var(--pfizer-deep);border-left:1px solid var(--border-subtle);padding-left:20px}
51
+ .top-header-left span{font-size:13px;color:var(--text-secondary);margin-left:8px}
52
+ .header-badge{padding:6px 16px;border-radius:20px;font-size:12px;font-weight:600;background:#ecfdf5;color:var(--accent-green);border:1px solid #a7f3d0}
53
+
54
+ /* ---- Tab Navigation ---- */
55
+ .tab-nav{background:var(--bg-card);border-bottom:1px solid var(--border-subtle);padding:0 32px;display:flex;gap:0;overflow-x:auto;-webkit-overflow-scrolling:touch}
56
+ .tab-nav::-webkit-scrollbar{height:0}
57
+ .tab-btn{padding:14px 20px;font-size:13px;font-weight:600;color:var(--text-secondary);cursor:pointer;border:none;background:none;border-bottom:2px solid transparent;transition:var(--transition-base);white-space:nowrap;display:flex;align-items:center;gap:8px}
58
+ .tab-btn:hover{color:var(--pfizer-blue);background:var(--bg-active)}
59
+ .tab-btn.active{color:var(--pfizer-blue);border-bottom-color:var(--pfizer-blue);background:var(--bg-active)}
60
+
61
+ /* ---- Tab Content ---- */
62
+ .tab-content{display:none;padding:32px;max-width:1440px;margin:0 auto;animation:fadeIn 0.35s ease}
63
+ .tab-content.active{display:block}
64
+ @keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
65
+
66
+ /* ---- Section ---- */
67
+ .section-header{display:flex;align-items:center;gap:16px;margin-bottom:24px}
68
+ .section-icon{width:40px;height:40px;border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;font-size:16px;background:rgba(0,81,165,0.08);color:var(--pfizer-blue)}
69
+ .section-title{font-size:20px;font-weight:700;color:var(--text-primary);letter-spacing:-0.3px}
70
+ .section-subtitle{font-size:14px;color:var(--text-secondary);margin-top:2px}
71
+
72
+ /* ---- Grids ---- */
73
+ .grid-2{display:grid;grid-template-columns:repeat(2,1fr);gap:24px}
74
+ .grid-3{display:grid;grid-template-columns:repeat(3,1fr);gap:24px}
75
+ .grid-4{display:grid;grid-template-columns:repeat(4,1fr);gap:20px}
76
+ .grid-5{display:grid;grid-template-columns:repeat(5,1fr);gap:20px}
77
+ .grid-2-1{display:grid;grid-template-columns:2fr 1fr;gap:24px}
78
+
79
+ /* ---- Card ---- */
80
+ .card{background:var(--bg-card);border:1px solid var(--border-card);border-radius:var(--radius-lg);padding:24px;box-shadow:var(--shadow-card);transition:var(--transition-base)}
81
+ .card:hover{box-shadow:var(--shadow-card-hover)}
82
+
83
+ /* ---- KPI Card ---- */
84
+ .kpi-card{display:flex;flex-direction:column;gap:8px}
85
+ .kpi-top{display:flex;align-items:center;justify-content:space-between}
86
+ .kpi-label{font-size:11px;color:var(--text-secondary);font-weight:600;text-transform:uppercase;letter-spacing:0.5px}
87
+ .kpi-icon{width:36px;height:36px;border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;font-size:14px}
88
+ .kpi-value{font-size:32px;font-weight:800;letter-spacing:-1px;color:var(--pfizer-deep);line-height:1.2}
89
+ .kpi-sub{font-size:13px;color:var(--text-muted);font-weight:500}
90
+
91
+ /* ---- Segment Card ---- */
92
+ .segment-card{position:relative;overflow:hidden}
93
+ .segment-card::before{content:'';position:absolute;top:0;left:0;width:4px;height:100%;border-radius:4px 0 0 4px}
94
+ .seg-a .segment-card::before,.seg-a.segment-card::before{background:var(--seg-a)}
95
+ .seg-b .segment-card::before,.seg-b.segment-card::before{background:var(--seg-b)}
96
+ .seg-c .segment-card::before,.seg-c.segment-card::before{background:var(--seg-c)}
97
+ .segment-name{font-size:18px;font-weight:700;margin-bottom:6px}
98
+ .segment-desc{font-size:14px;color:var(--text-secondary);line-height:1.6;margin-bottom:20px;min-height:66px}
99
+ .segment-stats{display:flex;flex-direction:column;gap:12px}
100
+ .segment-stat{display:flex;justify-content:space-between;align-items:center}
101
+ .segment-stat-label{font-size:13px;color:var(--text-secondary);font-weight:500}
102
+ .segment-stat-value{font-size:14px;font-weight:700}
103
+ .segment-meter{width:100%;height:6px;background:#f1f5f9;border-radius:3px;margin-top:4px;overflow:hidden}
104
+ .segment-meter-fill{height:100%;border-radius:3px;transition:width 1s ease}
105
+
106
+ /* ---- Plotly Container ---- */
107
+ .plotly-chart{width:100%;min-height:340px;border-radius:var(--radius-md);overflow:hidden}
108
+ .plotly-chart-sm{width:100%;min-height:280px}
109
+ .chart-title{font-size:16px;font-weight:700;margin-bottom:16px;color:var(--text-primary);letter-spacing:-0.2px}
110
+ .chart-container{position:relative;width:100%}
111
+ .chart-container canvas{width:100%!important}
112
+
113
+ /* ---- Persona ---- */
114
+ .persona-card{padding:32px}
115
+ .persona-header{display:flex;align-items:center;gap:20px;margin-bottom:24px}
116
+ .persona-avatar{width:64px;height:64px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:22px;font-weight:700;color:#fff;flex-shrink:0}
117
+ .persona-name{font-size:20px;font-weight:700;color:var(--text-primary)}
118
+ .persona-role{font-size:14px;color:var(--text-secondary);margin-top:2px}
119
+ .persona-seg{font-size:13px;font-weight:600;margin-top:4px}
120
+ .persona-detail-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-top:24px}
121
+ .persona-metric{background:#f8fafc;padding:16px;border-radius:var(--radius-md);border:1px solid #f1f5f9}
122
+ .persona-metric-label{font-size:12px;color:var(--text-muted);text-transform:uppercase;font-weight:600;letter-spacing:0.5px}
123
+ .persona-metric-value{font-size:22px;font-weight:800;color:var(--pfizer-deep);margin-top:4px;letter-spacing:-0.5px}
124
+
125
+ /* ---- Badge ---- */
126
+ .badge{display:inline-flex;align-items:center;gap:6px;padding:4px 12px;border-radius:20px;font-size:12px;font-weight:600}
127
+ .badge-green{background:#ecfdf5;color:var(--accent-green)}
128
+ .badge-amber{background:#fffbeb;color:var(--accent-amber)}
129
+ .badge-red{background:#fef2f2;color:var(--accent-coral)}
130
+ .badge-blue{background:#eff6ff;color:var(--pfizer-blue)}
131
+ .badge-sky{background:#f0f9ff;color:var(--pfizer-light)}
132
+ .badge-purple{background:#f5f3ff;color:var(--seg-unlabeled)}
133
+
134
+ /* ---- Alert ---- */
135
+ .alert-box{padding:16px 20px;border-radius:var(--radius-md);font-size:14px;line-height:1.6;display:flex;align-items:flex-start;gap:12px;margin-top:20px}
136
+ .alert-box i{margin-top:3px;font-size:16px}
137
+ .alert-info{background:#eff6ff;border:1px solid #bfdbfe;color:var(--pfizer-blue)}
138
+ .alert-warning{background:#fffbeb;border:1px solid #fde68a;color:var(--accent-amber)}
139
+ .alert-success{background:#ecfdf5;border:1px solid #a7f3d0;color:var(--accent-green)}
140
+
141
+ /* ---- Insight Card ---- */
142
+ .insight-card{border-left:4px solid var(--pfizer-light)}
143
+ .insight-number{font-size:40px;font-weight:900;color:var(--pfizer-sky);opacity:0.25;line-height:1;margin-bottom:8px}
144
+ .insight-title{font-size:16px;font-weight:700;margin-bottom:8px;color:var(--text-primary)}
145
+ .insight-text{font-size:14px;color:var(--text-secondary);line-height:1.6}
146
+
147
+ /* ---- Opportunity Tier Cards ---- */
148
+ .tier-card{border-left:4px solid transparent;padding:20px 24px}
149
+ .tier-card.tier-1{border-left-color:var(--accent-green)}
150
+ .tier-card.tier-2{border-left-color:var(--accent-amber)}
151
+ .tier-card.tier-3{border-left-color:var(--text-muted)}
152
+ .tier-value{font-size:28px;font-weight:800;letter-spacing:-0.5px;line-height:1.2}
153
+ .tier-label{font-size:13px;color:var(--text-secondary);font-weight:600;margin-top:2px}
154
+ .tier-desc{font-size:12px;color:var(--text-muted);margin-top:6px}
155
+
156
+ /* ---- Metric Highlight ---- */
157
+ .metric-highlight{text-align:center;padding:20px;background:#f8fafc;border-radius:var(--radius-md);border:1px solid #f1f5f9}
158
+ .metric-highlight-value{font-size:28px;font-weight:800;color:var(--pfizer-deep);letter-spacing:-0.5px}
159
+ .metric-highlight-label{font-size:12px;color:var(--text-muted);font-weight:600;text-transform:uppercase;letter-spacing:0.5px;margin-top:4px}
160
+
161
+ /* ---- Sub-tabs ---- */
162
+ .sub-tabs{display:flex;gap:12px;margin-bottom:24px;flex-wrap:wrap}
163
+ .sub-tab{padding:10px 24px;border-radius:24px;font-size:14px;font-weight:600;cursor:pointer;border:1px solid var(--border-card);background:var(--bg-card);color:var(--text-secondary);transition:var(--transition-base)}
164
+ .sub-tab:hover{border-color:var(--pfizer-blue);color:var(--pfizer-blue)}
165
+ .sub-tab.active{background:var(--pfizer-deep);color:#fff;border-color:var(--pfizer-deep)}
166
+ .sub-panel{display:none}
167
+ .sub-panel.active{display:block;animation:fadeIn 0.3s ease}
168
+
169
+ /* ---- Data Table ---- */
170
+ .data-table{width:100%;border-collapse:collapse;font-size:14px}
171
+ .data-table thead th{text-align:left;padding:12px 16px;font-weight:600;color:var(--text-secondary);font-size:12px;text-transform:uppercase;letter-spacing:0.5px;border-bottom:2px solid var(--border-subtle);background:#f8fafc}
172
+ .data-table tbody td{padding:12px 16px;border-bottom:1px solid var(--border-subtle)}
173
+ .data-table tbody tr:hover{background:#f8fafc}
174
+
175
+ /* ---- Coverage Gap Badge ---- */
176
+ .coverage-gap{display:inline-flex;align-items:center;gap:6px;padding:6px 14px;border-radius:var(--radius-sm);font-size:13px;font-weight:600;background:#fef2f2;color:var(--accent-coral);border:1px solid #fecaca}
177
+
178
+ /* ---- Responsive ---- */
179
+ @media(max-width:1200px){.grid-5{grid-template-columns:repeat(3,1fr)}.grid-4{grid-template-columns:repeat(2,1fr)}}
180
+ @media(max-width:900px){.grid-3,.grid-2,.grid-2-1{grid-template-columns:1fr}}
181
+ @media(max-width:600px){.grid-5,.grid-4{grid-template-columns:1fr}.tab-btn{padding:12px 14px;font-size:12px}}