Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Financial Report Analyzer</title> | |
| <style> | |
| body { | |
| font-family: sans-serif; | |
| max-width: 800px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| line-height: 1.6; | |
| } | |
| .container { | |
| border: 1px solid #ccc; | |
| padding: 20px; | |
| border-radius: 8px; | |
| background: #f9f9f9; | |
| } | |
| h1 { | |
| text-align: center; | |
| color: #333; | |
| } | |
| .form-group { | |
| margin-bottom: 20px; | |
| text-align: center; | |
| } | |
| input[type="file"] { | |
| margin: 10px 0; | |
| } | |
| button { | |
| background-color: #007bff; | |
| color: white; | |
| border: none; | |
| padding: 10px 20px; | |
| border-radius: 4px; | |
| cursor: pointer; | |
| font-size: 16px; | |
| } | |
| button:hover { | |
| background-color: #0056b3; | |
| } | |
| button:disabled { | |
| background-color: #ccc; | |
| cursor: not-allowed; | |
| } | |
| #status { | |
| text-align: center; | |
| margin-top: 10px; | |
| font-weight: bold; | |
| } | |
| #result { | |
| margin-top: 20px; | |
| white-space: pre-wrap; | |
| background: #fff; | |
| padding: 15px; | |
| border: 1px solid #ddd; | |
| border-radius: 4px; | |
| display: none; | |
| } | |
| .error { | |
| color: #dc3545; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>Financial Report Analyzer</h1> | |
| <p style="text-align: center;">Upload a 10-K/Annual Report PDF to extract page ranges for primary financial | |
| statements.</p> | |
| <div class="form-group"> | |
| <input type="file" id="pdfInput" accept=".pdf" /> | |
| <br> | |
| <button id="analyzeBtn" onclick="analyzePdf()">Analyze PDF</button> | |
| </div> | |
| <div id="status"></div> | |
| <pre id="result"></pre> | |
| </div> | |
| <script> | |
| async function analyzePdf() { | |
| const input = document.getElementById('pdfInput'); | |
| const file = input.files[0]; | |
| const btn = document.getElementById('analyzeBtn'); | |
| const status = document.getElementById('status'); | |
| const resultDisplay = document.getElementById('result'); | |
| if (!file) { | |
| alert("Please select a PDF file first."); | |
| return; | |
| } | |
| // Reset UI | |
| btn.disabled = true; | |
| status.textContent = "Analyzing... This may take a minute."; | |
| status.className = ""; | |
| resultDisplay.style.display = 'none'; | |
| resultDisplay.innerHTML = ""; // Clear previous content | |
| const formData = new FormData(); | |
| formData.append('file', file); | |
| try { | |
| const response = await fetch('/analyze', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| if (!response.ok) { | |
| const errorData = await response.json(); | |
| throw new Error(errorData.detail || "Analysis failed"); | |
| } | |
| const data = await response.json(); | |
| status.textContent = "Analysis Complete!"; | |
| // Render nicely | |
| renderResults(data, resultDisplay); | |
| resultDisplay.style.display = 'block'; | |
| } catch (error) { | |
| console.error("Error:", error); | |
| status.textContent = "Error: " + error.message; | |
| status.className = "error"; | |
| } finally { | |
| btn.disabled = false; | |
| } | |
| } | |
| function renderResults(data, container) { | |
| let html = ""; | |
| const sections = [ | |
| { key: 'balance_sheet', label: 'Balance Sheet' }, | |
| { key: 'profit_and_loss', label: 'Profit & Loss' }, | |
| { key: 'cash_flow', label: 'Cash Flow' } | |
| ]; | |
| sections.forEach(sec => { | |
| html += `<h3>${sec.label}</h3>`; | |
| const items = data[sec.key]; | |
| if (!items || items.length === 0) { | |
| html += "<p>No ranges found.</p>"; | |
| } else { | |
| html += ` | |
| <div style="overflow-x: auto;"> | |
| <table border="1" cellpadding="8" style="border-collapse: collapse; width: 100%; margin-bottom: 20px;"> | |
| <tr style="background: #eee;"> | |
| <th>Scope</th> | |
| <th>Pages</th> | |
| <th>Details</th> | |
| <th style="min-width: 300px;">Evidence Images</th> | |
| </tr>`; | |
| items.forEach(item => { | |
| const pagesStr = (item.pages || []).join(", "); | |
| // Generate images for all pages in the range | |
| let imagesHtml = '<div style="display: flex; gap: 10px; overflow-x: auto;">'; | |
| const pagesToShow = item.evidence_pages && item.evidence_pages.length > 0 | |
| ? item.evidence_pages | |
| : (item.pages || []); | |
| pagesToShow.forEach(pNum => { | |
| imagesHtml += ` | |
| <div style="text-align: center;"> | |
| <a href="/pdf/page/${pNum}" target="_blank"> | |
| <img src="/pdf/page/${pNum}" style="height: 200px; border: 1px solid #ddd; box-shadow: 2px 2px 5px rgba(0,0,0,0.1);" alt="Page ${pNum}" loading="lazy"><br> | |
| <small>Page ${pNum}</small> | |
| </a> | |
| </div>`; | |
| }); | |
| imagesHtml += '</div>'; | |
| html += ` | |
| <tr> | |
| <td><strong>${item.scope}</strong></td> | |
| <td>${pagesStr}</td> | |
| <td> | |
| <strong>Title:</strong> ${item.title || "<em>(null)</em>"}<br> | |
| <strong>Confidence:</strong> ${(item.confidence * 100).toFixed(0)}% | |
| </td> | |
| <td>${imagesHtml}</td> | |
| </tr>`; | |
| }); | |
| html += "</table></div>"; | |
| } | |
| }); | |
| // Notes | |
| if (data.notes && data.notes.length > 0) { | |
| html += "<h3>Notes</h3><ul>"; | |
| data.notes.forEach(note => { | |
| html += `<li>${note}</li>`; | |
| }); | |
| html += "</ul>"; | |
| } | |
| // Raw JSON toggle (optional) | |
| html += `<hr><details><summary>Raw JSON</summary><pre>${JSON.stringify(data, null, 2)}</pre></details>`; | |
| container.innerHTML = html; | |
| } | |
| </script> | |
| </body> | |
| </html> |