| <!doctype html> |
| <html lang="en"> |
| <head> |
| <meta charset="utf-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> |
| <title>__PORTAL_SUBMISSION_TITLE__</title> |
| <link rel="preconnect" href="https://fonts.googleapis.com"> |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
| <link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Source+Serif+4:wght@500;600;700&display=swap" rel="stylesheet"> |
| <link rel="stylesheet" href="/task1/dev/static/app.css" /> |
| </head> |
| <body> |
| <div class="page-shell narrow-shell"> |
| <div class="site-ribbon"> |
| <span>FinMMEval Lab 2026 · CLEF Official __PORTAL_TITLE__</span> |
| <a href="__OFFICIAL_SITE_URL__">Main Site</a> |
| </div> |
| <header class="topbar compact-topbar"> |
| <div class="brand-block"> |
| <div class="brand-kicker">Participant Submission</div> |
| <h1>__PORTAL_VARIANT__ Dev Upload</h1> |
| <p id="submission-intro">Upload one prediction file per team. A new upload from the same team replaces the older one and refreshes the official __PORTAL_VARIANT__ dev ranking.</p> |
| </div> |
| <nav class="nav-links"> |
| <a href="__OFFICIAL_SITE_URL__">Official Site</a> |
| <a href="/task1/dev">Home</a> |
| <a id="leaderboard-link" href="/task1/dev/leaderboard">Leaderboard</a> |
| </nav> |
| </header> |
|
|
| <main class="form-layout"> |
| <section class="panel"> |
| <h2>Submit Predictions</h2> |
| <p class="helper-text">This page is part of the official FinMMEval Lab Task 1 __PORTAL_VARIANT__ dev workflow.</p> |
| <p class="helper-text">Please submit under one consistent official team name. Multiple aliases, test names, or duplicate team entries may be merged, renamed, or removed by the organizers to keep the leaderboard clean.</p> |
| <form id="submission-form" class="stack-form"> |
| <label> |
| <span>Team Name</span> |
| <input type="text" id="team_name" name="team_name" placeholder="Example: MBZUAI Finance Lab" required /> |
| </label> |
|
|
| <label id="contact-email-field" class="hidden"> |
| <span>Email</span> |
| <input type="email" id="contact_email" name="contact_email" placeholder="name@example.com" autocomplete="email" /> |
| </label> |
|
|
| <label id="submission-code-field" class="hidden"> |
| <span>Submission Code</span> |
| <input type="password" id="submission_code" name="submission_code" autocomplete="off" /> |
| </label> |
|
|
| <label> |
| <span>Prediction File</span> |
| <input type="file" id="prediction_file" name="prediction_file" accept=".json,.jsonl" required /> |
| </label> |
|
|
| <div class="helper-text"> |
| Required per-item format: |
| <code>{"id":"acct-dev-001","prediction":"B"}</code> |
| </div> |
|
|
| <div class="card-actions"> |
| <button id="submit-button" type="submit" class="button button-primary">Submit And Refresh Leaderboard</button> |
| <a class="button button-secondary" href="/api/task1/template/download">Download Template</a> |
| </div> |
| </form> |
| </section> |
|
|
| <aside class="panel muted-panel"> |
| <h3>Resources</h3> |
| <ul class="resource-list"> |
| <li><a href="/api/task1/devset/download">Download Public Dev Set</a></li> |
| <li><a href="/api/task1/template/download">Download Submission Template</a></li> |
| <li><a id="resource-leaderboard-link" href="/task1/dev/leaderboard">View Live Leaderboard</a></li> |
| </ul> |
| <div class="mini-status"> |
| <div><span>Submissions</span><strong id="submission-count">Loading...</strong></div> |
| <div><span>Last Refresh</span><strong id="last-updated">Loading...</strong></div> |
| </div> |
| </aside> |
| </main> |
|
|
| <section id="result-panel" class="result-panel hidden"></section> |
| </div> |
|
|
| <script> |
| function escapeHtml(value) { |
| return String(value ?? '') |
| .replaceAll('&', '&') |
| .replaceAll('<', '<') |
| .replaceAll('>', '>') |
| .replaceAll('"', '"') |
| .replaceAll("'", '''); |
| } |
| |
| async function loadMeta() { |
| const response = await fetch('/api/task1/dev/meta'); |
| if (!response.ok) return; |
| const payload = await response.json(); |
| const isTestMode = payload.portal_mode === 'test'; |
| document.getElementById('submission-count').textContent = String(payload.submission_count ?? 0); |
| document.getElementById('last-updated').textContent = payload.last_updated ?? 'Not evaluated yet'; |
| document.getElementById('leaderboard-link').textContent = isTestMode ? 'Submission Status' : 'Leaderboard'; |
| document.getElementById('resource-leaderboard-link').textContent = isTestMode ? 'View Submission Status' : 'View Live Leaderboard'; |
| document.getElementById('submit-button').textContent = isTestMode ? 'Submit Final Predictions' : 'Submit And Refresh Leaderboard'; |
| document.getElementById('submission-intro').textContent = isTestMode |
| ? 'Upload one final-test prediction file. Your email is used as the unique submission key; a later upload from the same email replaces the earlier active submission. Scores and ranks remain hidden.' |
| : 'Upload one prediction file per team. A new upload from the same team replaces the older one and refreshes the official __PORTAL_VARIANT__ dev ranking.'; |
| if (payload.submissions_closed) { |
| document.getElementById('submit-button').textContent = 'Submission Window Closed'; |
| document.getElementById('submit-button').disabled = true; |
| document.getElementById('submission-intro').textContent = 'The final-test submission window is closed. Please use the final leaderboards for official Task 1 results.'; |
| document.querySelectorAll('#submission-form input, #submission-form button').forEach((item) => { |
| item.disabled = true; |
| }); |
| } |
| |
| const emailField = document.getElementById('contact-email-field'); |
| const emailInput = document.getElementById('contact_email'); |
| if (payload.requires_email) { |
| emailField.classList.remove('hidden'); |
| emailInput.required = true; |
| } else { |
| emailField.classList.add('hidden'); |
| emailInput.required = false; |
| } |
| |
| const codeField = document.getElementById('submission-code-field'); |
| const codeInput = document.getElementById('submission_code'); |
| if (payload.requires_team_code) { |
| codeField.classList.remove('hidden'); |
| codeInput.required = true; |
| document.getElementById('team_name').required = false; |
| } else { |
| codeField.classList.add('hidden'); |
| codeInput.required = false; |
| document.getElementById('team_name').required = true; |
| } |
| } |
| |
| function renderResult(payload, isError = false) { |
| const panel = document.getElementById('result-panel'); |
| panel.classList.remove('hidden', 'result-success', 'result-error'); |
| panel.classList.add(isError ? 'result-error' : 'result-success'); |
| if (isError) { |
| panel.innerHTML = `<h3>Submission Failed</h3><pre>${escapeHtml(JSON.stringify(payload, null, 2))}</pre>`; |
| return; |
| } |
| const row = payload.leaderboard_row || null; |
| const validation = payload.validation || {}; |
| const evaluation = payload.evaluation || {}; |
| const scoreCells = row |
| ? ` |
| <div><span>Rank</span><strong>${escapeHtml(row.rank ?? '-')}</strong></div> |
| <div><span>Accuracy</span><strong>${escapeHtml(row.accuracy ?? '-')}</strong></div> |
| <div><span>Coverage</span><strong>${escapeHtml(row.coverage ?? '-')}</strong></div>` |
| : ` |
| <div><span>Coverage</span><strong>${escapeHtml(validation.coverage ?? '-')}</strong></div> |
| <div><span>Answered</span><strong>${escapeHtml(validation.answered ?? '-')} / ${escapeHtml(validation.total ?? '-')}</strong></div> |
| <div><span>Format Valid</span><strong>${validation.valid_submission ? 'yes' : 'no'}</strong></div> |
| <div><span>Evaluation</span><strong>${evaluation.completed ? 'completed' : 'not completed'}</strong></div>`; |
| panel.innerHTML = ` |
| <h3>Submission Accepted</h3> |
| <p>${escapeHtml(payload.message)}</p> |
| <div class="result-grid"> |
| <div><span>Team</span><strong>${escapeHtml(payload.team_name)}</strong></div> |
| ${scoreCells} |
| </div> |
| <p class="helper-text">Validation: unknown IDs = ${escapeHtml(validation.unknown_ids?.length ?? 0)}, missing IDs = ${escapeHtml(validation.missing_ids?.length ?? 0)}, duplicate IDs = ${escapeHtml(validation.duplicate_ids ?? 0)}, invalid predictions = ${escapeHtml(validation.invalid_prediction_count ?? 0)}</p> |
| `; |
| } |
| |
| document.getElementById('submission-form').addEventListener('submit', async (event) => { |
| event.preventDefault(); |
| const form = event.currentTarget; |
| const data = new FormData(form); |
| const response = await fetch('/api/task1/submissions', { |
| method: 'POST', |
| body: data, |
| }); |
| const payload = await response.json(); |
| if (!response.ok) { |
| renderResult(payload, true); |
| return; |
| } |
| renderResult(payload, false); |
| form.reset(); |
| loadMeta(); |
| }); |
| |
| loadMeta(); |
| </script> |
| </body> |
| </html> |
|
|