Spaces:
Running
Running
Commit ·
6ec6311
1
Parent(s): 72ab5f3
Space UI: Hub design system, header/hero, KPI strip, status pills and episode bars
Browse files
app.py
CHANGED
|
@@ -7,7 +7,7 @@ from urllib.parse import quote
|
|
| 7 |
import gradio as gr
|
| 8 |
import requests
|
| 9 |
|
| 10 |
-
from cards import CSS, card_payload, render
|
| 11 |
from quick_grade import parse_ref, quick_grade
|
| 12 |
|
| 13 |
# ZeroGPU compatibility shim. If the Space is ever scheduled on ZeroGPU hardware,
|
|
@@ -42,12 +42,14 @@ def examples():
|
|
| 42 |
with ThreadPoolExecutor(max_workers=3) as pool:
|
| 43 |
payloads = [p for p in pool.map(published, FEATURED) if p]
|
| 44 |
if not payloads:
|
| 45 |
-
return '<p>Featured reports are unavailable. You can still grade a public dataset above.</p>'
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
|
|
|
|
|
|
| 51 |
|
| 52 |
|
| 53 |
def run(ref):
|
|
@@ -56,7 +58,7 @@ def run(ref):
|
|
| 56 |
# Always run fresh. Existing published reports never short-circuit grading.
|
| 57 |
card = quick_grade(ref)
|
| 58 |
payload = card_payload(card, KALANOS_API)
|
| 59 |
-
return render(payload, published=False), payload['markdown'], 'Quick audit complete. Video: not assessed.', card
|
| 60 |
except ValueError as exc:
|
| 61 |
return '', '', str(exc), None
|
| 62 |
except Exception:
|
|
@@ -68,7 +70,7 @@ def lookup(ref):
|
|
| 68 |
try:
|
| 69 |
payload = published(parse_ref(ref))
|
| 70 |
if payload:
|
| 71 |
-
return render(payload), payload['markdown'], 'Stored assessment shown with its recorded date and scope. Grade dataset runs a fresh audit.', None
|
| 72 |
except ValueError as exc:
|
| 73 |
return '', '', str(exc), None
|
| 74 |
return '', '', 'No published schema-7 report found. Grade dataset to run a fresh quick audit.', None
|
|
@@ -81,25 +83,33 @@ def lookup(ref):
|
|
| 81 |
# Until then: copy the HF card Markdown, or publish from the CLI.
|
| 82 |
|
| 83 |
with gr.Blocks(title='Kalanos · Robotics Dataset Quality') as demo:
|
| 84 |
-
gr.
|
| 85 |
-
gr.
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
|
|
|
| 92 |
out = gr.HTML()
|
| 93 |
report_state = gr.State(None, time_to_live=600)
|
| 94 |
-
with gr.Accordion('HF card Markdown', open=False):
|
| 95 |
card_text = gr.Code(language='markdown', label='Copy into your existing dataset README', interactive=False)
|
| 96 |
-
gr.
|
| 97 |
featured = gr.HTML()
|
| 98 |
demo.load(examples, outputs=featured)
|
| 99 |
go.click(run, inputs=ref_in, outputs=[out, card_text, status, report_state], concurrency_limit=1, concurrency_id='audit')
|
| 100 |
ref_in.submit(run, inputs=ref_in, outputs=[out, card_text, status, report_state], concurrency_limit=1, concurrency_id='audit')
|
| 101 |
lookup_btn.click(lookup, inputs=ref_in, outputs=[out, card_text, status, report_state])
|
| 102 |
-
gr.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
|
| 104 |
if __name__ == '__main__':
|
| 105 |
-
demo.queue(max_size=4).launch(css=CSS)
|
|
|
|
| 7 |
import gradio as gr
|
| 8 |
import requests
|
| 9 |
|
| 10 |
+
from cards import CSS, card_payload, grader_intro, header, kpis, render, section, share_strip
|
| 11 |
from quick_grade import parse_ref, quick_grade
|
| 12 |
|
| 13 |
# ZeroGPU compatibility shim. If the Space is ever scheduled on ZeroGPU hardware,
|
|
|
|
| 42 |
with ThreadPoolExecutor(max_workers=3) as pool:
|
| 43 |
payloads = [p for p in pool.map(published, FEATURED) if p]
|
| 44 |
if not payloads:
|
| 45 |
+
return '<div class="k-html"><p class="k-small k-muted">Featured reports are unavailable right now. You can still grade a public dataset above.</p></div>'
|
| 46 |
+
return ('<div class="k-html"><p class="k-small k-muted" style="margin:0">KPIs cover these example reports only.</p>'
|
| 47 |
+
+ kpis(payloads) + '<div class="k-cards">' + ''.join(render(p) for p in payloads) + '</div>'
|
| 48 |
+
'<p class="k-foot">Published CLI assessments · outcomes apply within the recorded scope. Reports may use different revisions and assessment scopes.</p></div>')
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def _one(html):
|
| 52 |
+
return f'<div class="k-html"><div class="k-cards" style="grid-template-columns:minmax(0,1fr);max-width:420px">{html}</div></div>' if html else ''
|
| 53 |
|
| 54 |
|
| 55 |
def run(ref):
|
|
|
|
| 58 |
# Always run fresh. Existing published reports never short-circuit grading.
|
| 59 |
card = quick_grade(ref)
|
| 60 |
payload = card_payload(card, KALANOS_API)
|
| 61 |
+
return _one(render(payload, published=False)), payload['markdown'], 'Quick audit complete. Video: not assessed.', card
|
| 62 |
except ValueError as exc:
|
| 63 |
return '', '', str(exc), None
|
| 64 |
except Exception:
|
|
|
|
| 70 |
try:
|
| 71 |
payload = published(parse_ref(ref))
|
| 72 |
if payload:
|
| 73 |
+
return _one(render(payload)), payload['markdown'], 'Stored assessment shown with its recorded date and scope. Grade dataset runs a fresh audit.', None
|
| 74 |
except ValueError as exc:
|
| 75 |
return '', '', str(exc), None
|
| 76 |
return '', '', 'No published schema-7 report found. Grade dataset to run a fresh quick audit.', None
|
|
|
|
| 83 |
# Until then: copy the HF card Markdown, or publish from the CLI.
|
| 84 |
|
| 85 |
with gr.Blocks(title='Kalanos · Robotics Dataset Quality') as demo:
|
| 86 |
+
gr.HTML(header(KALANOS_API))
|
| 87 |
+
with gr.Group(elem_classes='k-grader'):
|
| 88 |
+
gr.HTML(grader_intro())
|
| 89 |
+
with gr.Row():
|
| 90 |
+
ref_in = gr.Textbox(label='Public HF dataset', placeholder='lerobot/pusht', scale=4, show_label=False, container=False)
|
| 91 |
+
go = gr.Button('Grade dataset', variant='primary', scale=1, elem_classes='k-primary')
|
| 92 |
+
# Secondary action stays clear; report cards themselves use compact icons.
|
| 93 |
+
lookup_btn = gr.Button('View published assessment', scale=1, elem_classes='k-secondary')
|
| 94 |
+
status = gr.Textbox(label='Audit status', interactive=False, lines=1, max_lines=2, elem_classes='k-status', container=False, placeholder='Audit status appears here.')
|
| 95 |
out = gr.HTML()
|
| 96 |
report_state = gr.State(None, time_to_live=600)
|
| 97 |
+
with gr.Accordion('HF card Markdown', open=False, elem_classes='k-accordion'):
|
| 98 |
card_text = gr.Code(language='markdown', label='Copy into your existing dataset README', interactive=False)
|
| 99 |
+
gr.HTML(section('Featured dataset reports', 'Live from Kalanos Hub'))
|
| 100 |
featured = gr.HTML()
|
| 101 |
demo.load(examples, outputs=featured)
|
| 102 |
go.click(run, inputs=ref_in, outputs=[out, card_text, status, report_state], concurrency_limit=1, concurrency_id='audit')
|
| 103 |
ref_in.submit(run, inputs=ref_in, outputs=[out, card_text, status, report_state], concurrency_limit=1, concurrency_id='audit')
|
| 104 |
lookup_btn.click(lookup, inputs=ref_in, outputs=[out, card_text, status, report_state])
|
| 105 |
+
gr.HTML(share_strip(KALANOS_API))
|
| 106 |
+
|
| 107 |
+
THEME = gr.themes.Base(
|
| 108 |
+
primary_hue=gr.themes.colors.slate, neutral_hue=gr.themes.colors.stone,
|
| 109 |
+
font=[gr.themes.GoogleFont('IBM Plex Sans'), 'sans-serif'],
|
| 110 |
+
font_mono=[gr.themes.GoogleFont('IBM Plex Mono'), 'monospace'],
|
| 111 |
+
).set(body_background_fill='#f2efe6', body_text_color='#202622', block_radius='0px', button_large_radius='3px',
|
| 112 |
+
input_radius='3px', input_background_fill='#ffffff', input_border_color='#d2d3cb')
|
| 113 |
|
| 114 |
if __name__ == '__main__':
|
| 115 |
+
demo.queue(max_size=4).launch(css=CSS, theme=THEME)
|
cards.py
CHANGED
|
@@ -1,4 +1,8 @@
|
|
| 1 |
-
"""Escaped report cards; no invented scores or client-side regrading.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
from html import escape
|
| 3 |
from urllib.parse import quote
|
| 4 |
|
|
@@ -7,17 +11,73 @@ ICONS = {
|
|
| 7 |
'card': '<rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18M7 13h4M7 16h8"/>',
|
| 8 |
'hf': '<path d="M14 3h7v7m0-7L10 14M10 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-5"/>',
|
| 9 |
}
|
| 10 |
-
|
| 11 |
-
.
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
.k-
|
| 16 |
-
.
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
'''
|
| 22 |
|
| 23 |
|
|
@@ -29,6 +89,41 @@ def link(url, label, kind):
|
|
| 29 |
return f'<a class="k-icon" href="{escape(url, quote=True)}" title="{label}" aria-label="{label}" target="_blank" rel="noopener">{svg(kind)}</a>'
|
| 30 |
|
| 31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
def card_payload(card, base):
|
| 33 |
d = card['decision_summary']
|
| 34 |
ref = card['source']['ref']
|
|
@@ -51,15 +146,68 @@ def card_payload(card, base):
|
|
| 51 |
dataset_url='https://huggingface.co/datasets/' + quote(ref, safe='/'), report_url=None)
|
| 52 |
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
def render(payload, published=True):
|
| 55 |
c = payload['counts']
|
| 56 |
score = payload['readiness']
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
|
|
|
| 60 |
actions = link(payload['report_url'], 'View full report', 'report') if published and payload.get('report_url') else ''
|
| 61 |
preview = '<table>' + ''.join(f'<tr><td>{escape(str(k))}</td><td>{escape(str(v))}</td></tr>' for k, v in payload['details']) + '</table>'
|
| 62 |
-
actions += f'<details><summary class="k-icon" title="Preview and copy HF dataset card" aria-label="Preview and copy HF dataset card">{svg("card")}</summary>
|
|
|
|
|
|
|
| 63 |
actions += link(payload['dataset_url'], 'Open dataset on Hugging Face', 'hf')
|
| 64 |
-
stats = ''.join(f'<div><
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Escaped report cards and page chrome; no invented scores or client-side regrading.
|
| 2 |
+
|
| 3 |
+
Visual language follows design/hf-card-mockup in the publisher repo: IBM Plex,
|
| 4 |
+
paper/navy/sage palette, status pills, two-tone episode bars, compact icon actions.
|
| 5 |
+
"""
|
| 6 |
from html import escape
|
| 7 |
from urllib.parse import quote
|
| 8 |
|
|
|
|
| 11 |
'card': '<rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18M7 13h4M7 16h8"/>',
|
| 12 |
'hf': '<path d="M14 3h7v7m0-7L10 14M10 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-5"/>',
|
| 13 |
}
|
| 14 |
+
|
| 15 |
+
FONTS = ("https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;450;500;600"
|
| 16 |
+
"&family=IBM+Plex+Mono:wght@400;500&display=swap")
|
| 17 |
+
|
| 18 |
+
# Design tokens mirror the mockup's :root. Everything below is scoped to the
|
| 19 |
+
# Gradio container (.gradio-container) or to k-* classes so it never leaks into
|
| 20 |
+
# the HF Space chrome around the app.
|
| 21 |
+
CSS = f'''
|
| 22 |
+
@import url('{FONTS}');
|
| 23 |
+
:root{{--k-paper:#f2efe6;--k-ink:#202622;--k-muted:#656962;--k-line:#d2d3cb;--k-navy:#102638;--k-green:#527664;--k-amber:#bd9552;--k-sage:#e9efe8;--k-link:#176b62}}
|
| 24 |
+
.gradio-container{{background:var(--k-paper)!important;color:var(--k-ink)!important;font-family:'IBM Plex Sans',sans-serif!important;max-width:1150px!important;margin:auto!important}}
|
| 25 |
+
.gradio-container .prose,.gradio-container label,.gradio-container input,.gradio-container textarea,.gradio-container button{{font-family:'IBM Plex Sans',sans-serif!important;color:var(--k-ink)}}
|
| 26 |
+
.gradio-container .prose a,.k-html a{{color:var(--k-link)}}
|
| 27 |
+
.gradio-container .block,.gradio-container .form{{background:transparent!important;border:0!important;box-shadow:none!important}}
|
| 28 |
+
.gradio-container input[type=text],.gradio-container textarea{{background:#fff!important;border:1px solid var(--k-line)!important;border-radius:3px!important;box-shadow:none!important}}
|
| 29 |
+
.gradio-container input[type=text]:focus-visible{{outline:3px solid #dc8437!important;outline-offset:3px}}
|
| 30 |
+
.gradio-container .k-primary{{background:var(--k-navy)!important;color:#fff!important;border:1px solid var(--k-navy)!important;border-radius:3px!important;font-weight:500}}
|
| 31 |
+
.gradio-container .k-primary:hover{{background:#24475a!important}}
|
| 32 |
+
.gradio-container .k-secondary{{background:#fff!important;border:1px solid var(--k-line)!important;color:var(--k-ink)!important;border-radius:3px!important}}
|
| 33 |
+
.gradio-container .k-secondary:hover{{border-color:#506d68!important;background:#f5f8f5!important}}
|
| 34 |
+
.gradio-container .k-status textarea{{font-family:'IBM Plex Mono',monospace!important;font-size:12px!important;color:var(--k-muted)!important;background:transparent!important;border:0!important;padding:0!important}}
|
| 35 |
+
.gradio-container .k-status label span{{display:none}}
|
| 36 |
+
.gradio-container .accordion,.gradio-container .k-accordion{{border:1px solid var(--k-line)!important;background:#fff!important;border-radius:0!important}}
|
| 37 |
+
|
| 38 |
+
.k-mono,.k-eyebrow{{font-family:'IBM Plex Mono',monospace;font-size:12px}}
|
| 39 |
+
.k-eyebrow{{letter-spacing:1px;text-transform:uppercase;color:var(--k-muted)}}
|
| 40 |
+
.k-eyebrow.accent{{color:var(--k-link)}}
|
| 41 |
+
.k-muted{{color:var(--k-muted)}}.k-small{{font-size:13px}}
|
| 42 |
+
|
| 43 |
+
.k-nav{{display:flex;justify-content:space-between;align-items:center;gap:20px;padding:18px 0;border-bottom:1px solid #afb4a9;margin-bottom:8px}}
|
| 44 |
+
.k-nav a{{color:var(--k-ink);text-decoration:none}}.k-nav .k-links{{display:flex;align-items:center;gap:22px;flex-wrap:wrap}}
|
| 45 |
+
.k-btn{{border:1px solid var(--k-line);background:#fff;padding:9px 14px;border-radius:3px;text-decoration:none;display:inline-block;color:var(--k-ink)}}
|
| 46 |
+
.k-btn.primary{{background:var(--k-navy);color:#fff!important;border-color:var(--k-navy)}}.k-btn.primary:hover{{background:#24475a}}
|
| 47 |
+
.k-hero{{max-width:750px;margin:22px 0 12px}}.k-hero h1{{font-size:34px;letter-spacing:-1px;line-height:1.2;margin:4px 0 6px;font-weight:500}}.k-hero p{{margin:6px 0 10px;color:var(--k-muted)}}
|
| 48 |
+
.k-hero .k-linklist{{display:flex;gap:18px;flex-wrap:wrap;font-size:13px}}
|
| 49 |
+
.k-grader{{background:var(--k-sage);border:1px solid #bdcbbb;padding:16px 20px 6px;margin:14px 0 22px}}.k-grader h3{{margin:0 0 4px;font-size:19px;font-weight:500}}.k-grader p{{margin:4px 0 8px;font-size:13px;color:var(--k-muted)}}
|
| 50 |
+
.k-section{{display:flex;justify-content:space-between;align-items:baseline;gap:16px;margin:20px 0 4px}}.k-section h2{{font-size:24px;letter-spacing:-.5px;margin:0;font-weight:500}}
|
| 51 |
+
|
| 52 |
+
.k-kpis{{display:grid;grid-template-columns:repeat(4,1fr);background:#fff;border:1px solid var(--k-line);margin:12px 0 16px}}
|
| 53 |
+
.k-kpis>div{{padding:12px 18px;border-right:1px solid var(--k-line)}}.k-kpis>div:last-child{{border:0}}
|
| 54 |
+
.k-kpis strong{{display:block;font-size:26px;font-weight:500;line-height:1.2}}.k-kpis span{{font-size:12px;color:var(--k-muted)}}
|
| 55 |
+
|
| 56 |
+
.k-cards{{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin:12px 0}}
|
| 57 |
+
.k-card{{background:#fff;border:1px solid var(--k-line);padding:17px;min-width:0;display:flex;flex-direction:column}}
|
| 58 |
+
.k-card h3{{font-size:21px;margin:8px 0 4px;font-weight:500}}
|
| 59 |
+
.k-pill{{display:inline-block;align-self:flex-start;padding:4px 9px;font-size:12px;border:0}}
|
| 60 |
+
.k-pill.review{{background:#fbedd4;color:#70501d}}.k-pill.pass{{background:#e8f0e9;color:#375b44}}.k-pill.blocked{{background:#f3dcd8;color:#6b2a22}}.k-pill.incomplete{{background:#ebebe6;color:#4b4f49}}
|
| 61 |
+
.k-card-id{{font:11px 'IBM Plex Mono',monospace;overflow-wrap:anywhere;min-height:32px;color:var(--k-muted)}}
|
| 62 |
+
.k-score{{min-height:82px;margin:10px 0 6px;border-bottom:1px solid var(--k-line);padding-bottom:10px}}
|
| 63 |
+
.k-score strong{{font-size:40px;font-weight:500;line-height:1.2;display:block}}.k-score strong.unestablished{{font-size:23px;margin:10px 0}}.k-score .k-small{{font-size:16px}}
|
| 64 |
+
.k-stats{{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin:6px 0 12px}}.k-stats strong{{display:block;font-size:21px;font-weight:500}}.k-stats span{{font-size:12px;color:var(--k-muted)}}
|
| 65 |
+
.k-bar{{height:8px;display:flex;margin:8px 0;border-radius:3px;overflow:hidden;background:#ebebe6}}
|
| 66 |
+
.k-bar span{{display:block;height:100%}}.k-bar .pass{{background:var(--k-green)}}.k-bar .review{{background:var(--k-amber)}}.k-bar .blocked{{background:#a6473a}}
|
| 67 |
+
.k-counts{{display:flex;gap:8px;flex-wrap:wrap;font-size:11px;justify-content:space-between;margin-bottom:12px}}
|
| 68 |
+
.k-note{{font-size:13px;min-height:58px;margin:0 0 12px}}
|
| 69 |
+
.k-meta{{font:11px/1.8 'IBM Plex Mono',monospace;border-top:1px solid var(--k-line);padding-top:12px;color:var(--k-muted);margin-top:auto;overflow-wrap:anywhere}}
|
| 70 |
+
.k-actions{{display:flex;align-items:flex-start;gap:4px;margin-top:10px;flex-wrap:wrap}}
|
| 71 |
+
.k-icon{{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;border:0;border-radius:5px;background:transparent;color:var(--k-navy);cursor:pointer;list-style:none}}
|
| 72 |
+
.k-icon:hover{{background:var(--k-sage)}}.k-icon:focus-visible{{outline:3px solid #dc8437;outline-offset:3px}}.k-icon::-webkit-details-marker{{display:none}}
|
| 73 |
+
.k-card details[open]{{flex-basis:100%}}.k-card details textarea{{width:100%;box-sizing:border-box;min-height:230px;font:12px/1.5 'IBM Plex Mono',monospace;border:1px solid var(--k-line)}}
|
| 74 |
+
.k-card details table{{width:100%;font-size:12px;border-collapse:collapse}}.k-card details td{{overflow-wrap:anywhere;padding:6px;border-bottom:1px solid var(--k-line);vertical-align:top}}
|
| 75 |
+
.k-foot{{font-size:12px;color:var(--k-muted);margin:4px 0 16px}}
|
| 76 |
+
.k-share{{border:1px solid #b5c7bb;background:var(--k-sage);padding:18px 22px;margin:22px 0 10px;display:flex;justify-content:space-between;align-items:center;gap:20px}}
|
| 77 |
+
.k-share h3{{margin:0 0 4px;font-size:19px;font-weight:500}}.k-share p{{margin:0;font-size:13px;color:var(--k-muted)}}
|
| 78 |
+
.k-footer{{font:12px 'IBM Plex Mono',monospace;color:var(--k-muted);padding:18px 0 8px;border-top:1px solid #afb4a9;margin-top:20px}}
|
| 79 |
+
@media(max-width:760px){{.k-nav{{flex-direction:column;align-items:flex-start}}.k-hero h1{{font-size:28px}}.k-kpis{{grid-template-columns:1fr 1fr}}.k-kpis>div:nth-child(2){{border-right:0}}.k-kpis>div:nth-child(-n+2){{border-bottom:1px solid var(--k-line)}}.k-cards{{grid-template-columns:1fr}}.k-score,.k-note,.k-card-id{{min-height:0}}.k-share{{flex-direction:column;align-items:flex-start}}}}
|
| 80 |
+
@media(pointer:coarse){{.k-icon{{width:44px;height:44px}}}}
|
| 81 |
'''
|
| 82 |
|
| 83 |
|
|
|
|
| 89 |
return f'<a class="k-icon" href="{escape(url, quote=True)}" title="{label}" aria-label="{label}" target="_blank" rel="noopener">{svg(kind)}</a>'
|
| 90 |
|
| 91 |
|
| 92 |
+
def header(base):
|
| 93 |
+
"""Site chrome matching the Hub nav: Hub → Search → GitHub → Method → Audit."""
|
| 94 |
+
return (f'<div class="k-html"><nav class="k-nav"><a class="k-mono" href="https://kalanos.ai/">KALANOS <span class="k-muted">/ ROBOTICS DATASET QUALITY</span></a>'
|
| 95 |
+
f'<div class="k-links k-mono"><a href="{base}/datasets">HUB</a><a href="{base}/search">SEARCH</a>'
|
| 96 |
+
f'<a href="https://github.com/KalanosAI" target="_blank" rel="noopener">GITHUB ↗</a><a href="https://kalanos.ai/methodology">METHOD</a>'
|
| 97 |
+
f'<a class="k-btn primary" href="{base}/dashboard">Audit your dataset</a></div></nav>'
|
| 98 |
+
f'<div class="k-hero"><div class="k-eyebrow accent">Public dataset assessments</div><h1>Kalanos · Robotics Dataset Quality</h1>'
|
| 99 |
+
f'<p>Check a LeRobot dataset. Explore and share its quality report.</p>'
|
| 100 |
+
f'<div class="k-linklist"><a href="{base}/datasets">Browse Kalanos Hub ↗</a>'
|
| 101 |
+
f'<a href="https://huggingface.co/collections/kalanosdev/robotics-dataset-quality-grades" target="_blank" rel="noopener">HF Collection ↗</a></div></div></div>')
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def grader_intro():
|
| 105 |
+
return ('<div class="k-html"><h3 style="margin:0 0 4px;font-size:19px;font-weight:500">Grade your dataset</h3>'
|
| 106 |
+
'<p class="k-small k-muted" style="margin:0 0 6px">Public LeRobot v2/v3 · numeric checks · video off. Up to 500 episodes, 128 MiB of numeric/metadata files, 3 minutes. Larger or private datasets: use the CLI.</p></div>')
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def section(title, note=''):
|
| 110 |
+
return f'<div class="k-html k-section"><h2>{escape(title)}</h2><span class="k-small k-muted">{escape(note)}</span></div>'
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def share_strip(base):
|
| 114 |
+
return (f'<div class="k-html"><div class="k-share"><div><h3>Make your assessment easy to share.</h3>'
|
| 115 |
+
f'<p>Open the card icon on any report, or the Markdown panel above, and paste the section into your HF dataset README.</p></div>'
|
| 116 |
+
f'<a class="k-btn" href="{base}/datasets">See all reports →</a></div>'
|
| 117 |
+
f'<footer class="k-footer">KALANOS / DATA QUALITY FOR PHYSICAL AI · <a href="{base}/datasets">Hub</a> · <a href="https://github.com/KalanosAI" target="_blank" rel="noopener">CLI</a> · <a href="https://kalanos.ai/methodology">Method</a></footer></div>')
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def kpis(payloads):
|
| 121 |
+
totals = {k: sum(p['counts'].get(k, 0) for p in payloads) for k in ('total', 'pass_count', 'review')}
|
| 122 |
+
tiles = [(len(payloads), 'Dataset reports'), (totals['total'], 'Episodes assessed'),
|
| 123 |
+
(totals['pass_count'], 'Episodes passed within scope'), (totals['review'], 'Episodes requiring review')]
|
| 124 |
+
return '<div class="k-kpis">' + ''.join(f'<div><strong>{n}</strong><span>{label}</span></div>' for n, label in tiles) + '</div>'
|
| 125 |
+
|
| 126 |
+
|
| 127 |
def card_payload(card, base):
|
| 128 |
d = card['decision_summary']
|
| 129 |
ref = card['source']['ref']
|
|
|
|
| 146 |
dataset_url='https://huggingface.co/datasets/' + quote(ref, safe='/'), report_url=None)
|
| 147 |
|
| 148 |
|
| 149 |
+
def _status(c):
|
| 150 |
+
if not c.get('inventory_complete') or c.get('unknown') or not c.get('total'):
|
| 151 |
+
return 'incomplete', 'Incomplete evidence'
|
| 152 |
+
if c.get('blocked'):
|
| 153 |
+
return 'blocked', 'Blocking findings'
|
| 154 |
+
if c.get('review'):
|
| 155 |
+
return 'review', 'Review required'
|
| 156 |
+
return 'pass', 'Pass within scope'
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def _bar(c):
|
| 160 |
+
total = c.get('total') or 0
|
| 161 |
+
if not total:
|
| 162 |
+
return ''
|
| 163 |
+
segs = ''.join(f'<span class="{k}" style="width:{100 * c.get(key, 0) / total:.1f}%"></span>'
|
| 164 |
+
for k, key in (('pass', 'pass_count'), ('review', 'review'), ('blocked', 'blocked')))
|
| 165 |
+
label = f'{c.get("pass_count", 0)} pass, {c.get("review", 0)} review, {c.get("blocked", 0)} blocked, {c.get("unknown", 0)} unknown'
|
| 166 |
+
counts = ''.join(f'<span>{c.get(key, 0)} {name}</span>' for key, name in (('pass_count', 'pass'), ('review', 'review'), ('blocked', 'blocked'), ('unknown', 'unknown')))
|
| 167 |
+
return f'<div class="k-bar" role="img" aria-label="{label}">{segs}</div><div class="k-counts">{counts}</div>'
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def _note(payload, status):
|
| 171 |
+
if status == 'pass':
|
| 172 |
+
return 'No blocking or review findings recorded within scope.'
|
| 173 |
+
if status == 'review':
|
| 174 |
+
return f'{payload["counts"].get("review", 0)} episode(s) carry findings that require review.'
|
| 175 |
+
if status == 'blocked':
|
| 176 |
+
return f'{payload["counts"].get("blocked", 0)} episode(s) have blocking findings.'
|
| 177 |
+
return 'Inventory incomplete or undetermined episodes; outcomes are not established.'
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
def _assessed(ts):
|
| 181 |
+
# "2026-10-05T12:12:03Z" -> "05 Oct 2026 · 12:12 UTC"; leave anything else alone.
|
| 182 |
+
try:
|
| 183 |
+
from datetime import datetime
|
| 184 |
+
dt = datetime.strptime(str(ts)[:19], '%Y-%m-%dT%H:%M:%S')
|
| 185 |
+
return dt.strftime('%d %b %Y · %H:%M UTC')
|
| 186 |
+
except ValueError:
|
| 187 |
+
return str(ts)
|
| 188 |
+
|
| 189 |
+
|
| 190 |
def render(payload, published=True):
|
| 191 |
c = payload['counts']
|
| 192 |
score = payload['readiness']
|
| 193 |
+
status, status_text = _status(c)
|
| 194 |
+
score_html = ('<strong class="unestablished">Not established</strong>' if score is None
|
| 195 |
+
else f'<strong>{score:g}<span class="k-small"> /100</span></strong>')
|
| 196 |
+
name = payload['dataset'].split('/', 1)[-1].replace('_', ' ')
|
| 197 |
actions = link(payload['report_url'], 'View full report', 'report') if published and payload.get('report_url') else ''
|
| 198 |
preview = '<table>' + ''.join(f'<tr><td>{escape(str(k))}</td><td>{escape(str(v))}</td></tr>' for k, v in payload['details']) + '</table>'
|
| 199 |
+
actions += (f'<details><summary class="k-icon" title="Preview and copy HF dataset card" aria-label="Preview and copy HF dataset card">{svg("card")}</summary>'
|
| 200 |
+
f'{preview}<p class="k-small">Select and copy this Markdown into your existing HF README. Copying does not publish.</p>'
|
| 201 |
+
f'<textarea readonly aria-label="HF dataset card Markdown">{escape(payload["markdown"])}</textarea></details>')
|
| 202 |
actions += link(payload['dataset_url'], 'Open dataset on Hugging Face', 'hf')
|
| 203 |
+
stats = ''.join(f'<div><strong>{v}</strong><span>{label}</span></div>' for v, label in [
|
| 204 |
+
(c.get('total', '—'), 'Episodes assessed'), (f'{c.get("pass_count", 0)}/{c.get("total", 0)}', 'Eligible episodes'),
|
| 205 |
+
(c.get('review', 0), 'Requiring review'), (c.get('blocked', 0), 'Blocked')])
|
| 206 |
+
kind = 'Published report' if published else 'Unpublished quick audit · video not assessed'
|
| 207 |
+
return (f'<article class="k-card"><span class="k-pill {status}">{status_text}</span><h3>{escape(name)}</h3>'
|
| 208 |
+
f'<div class="k-card-id">{escape(payload["dataset"])}</div>'
|
| 209 |
+
f'<div class="k-score"><span class="k-eyebrow">Technical readiness</span>{score_html}</div>'
|
| 210 |
+
f'<div class="k-stats">{stats}</div>{_bar(c)}'
|
| 211 |
+
f'<p class="k-note">{escape(_note(payload, status))}</p>'
|
| 212 |
+
f'<div class="k-meta">{escape(payload["requirements"])}<br>Assessed {escape(_assessed(payload["assessed_at"]))}<br>{kind}</div>'
|
| 213 |
+
f'<div class="k-actions">{actions}</div></article>')
|