PageSourceSearch

https://aequalishealth.ai/static/js/compare.js

js aequalishealth.ai collected 2026-10-05 15:08:23 UTC 24,970 bytes, 428 lines download raw bytes

1// ============================================================================
2// compare.js - Payer comparison tab functions
3// Phase 6D extraction from index.html
4// ============================================================================
5
6
7        // ============================================================
8        // PAYER COMPARISON FUNCTIONS
9        // ============================================================
10        
11        let currentComparisonCode = null;
12        let currentComparisonPage = 1;
13        let comparisonTotalPages = 1;
14        let comparisonAutocompleteTimeout = null;
15        
16        // Update payer display badges in comparison tab
17        function updateComparisonPayersDisplay() {
18            const container = document.getElementById('comparisonPayersList');
19            const selectedPayers = window.selectedPayerIds || [];
20            
21            if (!container) return;
22            
23            if (selectedPayers.length === 0) {
24                container.innerHTML = '<span style="color: #999;">No payers selected</span>';
25                return;
26            }
27            
28            // Use payer names from allDatabases if available
29            const payerNames = selectedPayers.map(id => {
30                if (window.allDatabases) {
31                    const db = window.allDatabases.find(d => d.id === id);
32                    return db ? db.display_name || db.name : id;
33                }
34                return id;
35            });
36            
37            container.innerHTML = payerNames.map((name, idx) => {
38                const colors = ['#5b6aa0', '#ff6b6b', '#2ecc71', '#f1c40f', '#9b59b6'];
39                const color = colors[idx % colors.length];
40                return `<span style="background: ${color}; color: white; padding: 6px 12px; border-radius: 20px; font-weight: 500;">${name}</span>`;
41            }).join('');
42        }
43        
44        // Handle autocomplete for comparison search
45        async function handleComparisonAutocomplete(query) {
46            if (comparisonAutocompleteTimeout) {
47                clearTimeout(comparisonAutocompleteTimeout);
48            }
49            
50            const container = document.getElementById('comparisonAutocomplete');
51            if (!container) return;
52            
53            if (query.length < 2) {
54                container.style.display = 'none';
55                return;
56            }
57            
58            comparisonAutocompleteTimeout = setTimeout(async () => {
59                try {
60                    const selectedPayers = window.selectedPayerIds || [];
61                    if (selectedPayers.length < 2) {
62                        container.innerHTML = '<div style="padding: 15px; color: #ff6b6b;">Please select at least 2 payers for comparison</div>';
63                        container.style.display = 'block';
64                        return;
65                    }
66                    
67                    const dbIds = selectedPayers.join(',');
68                    const response = await apiRequest(`/api/compare/search?q=${encodeURIComponent(query)}&db_ids=${dbIds}&limit=10`);
69                    
70                    if (response.results && response.results.length > 0) {
71                        container.innerHTML = response.results.map(r => `
72                            <div onclick="selectComparisonSuggestion('${r.billing_code}')" style="padding: 12px 15px; cursor: pointer; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center;" onmouseover="this.style.background='#f8f9fa'" onmouseout="this.style.background='white'">
73                                <div>
74                                    <strong style="color: #333;">${r.billing_code}</strong>
75                                    <span style="color: #666; margin-left: 10px;">${r.description || 'No description'}</span>
76                                </div>
77                                <span style="background: #e3f2fd; color: #1976d2; padding: 2px 8px; border-radius: 4px; font-size: 0.8em;">${r.payer_count} payer${r.payer_count > 1 ? 's' : ''}</span>
78                            </div>
79                        `).join('');
80                        container.style.display = 'block';
81                    } else {
82                        container.innerHTML = '<div style="padding: 15px; color: #999;">No matching services found</div>';
83                        container.style.display = 'block';
84                    }
85                } catch (err) {
86                    console.error('Autocomplete error:', err);
87                    container.style.display = 'none';
88                }
89            }, 300);
90        }
91        
92        // Select suggestion from autocomplete
93        function selectComparisonSuggestion(billingCode) {
94            document.getElementById('comparisonSearchInput').value = billingCode;
95            document.getElementById('comparisonAutocomplete').style.display = 'none';
96            searchForComparison();
97        }
98        
99        // Main comparison search function
100        async function searchForComparison() {
101            const searchInput = document.getElementById('comparisonSearchInput');
102            const query = searchInput.value.trim();
103            
104            if (!query) {
105                showNotification('Please enter a billing code or service name', 'warning');
106                return;
107            }
108            
109            const selectedPayers = window.selectedPayerIds || [];
110            if (selectedPayers.length < 2) {
111                showNotification('Please select at least 2 payers for comparison', 'warning');
112                return;
113            }
114            
115            // Hide autocomplete
116            document.getElementById('comparisonAutocomplete').style.display = 'none';
117            
118            // Show loading state
119            const statsContainer = document.getElementById('comparisonStatsContainer');
120            statsContainer.innerHTML = '<div style="text-align: center; padding: 40px;"><div class="loading-spinner"></div><p style="margin-top: 15px; color: #666;">Loading comparison data...</p></div>';
121            
122            document.getElementById('comparisonEmptyState').style.display = 'none';
123            document.getElementById('comparisonProvidersSection').style.display = 'none';
124            
125            try {
126                const dbIds = selectedPayers.join(',');
127                const response = await apiRequest(`/api/compare/billing-code/${encodeURIComponent(query)}?db_ids=${dbIds}`);
128                
129                if (response.billing_code) {
130                    currentComparisonCode = response.billing_code;
131                    displayComparisonStats(response);
132                    
133                    // Load providers
134                    await loadComparisonProviders(response.billing_code, 1);
135                } else {
136                    statsContainer.innerHTML = `
137                        <div style="background: white; border-radius: 12px; padding: 40px; text-align: center; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
138                            <div style="font-size: 3em; margin-bottom: 15px;"><i data-lucide=search></i></div>
139                            <h3 style="color: #333; margin-bottom: 10px;">No Data Found</h3>
140                            <p style="color: #666;">No pricing data found for billing code "${query}" across your selected payers.</p>
141                        </div>
142                    `;
143                }
144            } catch (err) {
145                console.error('Comparison search error:', err);
146                statsContainer.innerHTML = `
147                    <div style="background: #fee; border-radius: 12px; padding: 20px; color: #c00; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
148                        <strong>Error loading comparison data:</strong> ${err.message}
149                    </div>
150                `;
151            }
152        }
153        
154        // Display comparison stats cards
155        function displayComparisonStats(data) {
156            const container = document.getElementById('comparisonStatsContainer');
157            
158            if (!data.payer_stats || data.payer_stats.length === 0) {
159                container.innerHTML = '<div style="padding: 20px; text-align: center; color: #999;">No statistics available</div>';
160                return;
161            }
162            
163            // Calculate cross-payer stats
164            const allRates = data.payer_stats.flatMap(p => [p.min_rate, p.max_rate]).filter(r => r !== null && r !== undefined);
165            const overallMin = Math.min(...allRates);
166            const overallMax = Math.max(...allRates);
167            const priceDiff = overallMax - overallMin;
168            const priceDiffPercent = overallMin > 0 ? ((priceDiff / overallMin) * 100).toFixed(1) : 0;
169            
170            const colors = ['#5b6aa0', '#ff6b6b', '#2ecc71', '#f1c40f', '#9b59b6'];
171            
172            let html = `
173                <div style="background: white; border-radius: 12px; padding: 25px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); margin-bottom: 20px;">
174                    <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; flex-wrap: wrap; gap: 15px;">
175                        <div>
176                            <h3 style="margin: 0 0 5px 0; color: #333;"><i data-lucide=bar-chart-3></i> ${data.billing_code}</h3>
177                            <p style="margin: 0; color: #666;">${data.description || 'Healthcare Service'}</p>
178                        </div>
179                        <div style="text-align: right;">
180                            <div style="font-size: 0.9em; color: #666;">Price Variance</div>
181                            <div style="font-size: 1.8em; font-weight: 700; color: ${priceDiffPercent > 50 ? '#ff6b6b' : '#2ecc71'};">
182                                ${priceDiffPercent}%
183                            </div>
184                        </div>
185                    </div>
186                    
187                    <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px;">
188            `;
189            
190            data.payer_stats.forEach((payer, idx) => {
191                const color = colors[idx % colors.length];
192                const isLowest = payer.min_rate === overallMin;
193                const isHighest = payer.max_rate === overallMax;
194                
195                html += `
196                    <div style="background: linear-gradient(135deg, ${color}22 0%, ${color}11 100%); border-left: 4px solid ${color}; border-radius: 8px; padding: 20px;">
197                        <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
198                            <span style="font-weight: 700; color: #333; font-size: 1.1em;">${payer.payer_name}</span>
199                            ${isLowest ? '<span style="background: #2ecc71; color: white; padding: 2px 8px; border-radius: 4px; font-size: 0.75em;">LOWEST</span>' : ''}
200                            ${isHighest ? '<span style="background: #ff6b6b; color: white; padding: 2px 8px; border-radius: 4px; font-size: 0.75em;">HIGHEST</span>' : ''}
201                        </div>
202                        <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
203                            <div>
204                                <div style="font-size: 0.8em; color: #666; margin-bottom: 3px;">Min Rate</div>
205                                <div style="font-size: 1.3em; font-weight: 600; color: #2ecc71;">$${payer.min_rate?.toFixed(2) || 'N/A'}</div>
206                            </div>
207                            <div>
208                                <div style="font-size: 0.8em; color: #666; margin-bottom: 3px;">Max Rate</div>
209                                <div style="font-size: 1.3em; font-weight: 600; color: #ff6b6b;">$${payer.max_rate?.toFixed(2) || 'N/A'}</div>
210                            </div>
211                            <div>
212                                <div style="font-size: 0.8em; color: #666; margin-bottom: 3px;">Average</div>
213                                <div style="font-size: 1.3em; font-weight: 600; color: #333;">$${payer.avg_rate?.toFixed(2) || 'N/A'}</div>
214                            </div>
215                            <div>
216                                <div style="font-size: 0.8em; color: #666; margin-bottom: 3px;">Providers</div>
217                                <div style="font-size: 1.3em; font-weight: 600; color: #333;">${payer.provider_count?.toLocaleString() || 0}</div>
218                            </div>
219                        </div>
220                    </div>
221                `;
222            });
223            
224            html += `
225                    </div>
226                    
227                    <!-- Summary Bar -->
228                    <div style="margin-top: 20px; padding-top: 20px; border-top: 1px solid #eee;">
229                        <div style="display: flex; justify-content: space-around; text-align: center; flex-wrap: wrap; gap: 15px;">
230                            <div>
231                                <div style="font-size: 0.9em; color: #666;">Lowest Price</div>
232                                <div style="font-size: 1.4em; font-weight: 700; color: #2ecc71;">$${overallMin.toFixed(2)}</div>
233                            </div>
234                            <div>
235                                <div style="font-size: 0.9em; color: #666;">Highest Price</div>
236                                <div style="font-size: 1.4em; font-weight: 700; color: #ff6b6b;">$${overallMax.toFixed(2)}</div>
237                            </div>
238                            <div>
239                                <div style="font-size: 0.9em; color: #666;">Price Difference</div>
240                                <div style="font-size: 1.4em; font-weight: 700; color: #333;">$${priceDiff.toFixed(2)}</div>
241                            </div>
242                            <div>
243                                <div style="font-size: 0.9em; color: #666;">Payers Compared</div>
244                                <div style="font-size: 1.4em; font-weight: 700; color: #333;">${data.payer_stats.length}</div>
245                            </div>
246                        </div>
247                    </div>
248                </div>
249            `;
250            
251            container.innerHTML = html;
252        }
253        
254        // Load comparison providers with pagination
255        async function loadComparisonProviders(billingCode, page = 1) {
256            const section = document.getElementById('comparisonProvidersSection');
257            const tableContainer = document.getElementById('comparisonProvidersTable');
258            
259            section.style.display = 'block';
260            tableContainer.innerHTML = '<div style="text-align: center; padding: 40px;"><div class="loading-spinner"></div><p style="margin-top: 15px; color: #666;">Loading provider comparison...</p></div>';
261            
262            const selectedPayers = window.selectedPayerIds || [];
263            const dbIds = selectedPayers.join(',');
264            const sortBy = document.getElementById('comparisonSortBy')?.value || 'n
264ame';
265            const sortOrder = document.getElementById('comparisonSortOrder')?.value || 'asc';
266            
267            try {
268                const response = await apiRequest(`/api/compare/billing-code/${encodeURIComponent(billingCode)}/providers?db_ids=${dbIds}&page=${page}&per_page=20&sort_by=${sortBy}&sort_order=${sortOrder}`);
269                
270                currentComparisonPage = response.page || 1;
271                comparisonTotalPages = response.total_pages || 1;
272                
273                displayComparisonProviders(response);
274                renderComparisonPagination();
275                
276            } catch (err) {
277                console.error('Error loading comparison providers:', err);
278                tableContainer.innerHTML = `
279                    <div style="padding: 20px; color: #c00; text-align: center;">
280                        Error loading providers: ${err.message}
281                    </div>
282                `;
283            }
284        }
285        
286        // Display provider comparison table
287        function displayComparisonProviders(data) {
288            const tableContainer = document.getElementById('comparisonProvidersTable');
289            const selectedPayers = window.selectedPayerIds || [];
290            
291            if (!data.providers || data.providers.length === 0) {
292                tableContainer.innerHTML = '<div style="padding: 40px; text-align: center; color: #999;">No provider data available for comparison</div>';
293                return;
294            }
295            
296            // Get payer names for headers
297            const payerNames = selectedPayers.map(id => {
298                if (window.allDatabases) {
299                    const db = window.allDatabases.find(d => d.id === id);
300                    return db ? db.display_name || db.name : id;
301                }
302                return id;
303            });
304            
305            const colors = ['#5b6aa0', '#ff6b6b', '#2ecc71', '#f1c40f', '#9b59b6'];
306            
307            let html = `
308                <table style="width: 100%; border-collapse: collapse;">
309                    <thead>
310                        <tr style="background: #f8f9fa;">
311                            <th style="padding: 12px; text-align: left; border-bottom: 2px solid #eee; font-weight: 600;">Provider / NPI</th>
312                            <th style="padding: 12px; text-align: left; border-bottom: 2px solid #eee; font-weight: 600;">Location</th>
313                            ${payerNames.map((name, idx) => `
314                                <th style="padding: 12px; text-align: center; border-bottom: 2px solid #eee; font-weight: 600; background: ${colors[idx % colors.length]}22;">${name}</th>
315                            `).join('')}
316                            <th style="padding: 12px; text-align: center; border-bottom: 2px solid #eee; font-weight: 600;">Variance</th>
317                        </tr>
318                    </thead>
319                    <tbody>
320            `;
321            
322            data.providers.forEach((provider, rowIdx) => {
323                const rates = selectedPayers.map(payerId => provider.rates[payerId]);
324                const validRates = rates.filter(r => r !== null && r !== undefined && r > 0);
325                const minRate = validRates.length > 0 ? Math.min(...validRates) : null;
326                const maxRate = validRates.length > 0 ? Math.max(...validRates) : null;
327                const variance = minRate && maxRate ? ((maxRate - minRate) / minRate * 100).toFixed(1) : null;
328                
329                html += `
330                    <tr style="border-bottom: 1px solid #eee;${rowIdx % 2 === 0 ? '' : ' background: #fafafa;'}">
331                        <td style="padding: 12px;">
332                            <div style="font-weight: 600; color: #333;">${provider.provider_name || 'Unknown Provider'}</div>
333                            <div style="font-size: 0.85em; color: #666;">
334                                ${provider.npi ? `NPI: ${provider.npi}` : ''}
335                                ${provider.tin ? ` | TIN: ${provider.tin}` : ''}
336                            </div>
337                        </td>
338                        <td style="padding: 12px; color: #666; font-size: 0.9em;">
339                            ${provider.city && provider.state ? `${provider.city}, ${provider.state}` : provider.city || provider.state || '-'}
340                        </td>
341                        ${selectedPayers.map((payerId, idx) => {
342                            const rate = provider.rates[payerId];
343                            const isLowest = rate === minRate && validRates.length > 1;
344                            const isHighest = rate === maxRate && validRates.length > 1;
345                            const color = colors[idx % colors.length];
346                            
347                            if (rate === null || rate === undefined) {
348                                return `<td style="padding: 12px; text-align: center; background: ${color}11; color: #999;">-</td>`;
349                            }
350                            
351                            return `
352                                <td style="padding: 12px; text-align: center; background: ${color}11; font-weight: 600; ${isLowest ? 'color: #2ecc71;' : isHighest ? 'color: #ff6b6b;' : 'color: #333;'}">
353                                    $${rate.toFixed(2)}
354                                    ${isLowest ? ' <i data-lucide=check></i>' : ''}${isHighest ? ' <i data-lucide=arrow-up></i>' : ''}
355                                </td>
356                            `;
357                        }).join('')}
358                        <td style="padding: 12px; text-align: center;">
359                            ${variance !== null ? `
360                                <span style="font-weight: 600; color: ${parseFloat(variance) > 50 ? '#ff6b6b' : parseFloat(variance) > 20 ? '#f39c12' : '#2ecc71'};">
361                                    ${variance}%
362                                </span>
363                            ` : '-'}
364                        </td>
365                    </tr>
366                `;
367            });
368            
369            html += `
370                    </tbody>
371                </table>
372            `;
373            
374            tableContainer.innerHTML = html;
375        }
376        
377        // Render pagination for comparison providers
378        function renderComparisonPagination() {
379            const topPagination = document.getElementById('comparisonPaginationTop');
380            const bottomPagination = document.getElementById('comparisonPaginationBottom');
381            
382            const paginationHTML = `
383                <div style="color: #666; font-size: 0.9em;">
384                    Page ${currentComparisonPage} of ${comparisonTotalPages}
385                </div>
386                <div style="display: flex; gap: 10px;">
387                    <button 
388                        onclick="loadComparisonProviders(currentComparisonCode, 1)" 
389                        ${currentComparisonPage <= 1 ? 'disabled' : ''}
390                        style="padding: 8px 12px; border: 1px solid #ddd; background: white; border-radius: 6px; cursor: ${currentComparisonPage <= 1 ? 'not-allowed' : 'pointer'}; opacity: ${currentComparisonPage <= 1 ? '0.5' : '1'};">
391                        <i data-lucide=skip-back></i> First
392                    </button>
393                    <button 
394                        onclick="loadComparisonProviders(currentComparisonCode, ${currentComparisonPage - 1})" 
395                        ${currentComparisonPage <= 1 ? 'disabled' : ''}
396                        style="padding: 8px 12px; border: 1px solid #ddd; background: white; border-radius: 6px; cursor: ${currentComparisonPage <= 1 ? 'not-allowed' : 'pointer'}; opacity: ${currentComparisonPage <= 1 ? '0.5' : '1'};">
397                         Prev
398                    </button>
399                    <button 
400                        onclick="loadComparisonProviders(currentComparisonCode, ${currentComparisonPage + 1})" 
401                        ${currentComparisonPage >= comparisonTotalPages ? 'disabled' : ''}
402                        style="padding: 8px 12px; border: 1px solid #ddd; background: white; border-radius: 6px; cursor: ${currentComparisonPage >= comparisonTotalPages ? 'not-allowed' : 'pointer'}; opacity: ${currentComparisonPage >= comparisonTotalPages ? '0.5' : '1'};">
403                        Next 
404                    </button>
405                    <button 
406                        onclick="loadComparisonProviders(currentComparisonCode, ${comparisonTotalPages})" 
407                        ${currentComparisonPage >= comparisonTotalPages ? 'disabled' : ''}
408                        style="padding: 8px 12px; border: 1px solid #ddd; background: white; border-radius: 6px; cursor: ${currentComparisonPage >= comparisonTotalPages ? 'not-allowed' : 'pointer'}; opacity: ${currentComparisonPage >= comparisonTotalPages ? '0.5' : '1'};">
409                        Last <i data-lucide=skip-forward></i>
410                    </button>
411                </div>
412            `;
413            
414            if (topPagination) topPagination.innerHTML = paginationHTML;
415            if (bottomPagination) bottomPagination.innerHTML = paginationHTML;
416        }
417        
418        // Modal for payer selection (redirect to existing functionality)
419        function openPayerSelectorModal() {
420            // Trigger the existing payer selection modal or dropdown
421            const payerButton = document.querySelector('.payer-selector-btn') || document.querySelector('[oncl
421ick*="togglePayerDropdown"]');
422            if (payerButton) {
423                payerButton.click();
424            } else {
425                showNotification('Please use the payer selector in the header to change selections', 'info');
426            }
427        }
428        

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.