Dashboard: Team column + Intern/Recovery badges from report fields
Browse filesBackward-compatible: reports without a team show 'Unassigned'; unknown team
strings are clamped server-side. Adds team to the sortable columns.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
app.py
CHANGED
|
@@ -86,12 +86,19 @@ class Counts(BaseModel):
|
|
| 86 |
|
| 87 |
class Report(BaseModel):
|
| 88 |
annotator: str
|
|
|
|
|
|
|
|
|
|
| 89 |
folder: str = ""
|
| 90 |
total: int = 0
|
| 91 |
counts: Counts
|
| 92 |
updatedAt: int = 0
|
| 93 |
|
| 94 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
# ----------------------------------------------------------------------------
|
| 96 |
# Helpers
|
| 97 |
# ----------------------------------------------------------------------------
|
|
@@ -159,6 +166,7 @@ def submit_report(report: Report, x_report_token: str = Header(default="")):
|
|
| 159 |
slug = slugify(report.annotator)
|
| 160 |
payload = report.model_dump()
|
| 161 |
payload["annotator"] = report.annotator.strip() or "Anonymous"
|
|
|
|
| 162 |
payload["slug"] = slug
|
| 163 |
payload["receivedAt"] = int(time.time() * 1000)
|
| 164 |
|
|
@@ -285,6 +293,13 @@ DASHBOARD_HTML = """<!DOCTYPE html>
|
|
| 285 |
.medal.gold { background: linear-gradient(145deg, #ffe487, #f5b301); }
|
| 286 |
.medal.silver { background: linear-gradient(145deg, #eef2f5, #b4bdc7); }
|
| 287 |
.medal.bronze { background: linear-gradient(145deg, #f0b27a, #c2772f); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
/* in-row stacked status bar (table Progress column) */
|
| 289 |
.bar-track { flex: 1; display: flex; height: 14px; border-radius: 4px; overflow: hidden;
|
| 290 |
background: var(--panel-2); }
|
|
@@ -313,6 +328,7 @@ DASHBOARD_HTML = """<!DOCTYPE html>
|
|
| 313 |
<script>
|
| 314 |
const STATUS = ["delete", "correct", "to_correct", "edited", "pending"];
|
| 315 |
const LABEL = { delete: "Delete", correct: "Correct", to_correct: "To Fix", edited: "Edited", pending: "Pending" };
|
|
|
|
| 316 |
// Chart segment order (done group first, backlog, then pending) + color var.
|
| 317 |
const CHART_ORDER = [
|
| 318 |
["correct", "--correct"], ["edited", "--edited"], ["delete", "--delete"],
|
|
@@ -365,6 +381,7 @@ function sortValue(r, key) {
|
|
| 365 |
const c = r.counts || {};
|
| 366 |
switch (key) {
|
| 367 |
case "name": return (r.annotator || "").toLowerCase();
|
|
|
|
| 368 |
case "total": return r.total || 0;
|
| 369 |
case "delete": return c.delete || 0;
|
| 370 |
case "correct": return c.correct || 0;
|
|
@@ -505,9 +522,16 @@ async function refresh() {
|
|
| 505 |
const rankCell = (podium && rank <= 3)
|
| 506 |
? `<td class="rank"><span class="medal ${MEDALS[rank - 1]}">${rank}</span></td>`
|
| 507 |
: `<td class="rank">${rank}</td>`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 508 |
return `<tr>
|
| 509 |
${rankCell}
|
| 510 |
<td class="name">${escapeHtml(r.annotator || "—")}<div class="muted" style="font-weight:400;font-size:12px">${escapeHtml(r.folder || "")}</div></td>
|
|
|
|
| 511 |
<td class="num">${r.total || 0}</td>
|
| 512 |
<td><span class="pill delete">${c.delete || 0}</span></td>
|
| 513 |
<td><span class="pill correct">${c.correct || 0}</span></td>
|
|
@@ -524,6 +548,7 @@ async function refresh() {
|
|
| 524 |
const foot = `<tr>
|
| 525 |
<td class="rank"></td>
|
| 526 |
<td>Team total</td>
|
|
|
|
| 527 |
<td class="num">${agg.total}</td>
|
| 528 |
<td><span class="pill delete">${ac.delete || 0}</span></td>
|
| 529 |
<td><span class="pill correct">${ac.correct || 0}</span></td>
|
|
@@ -539,6 +564,7 @@ async function refresh() {
|
|
| 539 |
<thead><tr>
|
| 540 |
<th class="rank-h" title="Rank in the current sort">#</th>
|
| 541 |
<th data-sort="name" class="sortable">Annotator${arrow("name")}</th>
|
|
|
|
| 542 |
<th data-sort="total" class="sortable num">Total${arrow("total")}</th>
|
| 543 |
<th data-sort="delete" class="sortable">Delete${arrow("delete")}</th>
|
| 544 |
<th data-sort="correct" class="sortable">Correct${arrow("correct")}</th>
|
|
|
|
| 86 |
|
| 87 |
class Report(BaseModel):
|
| 88 |
annotator: str
|
| 89 |
+
team: str = ""
|
| 90 |
+
intern: bool = False
|
| 91 |
+
recoveryUsed: bool = False
|
| 92 |
folder: str = ""
|
| 93 |
total: int = 0
|
| 94 |
counts: Counts
|
| 95 |
updatedAt: int = 0
|
| 96 |
|
| 97 |
|
| 98 |
+
# Known teams; anything else is treated as Unassigned.
|
| 99 |
+
TEAMS = {"Research", "Investments", "Growth", "ConvAI", "HyperPersonalization"}
|
| 100 |
+
|
| 101 |
+
|
| 102 |
# ----------------------------------------------------------------------------
|
| 103 |
# Helpers
|
| 104 |
# ----------------------------------------------------------------------------
|
|
|
|
| 166 |
slug = slugify(report.annotator)
|
| 167 |
payload = report.model_dump()
|
| 168 |
payload["annotator"] = report.annotator.strip() or "Anonymous"
|
| 169 |
+
payload["team"] = report.team if report.team in TEAMS else ""
|
| 170 |
payload["slug"] = slug
|
| 171 |
payload["receivedAt"] = int(time.time() * 1000)
|
| 172 |
|
|
|
|
| 293 |
.medal.gold { background: linear-gradient(145deg, #ffe487, #f5b301); }
|
| 294 |
.medal.silver { background: linear-gradient(145deg, #eef2f5, #b4bdc7); }
|
| 295 |
.medal.bronze { background: linear-gradient(145deg, #f0b27a, #c2772f); }
|
| 296 |
+
/* team cell + intern/recovery tags */
|
| 297 |
+
td.team .team-name { font-weight: 600; color: var(--text); }
|
| 298 |
+
.tag-row { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
|
| 299 |
+
.tag { display: inline-block; padding: 1px 7px; border-radius: 99px; font-size: 11px;
|
| 300 |
+
font-weight: 600; line-height: 1.5; border: 1px solid transparent; }
|
| 301 |
+
.tag.intern { background: rgba(79,70,229,.16); color: #818cf8; border-color: rgba(79,70,229,.35); }
|
| 302 |
+
.tag.recovery { background: rgba(245,158,11,.16); color: #f59e0b; border-color: rgba(245,158,11,.35); }
|
| 303 |
/* in-row stacked status bar (table Progress column) */
|
| 304 |
.bar-track { flex: 1; display: flex; height: 14px; border-radius: 4px; overflow: hidden;
|
| 305 |
background: var(--panel-2); }
|
|
|
|
| 328 |
<script>
|
| 329 |
const STATUS = ["delete", "correct", "to_correct", "edited", "pending"];
|
| 330 |
const LABEL = { delete: "Delete", correct: "Correct", to_correct: "To Fix", edited: "Edited", pending: "Pending" };
|
| 331 |
+
const TEAMS = new Set(["Research", "Investments", "Growth", "ConvAI", "HyperPersonalization"]);
|
| 332 |
// Chart segment order (done group first, backlog, then pending) + color var.
|
| 333 |
const CHART_ORDER = [
|
| 334 |
["correct", "--correct"], ["edited", "--edited"], ["delete", "--delete"],
|
|
|
|
| 381 |
const c = r.counts || {};
|
| 382 |
switch (key) {
|
| 383 |
case "name": return (r.annotator || "").toLowerCase();
|
| 384 |
+
case "team": return (r.team || "").toLowerCase();
|
| 385 |
case "total": return r.total || 0;
|
| 386 |
case "delete": return c.delete || 0;
|
| 387 |
case "correct": return c.correct || 0;
|
|
|
|
| 522 |
const rankCell = (podium && rank <= 3)
|
| 523 |
? `<td class="rank"><span class="medal ${MEDALS[rank - 1]}">${rank}</span></td>`
|
| 524 |
: `<td class="rank">${rank}</td>`;
|
| 525 |
+
const team = TEAMS.has(r.team) ? r.team : "";
|
| 526 |
+
const tags = (r.intern ? `<span class="tag intern">Intern</span>` : "")
|
| 527 |
+
+ (r.recoveryUsed ? `<span class="tag recovery" title="Used skip-ahead recovery; numbers are partly self-declared">Recovery</span>` : "");
|
| 528 |
+
const teamCell = `<td class="team">`
|
| 529 |
+
+ (team ? `<span class="team-name">${escapeHtml(team)}</span>` : `<span class="muted">Unassigned</span>`)
|
| 530 |
+
+ (tags ? `<div class="tag-row">${tags}</div>` : "") + `</td>`;
|
| 531 |
return `<tr>
|
| 532 |
${rankCell}
|
| 533 |
<td class="name">${escapeHtml(r.annotator || "—")}<div class="muted" style="font-weight:400;font-size:12px">${escapeHtml(r.folder || "")}</div></td>
|
| 534 |
+
${teamCell}
|
| 535 |
<td class="num">${r.total || 0}</td>
|
| 536 |
<td><span class="pill delete">${c.delete || 0}</span></td>
|
| 537 |
<td><span class="pill correct">${c.correct || 0}</span></td>
|
|
|
|
| 548 |
const foot = `<tr>
|
| 549 |
<td class="rank"></td>
|
| 550 |
<td>Team total</td>
|
| 551 |
+
<td></td>
|
| 552 |
<td class="num">${agg.total}</td>
|
| 553 |
<td><span class="pill delete">${ac.delete || 0}</span></td>
|
| 554 |
<td><span class="pill correct">${ac.correct || 0}</span></td>
|
|
|
|
| 564 |
<thead><tr>
|
| 565 |
<th class="rank-h" title="Rank in the current sort">#</th>
|
| 566 |
<th data-sort="name" class="sortable">Annotator${arrow("name")}</th>
|
| 567 |
+
<th data-sort="team" class="sortable">Team${arrow("team")}</th>
|
| 568 |
<th data-sort="total" class="sortable num">Total${arrow("total")}</th>
|
| 569 |
<th data-sort="delete" class="sortable">Delete${arrow("delete")}</th>
|
| 570 |
<th data-sort="correct" class="sortable">Correct${arrow("correct")}</th>
|