cvpfus Codex commited on
Commit ·
eee651b
1
Parent(s): f32d42c
Add submission readiness rollup
Browse filesCo-authored-by: Codex <codex@openai.com>
- FIELD_NOTES.md +4 -0
- README.md +5 -1
- SUBMISSION.md +3 -0
- app.py +87 -0
- scripts/verify.py +33 -1
- static/app.css +10 -1
- static/app.js +34 -0
- static/index.html +5 -0
FIELD_NOTES.md
CHANGED
|
@@ -30,6 +30,8 @@ Runtime setup is also data-backed. `/api/runtime-setup` lists the app command, l
|
|
| 30 |
|
| 31 |
The judge runbook lives at `/api/demo-script`. It keeps the live presentation repeatable by pairing visible actions with API checks for health, model budget, runtime setup, runtime status, image descriptions, reader narration, and speech.
|
| 32 |
|
|
|
|
|
|
|
| 33 |
Image provenance lives in `/api/image-descriptions`. Each article illustration carries the planned FLUX.2 klein model id, prompt, seed, asset URL, and fallback status, and the session panel renders those receipts so the generated-image claim is inspectable.
|
| 34 |
|
| 35 |
Accessibility evidence lives at `/api/accessibility-audit`. It records the reader-mode choices that matter most for this prototype: semantic reading order, keyboard navigation, reader cursor state, shortcut safety, live narration, image alt text, transcript review, user-controlled playback, and fallback resilience.
|
|
@@ -77,4 +79,6 @@ Every model-facing backend path reports `elapsed_ms`. The frontend surfaces the
|
|
| 77 |
|
| 78 |
The app exposes `/api/award-evidence` and renders that data in the session panel, so the live demo can point directly to the hackathon bonus targets it is designed to satisfy.
|
| 79 |
|
|
|
|
|
|
|
| 80 |
`/api/runtime-status` keeps the demo honest: if llama.cpp or Kokoro is unavailable, the UI labels the fallback state instead of silently pretending that every model path is online.
|
|
|
|
| 30 |
|
| 31 |
The judge runbook lives at `/api/demo-script`. It keeps the live presentation repeatable by pairing visible actions with API checks for health, model budget, runtime setup, runtime status, image descriptions, reader narration, and speech.
|
| 32 |
|
| 33 |
+
Submission readiness lives at `/api/submission-readiness`. It aggregates the demo-critical checks into one payload: model budget, award evidence, custom frontend assets, runtime setup, accessibility audit, image receipts, and executable demo API checks.
|
| 34 |
+
|
| 35 |
Image provenance lives in `/api/image-descriptions`. Each article illustration carries the planned FLUX.2 klein model id, prompt, seed, asset URL, and fallback status, and the session panel renders those receipts so the generated-image claim is inspectable.
|
| 36 |
|
| 37 |
Accessibility evidence lives at `/api/accessibility-audit`. It records the reader-mode choices that matter most for this prototype: semantic reading order, keyboard navigation, reader cursor state, shortcut safety, live narration, image alt text, transcript review, user-controlled playback, and fallback resilience.
|
|
|
|
| 79 |
|
| 80 |
The app exposes `/api/award-evidence` and renders that data in the session panel, so the live demo can point directly to the hackathon bonus targets it is designed to satisfy.
|
| 81 |
|
| 82 |
+
The app also renders `/api/submission-readiness`, so the Field Notes story has a single rollup of which submission claims are currently backed by app evidence.
|
| 83 |
+
|
| 84 |
`/api/runtime-status` keeps the demo honest: if llama.cpp or Kokoro is unavailable, the UI labels the fallback state instead of silently pretending that every model path is online.
|
README.md
CHANGED
|
@@ -52,7 +52,7 @@ Start the app:
|
|
| 52 |
python app.py
|
| 53 |
```
|
| 54 |
|
| 55 |
-
Open the local URL printed by Gradio. The custom frontend calls `/api/reader-brain`, `/api/image-descriptions`, `/api/describe-image`, `/api/speak`, `/api/generate-image`, `/api/model-budget`, `/api/runtime-setup`, `/api/demo-script`,
|
| 56 |
|
| 57 |
Useful environment variables:
|
| 58 |
|
|
@@ -81,6 +81,8 @@ The verifier checks syntax, static assets, Space metadata consistency, determini
|
|
| 81 |
|
| 82 |
`/api/image-descriptions` includes image-generation provenance for every article illustration: the planned FLUX.2 klein model, prompt, seed, bundled asset URL, and fallback-ready status.
|
| 83 |
|
|
|
|
|
|
|
| 84 |
## Screen Reader Mode
|
| 85 |
|
| 86 |
The frontend builds a reading queue from semantic article nodes. When screen-reader mode is on:
|
|
@@ -120,6 +122,8 @@ Reader-brain, image-description, speech, and image-generation responses include
|
|
| 120 |
|
| 121 |
The session panel also renders a manifest-backed demo checklist for Tiny Titan, Llama Champion, Off-Brand, and Field Notes evidence. A model-budget panel reads `/api/model-budget` so judges can see each role's parameter count in the live app.
|
| 122 |
|
|
|
|
|
|
|
| 123 |
A runtime-plan panel reads `/api/runtime-setup` and summarizes each model path's runtime plus fallback, keeping the live demo honest about what is online and what is deterministic.
|
| 124 |
|
| 125 |
`/api/runtime-status` performs a short readiness check for llama.cpp and local speech dependencies, then reports which fallback paths are ready for a live demo.
|
|
|
|
| 52 |
python app.py
|
| 53 |
```
|
| 54 |
|
| 55 |
+
Open the local URL printed by Gradio. The custom frontend calls `/api/reader-brain`, `/api/image-descriptions`, `/api/describe-image`, `/api/speak`, `/api/generate-image`, `/api/model-budget`, `/api/runtime-setup`, `/api/demo-script`, `/api/accessibility-audit`, and `/api/submission-readiness`.
|
| 56 |
|
| 57 |
Useful environment variables:
|
| 58 |
|
|
|
|
| 81 |
|
| 82 |
`/api/image-descriptions` includes image-generation provenance for every article illustration: the planned FLUX.2 klein model, prompt, seed, bundled asset URL, and fallback-ready status.
|
| 83 |
|
| 84 |
+
`/api/submission-readiness` aggregates the judging receipts into one pass/fail payload covering model budget, award evidence, custom frontend assets, runtime setup, accessibility, image receipts, and demo API checks.
|
| 85 |
+
|
| 86 |
## Screen Reader Mode
|
| 87 |
|
| 88 |
The frontend builds a reading queue from semantic article nodes. When screen-reader mode is on:
|
|
|
|
| 122 |
|
| 123 |
The session panel also renders a manifest-backed demo checklist for Tiny Titan, Llama Champion, Off-Brand, and Field Notes evidence. A model-budget panel reads `/api/model-budget` so judges can see each role's parameter count in the live app.
|
| 124 |
|
| 125 |
+
A submission-readiness panel reads `/api/submission-readiness`, giving judges one compact rollup of the claims the live app can prove.
|
| 126 |
+
|
| 127 |
A runtime-plan panel reads `/api/runtime-setup` and summarizes each model path's runtime plus fallback, keeping the live demo honest about what is online and what is deterministic.
|
| 128 |
|
| 129 |
`/api/runtime-status` performs a short readiness check for llama.cpp and local speech dependencies, then reports which fallback paths are ready for a live demo.
|
SUBMISSION.md
CHANGED
|
@@ -43,6 +43,7 @@ The prototype is designed for a live hackathon demo: every model-facing path has
|
|
| 43 |
- `/api/accessibility-audit`: semantic queue, keyboard navigation, reader cursor state, shortcut safety, live narration, alt text, transcript, user control, and fallback evidence.
|
| 44 |
- `/api/demo-script`: repeatable judge runbook and API checks.
|
| 45 |
- `/api/image-descriptions`: generated article image descriptions plus prompt, seed, model, asset URL, and fallback status receipts.
|
|
|
|
| 46 |
|
| 47 |
The POST checks in `/api/demo-script` include sample JSON bodies for `/api/reader-brain` and `/api/speak`.
|
| 48 |
|
|
@@ -50,6 +51,8 @@ The accessibility audit also documents two reader-mode details that matter durin
|
|
| 50 |
|
| 51 |
The image receipts keep the generated-asset claim inspectable: each article illustration names the planned `black-forest-labs/FLUX.2-klein-4B` path, prompt, seed, and bundled fallback asset.
|
| 52 |
|
|
|
|
|
|
|
| 53 |
## Reliability notes
|
| 54 |
|
| 55 |
The demo remains navigable when local models are unavailable. The reader brain falls back to deterministic narration, image descriptions fall back to cached alt text, speech falls back to browser speech plus transcript, and generated images fall back to bundled article assets. Fallback states are labeled instead of hidden.
|
|
|
|
| 43 |
- `/api/accessibility-audit`: semantic queue, keyboard navigation, reader cursor state, shortcut safety, live narration, alt text, transcript, user control, and fallback evidence.
|
| 44 |
- `/api/demo-script`: repeatable judge runbook and API checks.
|
| 45 |
- `/api/image-descriptions`: generated article image descriptions plus prompt, seed, model, asset URL, and fallback status receipts.
|
| 46 |
+
- `/api/submission-readiness`: one pass/fail rollup for model budget, award evidence, custom frontend, runtime setup, accessibility, image receipts, and demo API checks.
|
| 47 |
|
| 48 |
The POST checks in `/api/demo-script` include sample JSON bodies for `/api/reader-brain` and `/api/speak`.
|
| 49 |
|
|
|
|
| 51 |
|
| 52 |
The image receipts keep the generated-asset claim inspectable: each article illustration names the planned `black-forest-labs/FLUX.2-klein-4B` path, prompt, seed, and bundled fallback asset.
|
| 53 |
|
| 54 |
+
The submission-readiness panel and API give judges a compact checklist for the whole build, so the live demo can move from individual receipts to an overall readiness view.
|
| 55 |
+
|
| 56 |
## Reliability notes
|
| 57 |
|
| 58 |
The demo remains navigable when local models are unavailable. The reader brain falls back to deterministic narration, image descriptions fall back to cached alt text, speech falls back to browser speech plus transcript, and generated images fall back to bundled article assets. Fallback states are labeled instead of hidden.
|
app.py
CHANGED
|
@@ -252,6 +252,7 @@ def demo_script_core() -> dict[str, Any]:
|
|
| 252 |
{"method": "GET", "path": "/api/runtime-status", "expect": "online or fallback-ready runtime labels"},
|
| 253 |
{"method": "GET", "path": "/api/accessibility-audit", "expect": "all reader-mode checks pass"},
|
| 254 |
{"method": "GET", "path": "/api/image-descriptions", "expect": "three article image descriptions"},
|
|
|
|
| 255 |
{
|
| 256 |
"method": "POST",
|
| 257 |
"path": "/api/reader-brain",
|
|
@@ -277,6 +278,82 @@ def demo_script_core() -> dict[str, Any]:
|
|
| 277 |
}
|
| 278 |
|
| 279 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
def accessibility_audit_core() -> dict[str, Any]:
|
| 281 |
checks = [
|
| 282 |
{
|
|
@@ -710,6 +787,11 @@ async def accessibility_audit() -> JSONResponse:
|
|
| 710 |
return _json(accessibility_audit_core())
|
| 711 |
|
| 712 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 713 |
@app.get("/api/runtime-status")
|
| 714 |
async def runtime_status() -> JSONResponse:
|
| 715 |
return _json(_runtime_status_core())
|
|
@@ -775,6 +857,11 @@ def accessibility_audit_api() -> str:
|
|
| 775 |
return json.dumps(accessibility_audit_core())
|
| 776 |
|
| 777 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 778 |
@app.api(name="speak")
|
| 779 |
def speak_api(text: str, voice: str = "af_heart", speed: float = 1.0) -> str:
|
| 780 |
return json.dumps(speak_core(text, voice, speed))
|
|
|
|
| 252 |
{"method": "GET", "path": "/api/runtime-status", "expect": "online or fallback-ready runtime labels"},
|
| 253 |
{"method": "GET", "path": "/api/accessibility-audit", "expect": "all reader-mode checks pass"},
|
| 254 |
{"method": "GET", "path": "/api/image-descriptions", "expect": "three article image descriptions"},
|
| 255 |
+
{"method": "GET", "path": "/api/submission-readiness", "expect": "all submission readiness checks pass"},
|
| 256 |
{
|
| 257 |
"method": "POST",
|
| 258 |
"path": "/api/reader-brain",
|
|
|
|
| 278 |
}
|
| 279 |
|
| 280 |
|
| 281 |
+
def submission_readiness_core() -> dict[str, Any]:
|
| 282 |
+
model_budget = model_budget_core()
|
| 283 |
+
runtime_setup = runtime_setup_core()
|
| 284 |
+
accessibility = accessibility_audit_core()
|
| 285 |
+
demo_script = demo_script_core()
|
| 286 |
+
image_descriptions = describe_article_images_core()
|
| 287 |
+
runtime_roles = {step["role"] for step in runtime_setup["steps"]}
|
| 288 |
+
expected_roles = set(MODEL_MANIFEST)
|
| 289 |
+
api_paths = {item["path"] for item in demo_script["api_checks"]}
|
| 290 |
+
checks = [
|
| 291 |
+
{
|
| 292 |
+
"id": "tiny_titan_budget",
|
| 293 |
+
"label": "Tiny Titan model budget",
|
| 294 |
+
"status": "pass" if model_budget["all_models_within_limit"] else "fail",
|
| 295 |
+
"evidence": f"{len(model_budget['models'])} model roles are at or below {TINY_TITAN_LIMIT_B}B parameters.",
|
| 296 |
+
},
|
| 297 |
+
{
|
| 298 |
+
"id": "award_targets",
|
| 299 |
+
"label": "Targeted award evidence",
|
| 300 |
+
"status": "pass" if len(AWARD_EVIDENCE) == 4 else "fail",
|
| 301 |
+
"evidence": "Tiny Titan, Llama Champion, Off-Brand, and Field Notes evidence is exposed.",
|
| 302 |
+
},
|
| 303 |
+
{
|
| 304 |
+
"id": "custom_frontend",
|
| 305 |
+
"label": "Custom frontend",
|
| 306 |
+
"status": "pass" if all((STATIC_DIR / name).exists() for name in ["index.html", "app.css", "app.js"]) else "fail",
|
| 307 |
+
"evidence": "The app serves custom HTML, CSS, and JavaScript through Gradio Server.",
|
| 308 |
+
},
|
| 309 |
+
{
|
| 310 |
+
"id": "runtime_setup",
|
| 311 |
+
"label": "Runtime setup",
|
| 312 |
+
"status": "pass" if runtime_roles == expected_roles else "fail",
|
| 313 |
+
"evidence": "Runtime setup covers reader brain, speech, vision, and image generation paths.",
|
| 314 |
+
},
|
| 315 |
+
{
|
| 316 |
+
"id": "reader_accessibility",
|
| 317 |
+
"label": "Reader accessibility",
|
| 318 |
+
"status": "pass" if accessibility["all_passed"] else "fail",
|
| 319 |
+
"evidence": f"{accessibility['passed_checks']} of {accessibility['total_checks']} accessibility checks pass.",
|
| 320 |
+
},
|
| 321 |
+
{
|
| 322 |
+
"id": "image_receipts",
|
| 323 |
+
"label": "Image receipts",
|
| 324 |
+
"status": "pass"
|
| 325 |
+
if all(
|
| 326 |
+
item.get("generation_model") == MODEL_MANIFEST["image_generation"]["id"] and isinstance(item.get("seed"), int)
|
| 327 |
+
for item in image_descriptions["descriptions"]
|
| 328 |
+
)
|
| 329 |
+
else "fail",
|
| 330 |
+
"evidence": "Every article illustration exposes prompt, seed, model, asset URL, and fallback status.",
|
| 331 |
+
},
|
| 332 |
+
{
|
| 333 |
+
"id": "demo_api_checks",
|
| 334 |
+
"label": "Demo API checks",
|
| 335 |
+
"status": "pass"
|
| 336 |
+
if {
|
| 337 |
+
"/api/model-budget",
|
| 338 |
+
"/api/runtime-setup",
|
| 339 |
+
"/api/accessibility-audit",
|
| 340 |
+
"/api/image-descriptions",
|
| 341 |
+
"/api/reader-brain",
|
| 342 |
+
"/api/speak",
|
| 343 |
+
}.issubset(api_paths)
|
| 344 |
+
else "fail",
|
| 345 |
+
"evidence": "The judge runbook includes GET evidence checks and executable POST sample bodies.",
|
| 346 |
+
},
|
| 347 |
+
]
|
| 348 |
+
return {
|
| 349 |
+
"ok": True,
|
| 350 |
+
"all_passed": all(check["status"] == "pass" for check in checks),
|
| 351 |
+
"passed_checks": sum(1 for check in checks if check["status"] == "pass"),
|
| 352 |
+
"total_checks": len(checks),
|
| 353 |
+
"checks": checks,
|
| 354 |
+
}
|
| 355 |
+
|
| 356 |
+
|
| 357 |
def accessibility_audit_core() -> dict[str, Any]:
|
| 358 |
checks = [
|
| 359 |
{
|
|
|
|
| 787 |
return _json(accessibility_audit_core())
|
| 788 |
|
| 789 |
|
| 790 |
+
@app.get("/api/submission-readiness")
|
| 791 |
+
async def submission_readiness() -> JSONResponse:
|
| 792 |
+
return _json(submission_readiness_core())
|
| 793 |
+
|
| 794 |
+
|
| 795 |
@app.get("/api/runtime-status")
|
| 796 |
async def runtime_status() -> JSONResponse:
|
| 797 |
return _json(_runtime_status_core())
|
|
|
|
| 857 |
return json.dumps(accessibility_audit_core())
|
| 858 |
|
| 859 |
|
| 860 |
+
@app.api(name="submission_readiness")
|
| 861 |
+
def submission_readiness_api() -> str:
|
| 862 |
+
return json.dumps(submission_readiness_core())
|
| 863 |
+
|
| 864 |
+
|
| 865 |
@app.api(name="speak")
|
| 866 |
def speak_api(text: str, voice: str = "af_heart", speed: float = 1.0) -> str:
|
| 867 |
return json.dumps(speak_core(text, voice, speed))
|
scripts/verify.py
CHANGED
|
@@ -39,6 +39,7 @@ def verify_static_assets() -> None:
|
|
| 39 |
assert_true('aria-live="polite"' in index_html, "Article should expose an aria-live narration region")
|
| 40 |
assert_true("transcriptLog" in index_html, "Article should expose a visible transcript log")
|
| 41 |
assert_true("imageReceiptList" in index_html, "Article should expose generated image receipts")
|
|
|
|
| 42 |
assert_true("function haltPlayback" in app_js, "Reader controls should expose a shared playback halt helper")
|
| 43 |
assert_true(
|
| 44 |
"haltPlayback({ clearAutoAdvance: false });" in app_js,
|
|
@@ -52,7 +53,13 @@ def verify_static_assets() -> None:
|
|
| 52 |
submission = (ROOT / "SUBMISSION.md").read_text(encoding="utf-8")
|
| 53 |
for target in ["Tiny Titan", "Llama Champion", "Off-Brand", "Field Notes"]:
|
| 54 |
assert_true(target in submission, f"Submission packet should mention {target}")
|
| 55 |
-
for endpoint in [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
assert_true(endpoint in submission, f"Submission packet should mention {endpoint}")
|
| 57 |
assert_true(
|
| 58 |
"nvidia/NVIDIA-Nemotron-3-Nano-4B-GGUF" in submission,
|
|
@@ -194,6 +201,8 @@ def verify_routes() -> None:
|
|
| 194 |
assert_true("autoAdvanceControl" in home.text, "Home route should include auto-advance control")
|
| 195 |
assert_true("transcriptLog" in home.text, "Home route should include transcript log")
|
| 196 |
assert_true("awardEvidenceList" in home.text, "Home route should include award evidence list")
|
|
|
|
|
|
|
| 197 |
assert_true("budgetStatus" in home.text, "Home route should include model budget status")
|
| 198 |
assert_true("modelBudgetList" in home.text, "Home route should include model budget list")
|
| 199 |
assert_true("runtimeSetupStatus" in home.text, "Home route should include runtime setup status")
|
|
@@ -309,6 +318,10 @@ def verify_routes() -> None:
|
|
| 309 |
any(item["path"] == "/api/accessibility-audit" for item in demo_payload["api_checks"]),
|
| 310 |
"Demo script should include the accessibility audit check",
|
| 311 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 312 |
reader_check = next(item for item in demo_payload["api_checks"] if item["path"] == "/api/reader-brain")
|
| 313 |
speech_check = next(item for item in demo_payload["api_checks"] if item["path"] == "/api/speak")
|
| 314 |
assert_true(reader_check["sample_body"]["mode"] == "narrate", "Reader-brain demo check should include a sample body")
|
|
@@ -351,6 +364,25 @@ def verify_routes() -> None:
|
|
| 351 |
"Accessibility audit should cover reader semantics, keyboard use, cursor state, shortcut safety, live narration, alt text, and transcript",
|
| 352 |
)
|
| 353 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 354 |
runtime = client.get("/api/runtime-status")
|
| 355 |
assert_true(runtime.status_code == 200, "Runtime status route should return 200")
|
| 356 |
runtime_payload = runtime.json()
|
|
|
|
| 39 |
assert_true('aria-live="polite"' in index_html, "Article should expose an aria-live narration region")
|
| 40 |
assert_true("transcriptLog" in index_html, "Article should expose a visible transcript log")
|
| 41 |
assert_true("imageReceiptList" in index_html, "Article should expose generated image receipts")
|
| 42 |
+
assert_true("submissionReadinessList" in index_html, "Article should expose submission readiness checks")
|
| 43 |
assert_true("function haltPlayback" in app_js, "Reader controls should expose a shared playback halt helper")
|
| 44 |
assert_true(
|
| 45 |
"haltPlayback({ clearAutoAdvance: false });" in app_js,
|
|
|
|
| 53 |
submission = (ROOT / "SUBMISSION.md").read_text(encoding="utf-8")
|
| 54 |
for target in ["Tiny Titan", "Llama Champion", "Off-Brand", "Field Notes"]:
|
| 55 |
assert_true(target in submission, f"Submission packet should mention {target}")
|
| 56 |
+
for endpoint in [
|
| 57 |
+
"/api/model-budget",
|
| 58 |
+
"/api/runtime-setup",
|
| 59 |
+
"/api/accessibility-audit",
|
| 60 |
+
"/api/demo-script",
|
| 61 |
+
"/api/submission-readiness",
|
| 62 |
+
]:
|
| 63 |
assert_true(endpoint in submission, f"Submission packet should mention {endpoint}")
|
| 64 |
assert_true(
|
| 65 |
"nvidia/NVIDIA-Nemotron-3-Nano-4B-GGUF" in submission,
|
|
|
|
| 201 |
assert_true("autoAdvanceControl" in home.text, "Home route should include auto-advance control")
|
| 202 |
assert_true("transcriptLog" in home.text, "Home route should include transcript log")
|
| 203 |
assert_true("awardEvidenceList" in home.text, "Home route should include award evidence list")
|
| 204 |
+
assert_true("submissionReadinessStatus" in home.text, "Home route should include submission readiness status")
|
| 205 |
+
assert_true("submissionReadinessList" in home.text, "Home route should include submission readiness list")
|
| 206 |
assert_true("budgetStatus" in home.text, "Home route should include model budget status")
|
| 207 |
assert_true("modelBudgetList" in home.text, "Home route should include model budget list")
|
| 208 |
assert_true("runtimeSetupStatus" in home.text, "Home route should include runtime setup status")
|
|
|
|
| 318 |
any(item["path"] == "/api/accessibility-audit" for item in demo_payload["api_checks"]),
|
| 319 |
"Demo script should include the accessibility audit check",
|
| 320 |
)
|
| 321 |
+
assert_true(
|
| 322 |
+
any(item["path"] == "/api/submission-readiness" for item in demo_payload["api_checks"]),
|
| 323 |
+
"Demo script should include the submission readiness check",
|
| 324 |
+
)
|
| 325 |
reader_check = next(item for item in demo_payload["api_checks"] if item["path"] == "/api/reader-brain")
|
| 326 |
speech_check = next(item for item in demo_payload["api_checks"] if item["path"] == "/api/speak")
|
| 327 |
assert_true(reader_check["sample_body"]["mode"] == "narrate", "Reader-brain demo check should include a sample body")
|
|
|
|
| 364 |
"Accessibility audit should cover reader semantics, keyboard use, cursor state, shortcut safety, live narration, alt text, and transcript",
|
| 365 |
)
|
| 366 |
|
| 367 |
+
readiness = client.get("/api/submission-readiness")
|
| 368 |
+
assert_true(readiness.status_code == 200, "Submission readiness route should return 200")
|
| 369 |
+
readiness_payload = readiness.json()
|
| 370 |
+
assert_true(readiness_payload["ok"], "Submission readiness payload should be ok")
|
| 371 |
+
assert_true(readiness_payload["all_passed"], "Submission readiness checks should pass")
|
| 372 |
+
assert_true(
|
| 373 |
+
{item["id"] for item in readiness_payload["checks"]}
|
| 374 |
+
>= {
|
| 375 |
+
"tiny_titan_budget",
|
| 376 |
+
"award_targets",
|
| 377 |
+
"custom_frontend",
|
| 378 |
+
"runtime_setup",
|
| 379 |
+
"reader_accessibility",
|
| 380 |
+
"image_receipts",
|
| 381 |
+
"demo_api_checks",
|
| 382 |
+
},
|
| 383 |
+
"Submission readiness should aggregate model, award, frontend, runtime, accessibility, image, and demo evidence",
|
| 384 |
+
)
|
| 385 |
+
|
| 386 |
runtime = client.get("/api/runtime-status")
|
| 387 |
assert_true(runtime.status_code == 200, "Runtime status route should return 200")
|
| 388 |
runtime_payload = runtime.json()
|
static/app.css
CHANGED
|
@@ -290,6 +290,14 @@ dd {
|
|
| 290 |
margin-top: 22px;
|
| 291 |
}
|
| 292 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 293 |
.budget-header,
|
| 294 |
.image-receipt-header {
|
| 295 |
display: flex;
|
|
@@ -309,7 +317,8 @@ dd {
|
|
| 309 |
|
| 310 |
.budget-header span,
|
| 311 |
.runtime-header span,
|
| 312 |
-
.image-receipt-header span
|
|
|
|
| 313 |
color: var(--accent-strong);
|
| 314 |
font-size: 0.76rem;
|
| 315 |
font-weight: 850;
|
|
|
|
| 290 |
margin-top: 22px;
|
| 291 |
}
|
| 292 |
|
| 293 |
+
.submission-header {
|
| 294 |
+
display: flex;
|
| 295 |
+
align-items: center;
|
| 296 |
+
justify-content: space-between;
|
| 297 |
+
gap: 10px;
|
| 298 |
+
margin-top: 22px;
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
.budget-header,
|
| 302 |
.image-receipt-header {
|
| 303 |
display: flex;
|
|
|
|
| 317 |
|
| 318 |
.budget-header span,
|
| 319 |
.runtime-header span,
|
| 320 |
+
.image-receipt-header span,
|
| 321 |
+
.submission-header span {
|
| 322 |
color: var(--accent-strong);
|
| 323 |
font-size: 0.76rem;
|
| 324 |
font-weight: 850;
|
static/app.js
CHANGED
|
@@ -18,6 +18,8 @@ const transcriptLog = document.querySelector("#transcriptLog");
|
|
| 18 |
const copyTranscriptButton = document.querySelector("#copyTranscriptButton");
|
| 19 |
const clearTranscriptButton = document.querySelector("#clearTranscriptButton");
|
| 20 |
const awardEvidenceList = document.querySelector("#awardEvidenceList");
|
|
|
|
|
|
|
| 21 |
const budgetStatus = document.querySelector("#budgetStatus");
|
| 22 |
const modelBudgetList = document.querySelector("#modelBudgetList");
|
| 23 |
const runtimeSetupStatus = document.querySelector("#runtimeSetupStatus");
|
|
@@ -224,6 +226,37 @@ async function loadAwardEvidence() {
|
|
| 224 |
}
|
| 225 |
}
|
| 226 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
function roleLabel(role) {
|
| 228 |
return role
|
| 229 |
.split("_")
|
|
@@ -651,6 +684,7 @@ controls.play.addEventListener("click", () => {
|
|
| 651 |
|
| 652 |
loadManifest();
|
| 653 |
loadAwardEvidence();
|
|
|
|
| 654 |
loadModelBudget();
|
| 655 |
loadRuntimeSetup();
|
| 656 |
loadRuntimeStatus();
|
|
|
|
| 18 |
const copyTranscriptButton = document.querySelector("#copyTranscriptButton");
|
| 19 |
const clearTranscriptButton = document.querySelector("#clearTranscriptButton");
|
| 20 |
const awardEvidenceList = document.querySelector("#awardEvidenceList");
|
| 21 |
+
const submissionReadinessStatus = document.querySelector("#submissionReadinessStatus");
|
| 22 |
+
const submissionReadinessList = document.querySelector("#submissionReadinessList");
|
| 23 |
const budgetStatus = document.querySelector("#budgetStatus");
|
| 24 |
const modelBudgetList = document.querySelector("#modelBudgetList");
|
| 25 |
const runtimeSetupStatus = document.querySelector("#runtimeSetupStatus");
|
|
|
|
| 226 |
}
|
| 227 |
}
|
| 228 |
|
| 229 |
+
async function loadSubmissionReadiness() {
|
| 230 |
+
try {
|
| 231 |
+
const payload = await postJson("/api/submission-readiness");
|
| 232 |
+
submissionReadinessStatus.textContent = payload.all_passed
|
| 233 |
+
? `${payload.passed_checks}/${payload.total_checks} pass`
|
| 234 |
+
: "Review needed";
|
| 235 |
+
submissionReadinessList.innerHTML = payload.checks
|
| 236 |
+
.map((item) => `
|
| 237 |
+
<li>
|
| 238 |
+
<div class="award-title">
|
| 239 |
+
<span>${escapeHtml(item.label)}</span>
|
| 240 |
+
<span class="award-status">${escapeHtml(item.status)}</span>
|
| 241 |
+
</div>
|
| 242 |
+
<p class="award-evidence">${escapeHtml(item.evidence)}</p>
|
| 243 |
+
</li>
|
| 244 |
+
`)
|
| 245 |
+
.join("");
|
| 246 |
+
} catch {
|
| 247 |
+
submissionReadinessStatus.textContent = "Unavailable";
|
| 248 |
+
submissionReadinessList.innerHTML = `
|
| 249 |
+
<li>
|
| 250 |
+
<div class="award-title">
|
| 251 |
+
<span>Submission readiness</span>
|
| 252 |
+
<span class="award-status">offline</span>
|
| 253 |
+
</div>
|
| 254 |
+
<p class="award-evidence">Submission readiness evidence is unavailable.</p>
|
| 255 |
+
</li>
|
| 256 |
+
`;
|
| 257 |
+
}
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
function roleLabel(role) {
|
| 261 |
return role
|
| 262 |
.split("_")
|
|
|
|
| 684 |
|
| 685 |
loadManifest();
|
| 686 |
loadAwardEvidence();
|
| 687 |
+
loadSubmissionReadiness();
|
| 688 |
loadModelBudget();
|
| 689 |
loadRuntimeSetup();
|
| 690 |
loadRuntimeStatus();
|
static/index.html
CHANGED
|
@@ -126,6 +126,11 @@
|
|
| 126 |
<h3>Demo Checklist</h3>
|
| 127 |
</div>
|
| 128 |
<ul id="awardEvidenceList" class="award-list" aria-label="Hackathon bonus evidence"></ul>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
<div class="budget-header">
|
| 130 |
<h3>Model Budget</h3>
|
| 131 |
<span id="budgetStatus">Checking</span>
|
|
|
|
| 126 |
<h3>Demo Checklist</h3>
|
| 127 |
</div>
|
| 128 |
<ul id="awardEvidenceList" class="award-list" aria-label="Hackathon bonus evidence"></ul>
|
| 129 |
+
<div class="submission-header">
|
| 130 |
+
<h3>Submission Readiness</h3>
|
| 131 |
+
<span id="submissionReadinessStatus">Checking</span>
|
| 132 |
+
</div>
|
| 133 |
+
<ul id="submissionReadinessList" class="award-list" aria-label="Submission readiness checks"></ul>
|
| 134 |
<div class="budget-header">
|
| 135 |
<h3>Model Budget</h3>
|
| 136 |
<span id="budgetStatus">Checking</span>
|