Spaces:
Running
Running
| async function runAnalysis() { | |
| const query = document.getElementById('query').value; | |
| const lastPrivate = document.getElementById('last-private').value; | |
| // NEW STRUCTURING INPUTS | |
| const ipoDiscount = document.getElementById('ipo-discount').value; | |
| const greenshoe = document.getElementById('greenshoe').checked; | |
| const primaryShares = document.getElementById('primary-shares').value; | |
| const loader = document.getElementById('loader'); | |
| const dashboard = document.getElementById('dashboard'); | |
| // UI State: Loading | |
| dashboard.style.display = 'none'; | |
| loader.style.display = 'block'; | |
| // Prepare form data | |
| const formData = new FormData(); | |
| formData.append('query', query); | |
| if (lastPrivate) formData.append('last_private', lastPrivate); | |
| // Append Structuring Levers | |
| formData.append('ipo_discount', ipoDiscount); | |
| formData.append('greenshoe', greenshoe); | |
| formData.append('primary_shares', primaryShares); | |
| // NEW: Capture Target Raise | |
| const targetRaise = document.getElementById('target-raise').value; | |
| formData.append('target_raise', targetRaise); | |
| try { | |
| const response = await fetch('/analyze', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| if (!response.ok) { | |
| const errorText = await response.text(); | |
| throw new Error(`Server Error (${response.status}): ${errorText}`); | |
| } | |
| const data = await response.json(); | |
| if (data.error) { | |
| alert("Analysis failed: " + data.error); | |
| return; | |
| } | |
| updateDashboard(data); | |
| // UI State: Done | |
| loader.style.display = 'none'; | |
| dashboard.style.display = 'flex'; // Flex layout | |
| } catch (e) { | |
| console.error(e); | |
| alert("System Error: " + e.message); | |
| loader.style.display = 'none'; | |
| } | |
| } | |
| function updateDashboard(data) { | |
| // 1. Executive Commentary | |
| if (data.advisory) { | |
| document.getElementById('exec-summary-content').innerHTML = data.advisory.commentary; | |
| document.getElementById('m-price').textContent = `$${data.advisory.low} - $${data.advisory.high}`; | |
| const statusEl = document.getElementById('m-status'); | |
| statusEl.textContent = data.advisory.sentiment; | |
| statusEl.style.color = data.advisory.color; | |
| // --- METRICS --- | |
| document.getElementById('m-momentum').textContent = (data.metrics.avg_momentum ? data.metrics.avg_momentum.toFixed(1) + "%" : "--"); | |
| document.getElementById('m-beta').textContent = (data.metrics.avg_beta ? data.metrics.avg_beta.toFixed(2) : "--"); | |
| document.getElementById('m-rule-40').textContent = (data.metrics.avg_rule_40 ? data.metrics.avg_rule_40.toFixed(0) : "--"); | |
| document.getElementById('m-vix').textContent = (data.macro ? data.macro.vix.toFixed(2) : "--"); | |
| // --- RISK CHECKLIST --- | |
| const riskBody = document.querySelector('#risk-table tbody'); | |
| if (riskBody && data.advisory.risk_matrix) { | |
| riskBody.innerHTML = ''; | |
| data.advisory.risk_matrix.forEach(r => { | |
| const row = ` | |
| <tr style="border-bottom: 1px solid rgba(255,255,255,0.05);"> | |
| <td style="padding:8px 0; color:#ccc;">${r.factor}</td> | |
| <td style="padding:8px 0;">${r.status}</td> | |
| <td style="padding:8px 0; color:var(--text-muted); font-style:italic;">${r.impact}</td> | |
| </tr> | |
| `; | |
| riskBody.innerHTML += row; | |
| }); | |
| } | |
| } | |
| // 2. IPO STRUCTURING (The Pro Layer) | |
| if (data.structure) { | |
| document.getElementById('s-final-price').textContent = `$${data.structure.final_price.toFixed(2)}`; | |
| document.getElementById('s-raise').textContent = `$${data.structure.capital_raised.toFixed(0)}M`; | |
| // Down-Round Alert | |
| const drAlert = document.getElementById('dr-alert'); | |
| if (data.down_round && data.down_round.is_active) { | |
| drAlert.style.display = 'block'; | |
| drAlert.innerHTML = data.down_round.text; | |
| } else { | |
| drAlert.style.display = 'none'; | |
| } | |
| // --- VISUALIZATION: PIE CHART & TABLE --- | |
| const existingShares = data.structure.ownership["Existing Shareholders"]; | |
| const newShares = data.structure.ownership["New Public Investors"]; | |
| const totalShares = data.structure.total_shares; | |
| // 1. Generate Table HTML | |
| const tableHTML = ` | |
| <table style="width:100%; border-collapse:collapse; color:#eee;"> | |
| <thead> | |
| <tr style="border-bottom:1px solid #444; color:#888;"> | |
| <th style="text-align:left; padding-bottom:5px;">Group</th> | |
| <th style="text-align:right; padding-bottom:5px;">Shares (M)</th> | |
| <th style="text-align:right; padding-bottom:5px;">% Own</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td style="padding:8px 0; color:#a0c4ff;">Existing</td> | |
| <td style="text-align:right;">${existingShares.toFixed(2)}</td> | |
| <td style="text-align:right;">${((existingShares / totalShares) * 100).toFixed(1)}%</td> | |
| </tr> | |
| <tr> | |
| <td style="padding:8px 0; color:#FFD700;">New Investors</td> | |
| <td style="text-align:right;">${newShares.toFixed(2)}</td> | |
| <td style="text-align:right;">${((newShares / totalShares) * 100).toFixed(1)}%</td> | |
| </tr> | |
| <tr style="border-top:1px solid #444; font-weight:bold;"> | |
| <td style="padding-top:8px;">Total</td> | |
| <td style="text-align:right; padding-top:8px;">${totalShares.toFixed(2)}</td> | |
| <td style="text-align:right; padding-top:8px;">100%</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| `; | |
| document.getElementById('ownership-table').innerHTML = tableHTML; | |
| // 2. Render Pie Chart (Navy & Gold) | |
| const ownerData = [{ | |
| values: [existingShares, newShares], | |
| labels: ['Existing Holders', 'New Public Investors'], | |
| type: 'pie', | |
| marker: { colors: ['#001F3F', '#D4AF37'] }, // Navy vs Gold | |
| textinfo: 'percent', | |
| hoverinfo: 'label+value+percent', | |
| hole: 0.6 | |
| }]; | |
| const ownerLayout = { | |
| paper_bgcolor: 'rgba(0,0,0,0)', | |
| plot_bgcolor: 'rgba(0,0,0,0)', | |
| font: { color: '#ccc', family: 'Inter' }, | |
| showlegend: false, | |
| margin: { t: 0, b: 0, l: 0, r: 0 }, | |
| height: 220 | |
| }; | |
| Plotly.newPlot('ownership-chart', ownerData, ownerLayout, { responsive: true, displayModeBar: false }); | |
| } | |
| // 3. Main Chart | |
| const layout = { | |
| plot_bgcolor: '#0E1117', | |
| paper_bgcolor: '#1E1E1E', | |
| font: { color: '#FAFAFA' }, | |
| margin: { t: 20, r: 20, b: 40, l: 40 }, | |
| xaxis: { showgrid: false }, | |
| yaxis: { showgrid: true, gridcolor: '#333' }, | |
| height: 350, | |
| showlegend: true, | |
| legend: { orientation: 'h', y: 1.1 } | |
| }; | |
| Plotly.newPlot('main-chart', data.chart_json, layout, { responsive: true }); | |
| // 4. Table | |
| const tbody = document.querySelector('#comps-table tbody'); | |
| tbody.innerHTML = ''; | |
| if (data.comparables) { | |
| data.comparables.forEach(c => { | |
| const r40Color = c.rule_40 < 40 ? '#fa5c5c' : '#5cfa85'; | |
| const row = ` | |
| <tr> | |
| <td style="font-weight:bold; color:var(--primary-gold)">${c.ticker}</td> | |
| <td>$${(c.market_cap / 1e9).toFixed(1)}B</td> | |
| <td>${c.ev_rev ? c.ev_rev.toFixed(1) + 'x' : 'N/A'}</td> | |
| <td style="color:${r40Color}">${c.rule_40 ? c.rule_40.toFixed(0) : '--'}</td> | |
| <td>${c.growth ? (c.growth * 100).toFixed(0) + '%' : 'N/A'}</td> | |
| <td>${c.beta ? c.beta.toFixed(2) : '1.00'}</td> | |
| </tr> | |
| `; | |
| tbody.innerHTML += row; | |
| }); | |
| } | |
| } | |