From 69fc140504fa7374f19a40c557ee2ea9df7003d2 Mon Sep 17 00:00:00 2001 From: hyeokjun32 Date: Wed, 29 Apr 2026 23:59:57 +0900 Subject: [PATCH 01/10] fix: clarify local studio pipeline states --- inferedgelab/studio/static/app.js | 11 ++++++++--- inferedgelab/studio/static/index.html | 4 ++-- inferedgelab/studio/static/style.css | 5 +++++ tests/test_studio_routes.py | 3 +++ 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/inferedgelab/studio/static/app.js b/inferedgelab/studio/static/app.js index 4dd85a0..b14c15d 100644 --- a/inferedgelab/studio/static/app.js +++ b/inferedgelab/studio/static/app.js @@ -544,11 +544,13 @@ function pipelineStatus() { const anyRunning = currentJobs.some((job) => job.status === "queued" || job.status === "running"); const anyCompleted = currentJobs.some((job) => job.status === "completed") || Boolean(importedResult); const hasCompareDecision = Boolean(activeDecision); + const hasImportedEvidence = Boolean(importedResult); + const hasGuardEvidence = Boolean(activeDecision?.guard_status); return { forge: importedResult ? "completed" : "idle", - runtime: anyRunning ? "running" : anyCompleted ? "completed" : "idle", - lab: hasCompareDecision ? "completed" : anyRunning ? "running" : "idle", - aiguard: hasCompareDecision && activeDecision?.guard_status ? "completed" : "idle", + runtime: hasImportedEvidence || anyCompleted ? "completed" : anyRunning ? "running" : "idle", + lab: hasCompareDecision || hasImportedEvidence ? "completed" : anyRunning ? "running" : "idle", + aiguard: hasGuardEvidence ? "completed" : "optional", }; } @@ -569,6 +571,9 @@ function normalizeState(state) { if (value === "running") { return "running"; } + if (value === "optional" || value === "skipped") { + return "optional"; + } return "idle"; } diff --git a/inferedgelab/studio/static/index.html b/inferedgelab/studio/static/index.html index a0d3159..14f525d 100644 --- a/inferedgelab/studio/static/index.html +++ b/inferedgelab/studio/static/index.html @@ -132,7 +132,7 @@ } } - +
@@ -275,6 +275,6 @@

Future Work

- + diff --git a/inferedgelab/studio/static/style.css b/inferedgelab/studio/static/style.css index 377f181..30f3712 100644 --- a/inferedgelab/studio/static/style.css +++ b/inferedgelab/studio/static/style.css @@ -261,6 +261,11 @@ body { color: var(--red); } +.state-pill.optional { + border-color: rgba(148, 163, 184, 0.28); + color: var(--muted); +} + .form-stack, .list-stack { display: grid; diff --git a/tests/test_studio_routes.py b/tests/test_studio_routes.py index 06f6bfc..9b84154 100644 --- a/tests/test_studio_routes.py +++ b/tests/test_studio_routes.py @@ -71,10 +71,13 @@ def test_studio_static_assets_include_redesigned_ui_contracts(): assert "responseErrorMessage" in app_text assert "runtimeModelName" in app_text assert "Same backend" in app_text + assert "hasImportedEvidence" in app_text + assert 'aiguard: hasGuardEvidence ? "completed" : "optional"' in app_text assert "#0b0f14" in style_text assert "grid-template-columns" in style_text assert ".form-stack button" in style_text assert ".tool-card" in style_text + assert ".state-pill.optional" in style_text def test_studio_app_preserves_selected_job_detail_contract(): From bcf6b88394b979815acb9cc49eb89fb03bf4176d Mon Sep 17 00:00:00 2001 From: hyeokjun32 Date: Thu, 30 Apr 2026 01:26:05 +0900 Subject: [PATCH 02/10] fix: stabilize local studio web workflow --- inferedgelab/studio/routes.py | 32 ++++- inferedgelab/studio/static/app.js | 173 +++++++++++++++++++++----- inferedgelab/studio/static/index.html | 11 +- inferedgelab/studio/static/style.css | 48 +++++++ tests/test_studio_routes.py | 104 +++++++++++++--- 5 files changed, 318 insertions(+), 50 deletions(-) diff --git a/inferedgelab/studio/routes.py b/inferedgelab/studio/routes.py index 3299f3d..6cdf126 100644 --- a/inferedgelab/studio/routes.py +++ b/inferedgelab/studio/routes.py @@ -213,12 +213,16 @@ def _build_imported_compare_response(base: dict[str, Any], new: dict[str, Any]) def _with_compare_keys(result: dict[str, Any]) -> dict[str, Any]: enriched = dict(result) if not enriched.get("backend_key"): - engine = enriched.get("engine") or enriched.get("backend") - device = enriched.get("device") or enriched.get("device_name") + engine = _first_display_value( + enriched.get("engine_backend"), + enriched.get("engine"), + enriched.get("backend"), + ) + device = _first_display_value(enriched.get("device_name"), enriched.get("device")) if engine and device: enriched["backend_key"] = f"{engine}__{device}" if not enriched.get("compare_key"): - model = enriched.get("model") + model = _first_display_value(enriched.get("model_name"), enriched.get("model")) batch = enriched.get("batch") height = enriched.get("height") width = enriched.get("width") @@ -226,3 +230,25 @@ def _with_compare_keys(result: dict[str, Any]) -> dict[str, Any]: if model and batch and height and width and precision: enriched["compare_key"] = f"{model}__b{batch}__h{height}w{width}__{precision}" return enriched + + +def _first_display_value(*values: Any) -> str: + for value in values: + display_value = _display_value(value) + if display_value: + return display_value + return "" + + +def _display_value(value: Any) -> str: + if value is None or value == "": + return "" + if isinstance(value, dict): + return _first_display_value( + value.get("name"), + value.get("backend"), + value.get("path"), + value.get("status"), + value.get("id"), + ) + return str(value) diff --git a/inferedgelab/studio/static/app.js b/inferedgelab/studio/static/app.js index b14c15d..87abdea 100644 --- a/inferedgelab/studio/static/app.js +++ b/inferedgelab/studio/static/app.js @@ -76,27 +76,41 @@ function setEmpty(selector, label = "No data available") { async function fetchJson(url) { assertHttpStudio(); - const response = await fetch(url, { headers: { Accept: "application/json" } }); - if (!response.ok) { - throw new Error(await responseErrorMessage(response)); + try { + const response = await fetch(url, { headers: { Accept: "application/json" } }); + if (!response.ok) { + throw new Error(await responseErrorMessage(response)); + } + return await parseJsonResponse(response); + } catch (error) { + if (error instanceof SyntaxError) { + throw new Error(`Invalid JSON response from ${url}.`); + } + throw error; } - return response.json(); } async function postJson(url, payload) { assertHttpStudio(); - const response = await fetch(url, { - method: "POST", - headers: { - Accept: "application/json", - "Content-Type": "application/json", - }, - body: JSON.stringify(payload), - }); - if (!response.ok) { - throw new Error(await responseErrorMessage(response)); + try { + const response = await fetch(url, { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify(payload), + }); + if (!response.ok) { + throw new Error(await responseErrorMessage(response)); + } + return await parseJsonResponse(response); + } catch (error) { + if (error instanceof SyntaxError) { + throw new Error(`Invalid JSON response from ${url}.`); + } + throw error; } - return response.json(); } function assertHttpStudio() { @@ -122,6 +136,14 @@ async function responseErrorMessage(response) { return fallback; } +async function parseJsonResponse(response) { + try { + return await response.json(); + } catch (error) { + throw new SyntaxError("Response body is not valid JSON."); + } +} + async function loadJobs(preferredJobId = selectedJobId) { setLoading("#job-list"); try { @@ -143,8 +165,8 @@ async function loadJobs(preferredJobId = selectedJobId) { currentJobs = []; selectedJob = null; selectedJobId = null; - setEmpty("#job-list"); - renderJobDetail(); + setEmpty("#job-list", `Error: ${formatError(error)}`); + renderJobDetail(`Unable to load jobs: ${formatError(error)}`); } renderPipeline(); } @@ -168,7 +190,7 @@ async function loadJobDetail(jobId) { updateDecision(extractDecision(selectedJob)); } catch (error) { selectedJob = null; - renderJobDetail(`Unable to load ${jobId}.`); + renderJobDetail(`Unable to load ${jobId}: ${formatError(error)}`); } renderPipeline(); } @@ -182,11 +204,11 @@ async function loadCompare() { if (compareData.status === "empty") { activeDecision = null; renderDecision(decision); - } else if (!activeDecision) { + } else { updateDecision(decision); } } catch (error) { - compareData = null; + compareData = { status: "error", error: formatError(error) }; renderCompare(); if (!activeDecision) { updateDecision(null); @@ -226,8 +248,9 @@ async function runModel() { async function importResult() { const button = document.querySelector("#import-button"); const jsonPath = document.querySelector("#import-json-path").value.trim(); - if (!jsonPath) { - setStatus("#import-status", "Error: enter a result path.", "error"); + const jsonPayload = document.querySelector("#import-json-payload").value.trim(); + if (!jsonPath && !jsonPayload) { + setStatus("#import-status", "Error: enter a result path or JSON payload.", "error"); return; } @@ -236,7 +259,7 @@ async function importResult() { setStatus("#import-status", "Loading: importing Runtime result...", "loading"); renderPipeline(); try { - const payload = await postJson("/studio/api/import", { path: jsonPath }); + const payload = await postJson("/studio/api/import", buildImportPayload(jsonPath, jsonPayload)); importedResult = payload.result; setStatus( "#import-status", @@ -246,8 +269,10 @@ async function importResult() { "success", ); setState("#import-state", "completed"); + renderImportEvidence(payload); renderImportedResult(); await loadCompare(); + renderPipeline(); } catch (error) { setStatus("#import-status", `Error: ${formatError(error)}`, "error"); setState("#import-state", "idle"); @@ -257,6 +282,17 @@ async function importResult() { } } +function buildImportPayload(path, jsonPayload) { + if (jsonPayload) { + try { + return { result: JSON.parse(jsonPayload) }; + } catch (error) { + throw new Error("JSON payload is not valid JSON."); + } + } + return { path }; +} + async function loadJetsonCommand() { const textarea = document.querySelector("#jetson-command"); setState("#jetson-state", "running"); @@ -307,6 +343,34 @@ function renderPipeline() { }); } +function renderImportEvidence(payload) { + const target = document.querySelector("#import-evidence"); + if (!target) { + return; + } + target.replaceChildren(); + + const result = payload?.result || {}; + const missing = missingResultKeys(result); + target.append( + evidenceItem("model", runtimeModelName(result)), + evidenceItem("backend", normalizedBackendKey(result) || runtimeBackendName(result)), + evidenceItem("compare", result.compare_key || fallbackLabel("compare_key")), + evidenceItem("mean", result.mean_ms === undefined ? "-" : `${formatNumber(result.mean_ms)} ms`), + ); + if (missing.length > 0) { + target.append( + createElement("p", "evidence-warning", `Fallback metadata: missing ${missing.join(", ")}.`), + ); + } +} + +function evidenceItem(label, value) { + const item = createElement("div", "evidence-item"); + item.append(createElement("span", "metric-name", label), createElement("strong", "", formatValue(value))); + return item; +} + function renderRunPanel() { document.querySelector("#run-button").onclick = runModel; document.querySelector("#import-button").onclick = importResult; @@ -422,6 +486,11 @@ function renderCompare() { const target = document.querySelector("#compare-panel"); target.replaceChildren(); + if (compareData?.status === "error") { + target.append(errorCompareCard(compareData.error)); + return; + } + if (!compareData || compareData.status === "empty") { target.append(emptyCompareCard()); return; @@ -430,6 +499,7 @@ function renderCompare() { const base = compareData.base || compareData.data?.base || {}; const newer = compareData.new || compareData.data?.new || {}; const result = compareData.result || compareData.data?.result || {}; + const judgement = compareData.judgement || compareData.data?.judgement || {}; const meanMetric = result.metrics?.mean_ms || {}; const speedup = result.speedup || result.backend_comparison?.speedup || calculateSpeedup(base, newer); const tensorRt = findResultByBackend([base, newer], "tensorrt"); @@ -439,7 +509,7 @@ function renderCompare() { target.append( compareMetricCard("TensorRT", tensorRt?.mean_ms, normalizedBackendKey(tensorRt) || "tensorrt"), compareMetricCard("ONNX Runtime", onnx?.mean_ms, normalizedBackendKey(onnx) || "onnxruntime"), - compareSummaryCard(meanMetric, speedup, base, newer, sameBackend), + compareSummaryCard(meanMetric, speedup, base, newer, sameBackend, judgement.overall), ); } @@ -462,7 +532,7 @@ function renderDecision(decision) { target.append( createElement("p", "caption", "Decision"), createElement("h3", "", decisionName.toUpperCase()), - createElement("p", "body-text", decision.reason || "-"), + createElement("p", "body-text", decisionReason(decision)), createElement("p", "caption", decision.notes || decision.recommended_action || ""), ); } @@ -494,13 +564,13 @@ function compareMetricCard(label, meanMs, backendKey) { return card; } -function compareSummaryCard(metric, speedup, base, newer, sameBackend = false) { - const card = createElement("article", "compare-card highlight"); +function compareSummaryCard(metric, speedup, base, newer, sameBackend = false, overall = "unknown") { + const card = createElement("article", `compare-card highlight ${compareTone(overall)}`); const diff = formatLatencyDiff(metric); const faster = sameBackend ? "Same backend" : speedup ? `${formatNumber(speedup)}x faster` : "speedup unavailable"; const note = sameBackend ? "Import a TensorRT and an ONNX Runtime result to compare backend speedup." : `Latency diff: ${diff}`; card.append( - createElement("p", "caption", "Latency comparison"), + createElement("p", "caption", `Latency comparison / ${overall || "unknown"}`), createElement("h3", "", faster), createElement("p", "body-text", note), createElement("p", "caption", `${normalizedBackendKey(base) || "-"} -> ${normalizedBackendKey(newer) || "-"}`), @@ -508,6 +578,16 @@ function compareSummaryCard(metric, speedup, base, newer, sameBackend = false) { return card; } +function errorCompareCard(message) { + const card = createElement("article", "compare-card empty error-card"); + card.append( + createElement("p", "caption", "Compare error"), + createElement("h3", "", "Unable to load compare data"), + createElement("p", "body-text", message || "No error detail was provided."), + ); + return card; +} + function emptyCompareCard() { const card = createElement("article", "compare-card empty"); card.append( @@ -525,6 +605,14 @@ function extractDecision(payload) { return payload.deployment_decision || payload.result?.deployment_decision || payload.data?.deployment_decision || null; } +function decisionReason(decision) { + const decisionName = String(decision?.decision || "unknown").toLowerCase(); + if (decisionName === "unknown" && !decision?.guard_status) { + return "AIGuard evidence not provided."; + } + return decision?.reason || "-"; +} + function extractRuntimeResult(job) { const result = job?.result; if (!result) { @@ -641,6 +729,35 @@ function displayValue(value) { return String(value); } +function missingResultKeys(result = {}) { + const missing = []; + if (!result.compare_key) { + missing.push("compare_key"); + } + if (!normalizedBackendKey(result)) { + missing.push("backend_key"); + } + return missing; +} + +function fallbackLabel(key) { + return `${key} unavailable`; +} + +function compareTone(overall) { + const value = String(overall || "").toLowerCase(); + if (value.includes("improvement")) { + return "improvement"; + } + if (value.includes("regression")) { + return "regression"; + } + if (value.includes("neutral")) { + return "neutral"; + } + return "unknown"; +} + function formatLatencyDiff(metric) { if (!metric || Object.keys(metric).length === 0) { return "-"; diff --git a/inferedgelab/studio/static/index.html b/inferedgelab/studio/static/index.html index 14f525d..882bd4f 100644 --- a/inferedgelab/studio/static/index.html +++ b/inferedgelab/studio/static/index.html @@ -132,7 +132,7 @@ } } - +
@@ -192,8 +192,15 @@

Runtime result JSON

+ +

+
@@ -275,6 +282,6 @@

Future Work

- + diff --git a/inferedgelab/studio/static/style.css b/inferedgelab/studio/static/style.css index 30f3712..a602fb1 100644 --- a/inferedgelab/studio/static/style.css +++ b/inferedgelab/studio/static/style.css @@ -417,6 +417,37 @@ body { margin: 0; } +.evidence-summary { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; +} + +.evidence-item { + border: 1px solid var(--line); + border-radius: 10px; + background: rgba(15, 23, 42, 0.78); + padding: 10px; +} + +.evidence-item strong { + display: block; + margin-top: 6px; + color: var(--ink); + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", + monospace; + font-size: 0.82rem; + overflow-wrap: anywhere; +} + +.evidence-warning { + grid-column: 1 / -1; + margin: 0; + color: var(--yellow); + font-size: 0.82rem; + line-height: 1.45; +} + .metric-name, .metric-value { display: block; @@ -446,6 +477,19 @@ body { background: linear-gradient(180deg, rgba(34, 211, 238, 0.09), var(--panel)); } +.compare-card.improvement { + border-color: rgba(34, 197, 94, 0.32); +} + +.compare-card.regression, +.error-card { + border-color: rgba(239, 68, 68, 0.32); +} + +.compare-card.neutral { + border-color: rgba(234, 179, 8, 0.32); +} + .compare-card.empty { grid-column: 1 / -1; } @@ -517,4 +561,8 @@ body { .metric-grid { grid-template-columns: 1fr; } + + .evidence-summary { + grid-template-columns: 1fr; + } } diff --git a/tests/test_studio_routes.py b/tests/test_studio_routes.py index 9b84154..cf65750 100644 --- a/tests/test_studio_routes.py +++ b/tests/test_studio_routes.py @@ -15,6 +15,33 @@ def _get_route(app, path: str): raise AssertionError(f"route not found: {path}") +def _runtime_result( + *, + engine: str = "onnxruntime", + device: str = "cpu", + mean_ms: float = 45.0, + p99_ms: float = 50.0, + backend_key: str | None = "onnxruntime__cpu", +) -> dict: + result = { + "runtime_role": "runtime-result", + "model": "yolov8n", + "engine": engine, + "device": device, + "precision": "fp32", + "batch": 1, + "height": 640, + "width": 640, + "mean_ms": mean_ms, + "p99_ms": p99_ms, + "timestamp": "2026-04-29T12:00:00Z", + "compare_key": "yolov8n__b1__h640w640__fp32", + } + if backend_key is not None: + result["backend_key"] = backend_key + return result + + def test_studio_route_returns_local_studio_html(): app = api.create_app() route = _get_route(app, "/studio") @@ -32,9 +59,10 @@ def test_studio_route_returns_local_studio_html(): assert "Import" in html assert "Jetson Helper" in html assert 'data-critical="studio-dark"' in html - assert 'href="/studio/static/style.css?v=' in html - assert 'src="/studio/static/app.js?v=' in html + assert 'href="/studio/static/style.css?v=9"' in html + assert 'src="/studio/static/app.js?v=9"' in html assert 'value="results/latest.json"' in html + assert 'id="import-json-payload"' in html def test_studio_static_assets_are_served(): @@ -69,6 +97,10 @@ def test_studio_static_assets_include_redesigned_ui_contracts(): assert "DOMContentLoaded" in app_text assert "Open Studio from http://127.0.0.1:8000/studio" in app_text assert "responseErrorMessage" in app_text + assert "parseJsonResponse" in app_text + assert "renderImportEvidence" in app_text + assert "AIGuard evidence not provided" in app_text + assert "compareTone" in app_text assert "runtimeModelName" in app_text assert "Same backend" in app_text assert "hasImportedEvidence" in app_text @@ -78,6 +110,8 @@ def test_studio_static_assets_include_redesigned_ui_contracts(): assert ".form-stack button" in style_text assert ".tool-card" in style_text assert ".state-pill.optional" in style_text + assert ".evidence-summary" in style_text + assert ".compare-card.improvement" in style_text def test_studio_app_preserves_selected_job_detail_contract(): @@ -117,9 +151,11 @@ def test_studio_compare_latest_api_returns_json_structure(): response = route.endpoint(request=request) assert route.status_code is None + assert response["status"] == "empty" assert "deployment_decision" in response assert "data" in response assert response.get("source") in {None, "/api/compare-latest"} + assert response["deployment_decision"]["decision"] == "unknown" def test_studio_run_api_creates_analyze_job(): @@ -158,21 +194,7 @@ def test_studio_import_api_accepts_runtime_result_json(): app = api.create_app() route = _get_route(app, "/studio/api/import") request = SimpleNamespace(app=app) - result = { - "runtime_role": "runtime-result", - "model": "yolov8n", - "engine": "onnxruntime", - "device": "cpu", - "precision": "fp32", - "batch": 1, - "height": 640, - "width": 640, - "mean_ms": 45.0, - "p99_ms": 50.0, - "timestamp": "2026-04-29T12:00:00Z", - "compare_key": "yolov8n__b1__h640w640__fp32", - "backend_key": "onnxruntime__cpu", - } + result = _runtime_result() response = route.endpoint(request=request, payload={"result": result}) @@ -182,6 +204,20 @@ def test_studio_import_api_accepts_runtime_result_json(): assert response["compare_ready"] is False +def test_studio_import_api_generates_missing_compare_keys(): + app = api.create_app() + route = _get_route(app, "/studio/api/import") + request = SimpleNamespace(app=app) + result = _runtime_result(backend_key=None) + result.pop("compare_key") + + response = route.endpoint(request=request, payload={"result": result}) + + assert response["status"] == "imported" + assert response["result"]["backend_key"] == "onnxruntime__cpu" + assert response["result"]["compare_key"] == "yolov8n__b1__h640w640__fp32" + + def test_studio_import_api_accepts_existing_result_path(): app = api.create_app() route = _get_route(app, "/studio/api/import") @@ -206,3 +242,37 @@ def test_studio_jetson_command_api_returns_command(): assert "--engine tensorrt" in response["command"] assert "--device jetson" in response["command"] assert "--output results/jetson/" in response["command"] + + +def test_studio_importing_two_compatible_results_returns_compare_data(): + app = api.create_app() + request = SimpleNamespace(app=app) + import_route = _get_route(app, "/studio/api/import") + compare_route = _get_route(app, "/studio/api/compare/latest") + + first = import_route.endpoint( + request=request, + payload={"result": _runtime_result(engine="onnxruntime", device="cpu", mean_ms=45.0, p99_ms=50.0)}, + ) + second = import_route.endpoint( + request=request, + payload={ + "result": _runtime_result( + engine="tensorrt", + device="jetson", + mean_ms=9.9, + p99_ms=12.0, + backend_key="tensorrt__jetson", + ) + }, + ) + compare = compare_route.endpoint(request=request) + + assert first["compare_ready"] is False + assert second["compare_ready"] is True + assert compare["status"] == "ok" + assert compare["base"]["backend_key"] == "onnxruntime__cpu" + assert compare["new"]["backend_key"] == "tensorrt__jetson" + assert compare["result"]["metrics"]["mean_ms"]["new"] == 9.9 + assert compare["judgement"]["overall"] == "improvement" + assert compare["deployment_decision"]["decision"] == "unknown" From bb180a71116725980474ea6b815129e9dd5cec78 Mon Sep 17 00:00:00 2001 From: hyeokjun32 Date: Thu, 30 Apr 2026 02:03:04 +0900 Subject: [PATCH 03/10] fix: guard local studio file preview mode --- inferedgelab/studio/static/app.js | 7 +++++++ inferedgelab/studio/static/index.html | 19 +++++++++++++++++-- inferedgelab/studio/static/style.css | 25 +++++++++++++++++++++++++ tests/test_studio_routes.py | 8 ++++++-- 4 files changed, 55 insertions(+), 4 deletions(-) diff --git a/inferedgelab/studio/static/app.js b/inferedgelab/studio/static/app.js index 87abdea..d8635e5 100644 --- a/inferedgelab/studio/static/app.js +++ b/inferedgelab/studio/static/app.js @@ -119,6 +119,12 @@ function assertHttpStudio() { } } +function markFileMode() { + if (window.location.protocol === "file:") { + document.body.classList.add("file-mode"); + } +} + async function responseErrorMessage(response) { const fallback = `Request failed: ${response.status}`; try { @@ -800,6 +806,7 @@ function formatValue(value) { async function initLocalStudio() { try { + markFileMode(); renderRunPanel(); renderPipeline(); renderJobDetail(); diff --git a/inferedgelab/studio/static/index.html b/inferedgelab/studio/static/index.html index 882bd4f..ca78b48 100644 --- a/inferedgelab/studio/static/index.html +++ b/inferedgelab/studio/static/index.html @@ -132,7 +132,12 @@ } } - +
@@ -145,6 +150,11 @@

InferEdge Local Studio

SaaS-ready (local mode)
+
+ Open Local Studio through the local server. +

Use http://127.0.0.1:8000/studio after running inferedgelab serve. Direct file mode can preview layout only; Run, Import, Compare, and Jetson helpers require the local API.

+
+

01

@@ -282,6 +292,11 @@

Future Work

- + diff --git a/inferedgelab/studio/static/style.css b/inferedgelab/studio/static/style.css index a602fb1..9312f07 100644 --- a/inferedgelab/studio/static/style.css +++ b/inferedgelab/studio/static/style.css @@ -47,6 +47,31 @@ body { margin-bottom: 28px; } +.file-protocol-warning { + display: none; + border: 1px solid rgba(234, 179, 8, 0.38); + border-radius: 12px; + background: rgba(234, 179, 8, 0.08); + color: var(--ink); + margin-bottom: 18px; + padding: 14px 16px; +} + +.file-protocol-warning strong { + display: block; + margin-bottom: 6px; +} + +.file-protocol-warning p { + color: var(--muted); + line-height: 1.5; + margin: 0; +} + +body.file-mode .file-protocol-warning { + display: block; +} + .title-block h1, .section-heading h2, .panel h3, diff --git a/tests/test_studio_routes.py b/tests/test_studio_routes.py index cf65750..e6aedc7 100644 --- a/tests/test_studio_routes.py +++ b/tests/test_studio_routes.py @@ -59,8 +59,10 @@ def test_studio_route_returns_local_studio_html(): assert "Import" in html assert "Jetson Helper" in html assert 'data-critical="studio-dark"' in html - assert 'href="/studio/static/style.css?v=9"' in html - assert 'src="/studio/static/app.js?v=9"' in html + assert "style.css?v=10" in html + assert "app.js?v=10" in html + assert "file-protocol-warning" in html + assert 'window.location.protocol === "file:" ? "" : "/studio/static/"' in html assert 'value="results/latest.json"' in html assert 'id="import-json-payload"' in html @@ -97,6 +99,7 @@ def test_studio_static_assets_include_redesigned_ui_contracts(): assert "DOMContentLoaded" in app_text assert "Open Studio from http://127.0.0.1:8000/studio" in app_text assert "responseErrorMessage" in app_text + assert "markFileMode" in app_text assert "parseJsonResponse" in app_text assert "renderImportEvidence" in app_text assert "AIGuard evidence not provided" in app_text @@ -110,6 +113,7 @@ def test_studio_static_assets_include_redesigned_ui_contracts(): assert ".form-stack button" in style_text assert ".tool-card" in style_text assert ".state-pill.optional" in style_text + assert ".file-protocol-warning" in style_text assert ".evidence-summary" in style_text assert ".compare-card.improvement" in style_text From c4b83d4eafbc3f088b5b0d56084d1cc25cf002b5 Mon Sep 17 00:00:00 2001 From: hyeokjun32 Date: Thu, 30 Apr 2026 02:08:43 +0900 Subject: [PATCH 04/10] fix: improve local studio section readability --- inferedgelab/studio/routes.py | 15 +++++++++++++++ inferedgelab/studio/static/index.html | 11 ++++++++--- inferedgelab/studio/static/style.css | 6 ++++++ tests/test_studio_routes.py | 17 +++++++++++++++-- 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/inferedgelab/studio/routes.py b/inferedgelab/studio/routes.py index 6cdf126..cc43385 100644 --- a/inferedgelab/studio/routes.py +++ b/inferedgelab/studio/routes.py @@ -9,6 +9,7 @@ from fastapi import Request from fastapi import Body from fastapi.responses import FileResponse +from fastapi.responses import RedirectResponse from inferedgelab.compare.comparator import compare_results from inferedgelab.compare.judgement import judge_comparison @@ -34,6 +35,11 @@ def studio_index() -> FileResponse: ) +@router.get("/studio로", include_in_schema=False) +def studio_korean_particle_redirect() -> RedirectResponse: + return RedirectResponse(url="/studio", status_code=307) + + @router.get("/studio/static/{asset_name}", include_in_schema=False) def studio_static(asset_name: str) -> FileResponse: media_type = STATIC_ASSETS.get(asset_name) @@ -141,6 +147,15 @@ def studio_jetson_command() -> dict[str, str]: return {"command": command} +@router.get("/studio{suffix:path}", include_in_schema=False) +def studio_path_fallback(suffix: str) -> RedirectResponse: + if suffix.startswith("/api") or suffix.startswith("/static"): + raise HTTPException(status_code=404, detail="studio route not found") + if suffix: + return RedirectResponse(url="/studio", status_code=307) + return RedirectResponse(url="/studio", status_code=307) + + def register_studio(app: FastAPI, job_store: Any | None = None) -> None: app.state.studio_job_store = job_store app.state.studio_imported_results = [] diff --git a/inferedgelab/studio/static/index.html b/inferedgelab/studio/static/index.html index ca78b48..bda840e 100644 --- a/inferedgelab/studio/static/index.html +++ b/inferedgelab/studio/static/index.html @@ -32,7 +32,6 @@ } .studio-header, - .section-heading, .card-header, .panel-title-row { display: flex; @@ -40,6 +39,12 @@ gap: 16px; } + .section-heading { + display: flex; + justify-content: flex-start; + gap: 14px; + } + .studio-header, .section-heading { align-items: start; @@ -135,7 +140,7 @@ @@ -295,7 +300,7 @@

Future Work

diff --git a/inferedgelab/studio/static/style.css b/inferedgelab/studio/static/style.css index 9312f07..4eb6bf8 100644 --- a/inferedgelab/studio/static/style.css +++ b/inferedgelab/studio/static/style.css @@ -128,10 +128,15 @@ body.file-mode .file-protocol-warning { .section-heading { display: flex; align-items: start; + justify-content: flex-start; gap: 14px; margin-bottom: 14px; } +.section-heading > div { + min-width: 0; +} + .section-heading h2 { font-size: 1.28rem; } @@ -144,6 +149,7 @@ body.file-mode .file-protocol-warning { .section-kicker { display: inline-grid; + flex: 0 0 auto; width: 38px; height: 28px; place-items: center; diff --git a/tests/test_studio_routes.py b/tests/test_studio_routes.py index e6aedc7..5a96065 100644 --- a/tests/test_studio_routes.py +++ b/tests/test_studio_routes.py @@ -4,6 +4,7 @@ from types import SimpleNamespace from fastapi.responses import FileResponse +from fastapi.responses import RedirectResponse import inferedgelab.api as api @@ -59,8 +60,8 @@ def test_studio_route_returns_local_studio_html(): assert "Import" in html assert "Jetson Helper" in html assert 'data-critical="studio-dark"' in html - assert "style.css?v=10" in html - assert "app.js?v=10" in html + assert "style.css?v=11" in html + assert "app.js?v=11" in html assert "file-protocol-warning" in html assert 'window.location.protocol === "file:" ? "" : "/studio/static/"' in html assert 'value="results/latest.json"' in html @@ -116,6 +117,7 @@ def test_studio_static_assets_include_redesigned_ui_contracts(): assert ".file-protocol-warning" in style_text assert ".evidence-summary" in style_text assert ".compare-card.improvement" in style_text + assert "justify-content: flex-start" in style_text def test_studio_app_preserves_selected_job_detail_contract(): @@ -147,6 +149,17 @@ def test_studio_jobs_api_returns_json_structure(): assert response["jobs"] == [] +def test_studio_malformed_path_redirects_to_studio(): + app = api.create_app() + route = _get_route(app, "/studio로") + + response = route.endpoint() + + assert isinstance(response, RedirectResponse) + assert response.status_code == 307 + assert response.headers["location"] == "/studio" + + def test_studio_compare_latest_api_returns_json_structure(): app = api.create_app() route = _get_route(app, "/studio/api/compare/latest") From 13f3d507d1cd4aaa6d8fe31aa64989eeb2bd942f Mon Sep 17 00:00:00 2001 From: hyeokjun32 Date: Thu, 30 Apr 2026 02:20:12 +0900 Subject: [PATCH 05/10] fix: stabilize local studio asset loading --- inferedgelab/studio/static/index.html | 18 +++++------------- tests/test_studio_routes.py | 10 ++++++---- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/inferedgelab/studio/static/index.html b/inferedgelab/studio/static/index.html index bda840e..f2d7c78 100644 --- a/inferedgelab/studio/static/index.html +++ b/inferedgelab/studio/static/index.html @@ -137,12 +137,8 @@ } } - + +
@@ -206,7 +202,7 @@

Runtime result JSON

- + @@ -276,14 +299,14 @@

Compare View

05

Deployment Decision

-

Lab remains the final decision owner; AIGuard evidence is optional.

+

Lab's local gate for deploy, review, or block. AIGuard evidence is optional and not required for this Studio flow.

-
+

Later

Future Work

@@ -293,7 +316,7 @@

Future Work

- - + + diff --git a/inferedgelab/studio/static/style.css b/inferedgelab/studio/static/style.css index 4eb6bf8..1d51200 100644 --- a/inferedgelab/studio/static/style.css +++ b/inferedgelab/studio/static/style.css @@ -133,6 +133,24 @@ body.file-mode .file-protocol-warning { margin-bottom: 14px; } +.future-heading { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + align-items: start; + gap: 16px; +} + +.future-heading h2 { + margin: 0; + font-size: 1.2rem; +} + +.future-heading p:last-child { + margin: 6px 0 0; + color: var(--muted); + line-height: 1.5; +} + .section-heading > div { min-width: 0; } @@ -258,6 +276,13 @@ body.file-mode .file-protocol-warning { line-height: 1.5; } +.stage-note { + margin-top: 10px; + color: var(--caption); + font-size: 0.78rem; + line-height: 1.45; +} + .state-pill { display: inline-flex; align-items: center; @@ -311,7 +336,8 @@ body.file-mode .file-protocol-warning { } .form-stack input, -.form-stack textarea { +.form-stack textarea, +.form-stack select { width: 100%; border: 1px solid var(--line-strong); border-radius: 10px; @@ -323,10 +349,30 @@ body.file-mode .file-protocol-warning { } .form-stack input:focus, -.form-stack textarea:focus { +.form-stack textarea:focus, +.form-stack select:focus { border-color: var(--primary); } +.form-stack select { + appearance: none; + background: + linear-gradient(45deg, transparent 50%, var(--primary) 50%) calc(100% - 18px) 50% / 6px 6px no-repeat, + linear-gradient(135deg, var(--primary) 50%, transparent 50%) calc(100% - 13px) 50% / 6px 6px no-repeat, + #0b1220; +} + +.inline-fields { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; +} + +.inline-fields > div { + display: grid; + gap: 8px; +} + .form-stack textarea { min-height: 146px; resize: vertical; @@ -596,4 +642,9 @@ body.file-mode .file-protocol-warning { .evidence-summary { grid-template-columns: 1fr; } + + .inline-fields, + .future-heading { + grid-template-columns: 1fr; + } } diff --git a/tests/test_studio_routes.py b/tests/test_studio_routes.py index a627f23..43073ff 100644 --- a/tests/test_studio_routes.py +++ b/tests/test_studio_routes.py @@ -60,14 +60,20 @@ def test_studio_route_returns_local_studio_html(): assert "Import" in html assert "Jetson Helper" in html assert 'data-critical="studio-dark"' in html - assert 'href="/studio/static/style.css?v=12"' in html - assert 'href="style.css?v=12"' in html - assert 'src="/studio/static/app.js?v=12"' in html - assert 'src="app.js?v=12"' in html + assert 'href="/studio/static/style.css?v=13"' in html + assert 'href="style.css?v=13"' in html + assert 'src="/studio/static/app.js?v=13"' in html + assert 'src="app.js?v=13"' in html assert "file-protocol-warning" in html assert 'placeholder="results/latest.json"' in html assert 'value="results/latest.json"' not in html assert 'id="import-json-payload"' in html + assert 'autocomplete="off"' in html + assert 'id="run-backend"' in html + assert 'id="run-device"' in html + assert 'id="import-backend-preset"' in html + assert "TensorRT / Jetson" in html + assert "Lab's local gate" in html def test_studio_static_assets_are_served(): @@ -110,6 +116,11 @@ def test_studio_static_assets_include_redesigned_ui_contracts(): assert "runtimeModelName" in app_text assert "Same backend" in app_text assert "hasImportedEvidence" in app_text + assert "importedResultsByJobId" in app_text + assert "rememberImportedResultForSelectedJob" in app_text + assert "runOptions" in app_text + assert "resetTransientInputs" in app_text + assert "No guard run is required" in app_text assert 'aiguard: hasGuardEvidence ? "completed" : "optional"' in app_text assert "#0b0f14" in style_text assert "grid-template-columns" in style_text @@ -136,6 +147,8 @@ def test_studio_app_preserves_selected_job_detail_contract(): assert "Queued job" in app_text assert "Runtime metrics will appear" in app_text assert ".detail-note" in style_text + assert ".inline-fields" in style_text + assert ".future-heading" in style_text def test_studio_jobs_api_returns_json_structure(): @@ -182,12 +195,22 @@ def test_studio_run_api_creates_analyze_job(): route = _get_route(app, "/studio/api/run") request = SimpleNamespace(app=app) - response = route.endpoint(request=request, payload={"model_path": "models/yolov8n.onnx"}) + response = route.endpoint( + request=request, + payload={ + "model_path": "models/yolov8n.onnx", + "options": {"backend": "tensorrt", "device": "jetson"}, + }, + ) assert response["status"] == "created" assert response["source"] == "/api/analyze" assert response["job_id"].startswith("job_") assert response["job"]["input_summary"]["model_path"] == "models/yolov8n.onnx" + assert response["job"]["input_summary"]["options"] == { + "backend": "tensorrt", + "device": "jetson", + } def test_studio_run_job_can_be_listed_and_selected(): @@ -223,6 +246,24 @@ def test_studio_import_api_accepts_runtime_result_json(): assert response["compare_ready"] is False +def test_studio_import_api_applies_backend_override(): + app = api.create_app() + route = _get_route(app, "/studio/api/import") + request = SimpleNamespace(app=app) + result = _runtime_result(engine="onnxruntime", device="cpu", mean_ms=9.9) + + response = route.endpoint( + request=request, + payload={"result": result, "backend_override": "tensorrt__jetson"}, + ) + + assert response["status"] == "imported" + assert response["result"]["engine"] == "tensorrt" + assert response["result"]["engine_backend"] == "tensorrt" + assert response["result"]["device"] == "jetson" + assert response["result"]["backend_key"] == "tensorrt__jetson" + + def test_studio_import_api_generates_missing_compare_keys(): app = api.create_app() route = _get_route(app, "/studio/api/import") From 695dd6231ad70747ee8653266452101b8f904d25 Mon Sep 17 00:00:00 2001 From: hyeokjun32 Date: Thu, 30 Apr 2026 20:57:17 +0900 Subject: [PATCH 07/10] feat: add local studio demo evidence --- .../studio_demo/onnxruntime_cpu_result.json | 38 ++++++++++++ .../studio_demo/tensorrt_jetson_result.json | 40 +++++++++++++ inferedgelab/studio/routes.py | 41 +++++++++++++ inferedgelab/studio/static/app.js | 52 +++++++++++++++- inferedgelab/studio/static/index.html | 23 +++++-- inferedgelab/studio/static/style.css | 60 ++++++++++++++++++- tests/test_studio_routes.py | 40 +++++++++++-- 7 files changed, 282 insertions(+), 12 deletions(-) create mode 100644 examples/studio_demo/onnxruntime_cpu_result.json create mode 100644 examples/studio_demo/tensorrt_jetson_result.json diff --git a/examples/studio_demo/onnxruntime_cpu_result.json b/examples/studio_demo/onnxruntime_cpu_result.json new file mode 100644 index 0000000..5dd20d4 --- /dev/null +++ b/examples/studio_demo/onnxruntime_cpu_result.json @@ -0,0 +1,38 @@ +{ + "runtime_role": "runtime-result", + "model": "yolov8n.onnx", + "engine": "onnxruntime", + "engine_backend": "onnxruntime", + "device": "cpu", + "device_name": "cpu", + "precision": "fp32", + "batch": 1, + "height": 640, + "width": 640, + "mean_ms": 45.4299, + "p99_ms": 49.2128, + "fps_value": 22.0119, + "success": true, + "status": "success", + "timestamp": "2026-04-29T12:00:00Z", + "compare_key": "yolov8n__b1__h640w640__fp32", + "backend_key": "onnxruntime__cpu", + "system": { + "os": "macOS", + "machine": "arm64" + }, + "run_config": { + "warmup": 1, + "runs": 5, + "mode": "image", + "task": "detection" + }, + "accuracy": {}, + "extra": { + "input_mode": "image", + "input_preprocess": "opencv_bgr_to_rgb_resize_float32_nchw", + "effective_batch": 1, + "effective_height": 640, + "effective_width": 640 + } +} diff --git a/examples/studio_demo/tensorrt_jetson_result.json b/examples/studio_demo/tensorrt_jetson_result.json new file mode 100644 index 0000000..8fb682b --- /dev/null +++ b/examples/studio_demo/tensorrt_jetson_result.json @@ -0,0 +1,40 @@ +{ + "runtime_role": "runtime-result", + "model": "yolov8n.onnx", + "engine": "tensorrt", + "engine_backend": "tensorrt", + "device": "jetson", + "device_name": "jetson", + "precision": "fp32", + "batch": 1, + "height": 640, + "width": 640, + "mean_ms": 9.9375, + "p99_ms": 15.5231, + "fps_value": 100.6293, + "success": true, + "status": "success", + "timestamp": "2026-04-29T12:01:00Z", + "compare_key": "yolov8n__b1__h640w640__fp32", + "backend_key": "tensorrt__jetson", + "system": { + "os": "Linux 5.15.148-tegra", + "machine": "aarch64" + }, + "run_config": { + "warmup": 1, + "runs": 5, + "mode": "image", + "task": "detection" + }, + "accuracy": {}, + "extra": { + "input_mode": "image", + "input_preprocess": "opencv_bgr_to_rgb_resize_float32_nchw", + "manifest_applied": true, + "runtime_artifact_path": "~/InferEdgeForge/builds/yolov8n__jetson__tensorrt__jetson_fp16/model.engine", + "effective_batch": 1, + "effective_height": 640, + "effective_width": 640 + } +} diff --git a/inferedgelab/studio/routes.py b/inferedgelab/studio/routes.py index da26b7f..78ac2ae 100644 --- a/inferedgelab/studio/routes.py +++ b/inferedgelab/studio/routes.py @@ -1,5 +1,6 @@ from __future__ import annotations +import json from pathlib import Path from typing import Any @@ -18,6 +19,11 @@ from inferedgelab.services.deployment_decision import build_deployment_decision STATIC_DIR = Path(__file__).resolve().parent / "static" +DEMO_EVIDENCE_DIR = Path(__file__).resolve().parents[2] / "examples" / "studio_demo" +DEMO_EVIDENCE_FILES = ( + "onnxruntime_cpu_result.json", + "tensorrt_jetson_result.json", +) STATIC_ASSETS = { "app.js": "application/javascript", "style.css": "text/css", @@ -138,6 +144,23 @@ def studio_import(request: Request, payload: dict[str, Any] = Body(...)) -> dict } +@router.get("/studio/api/demo-evidence", include_in_schema=False) +def studio_demo_evidence(request: Request) -> dict[str, Any]: + results = [_load_demo_result(file_name) for file_name in DEMO_EVIDENCE_FILES] + imported_results = _get_imported_results(request) + imported_results.extend(results) + compare = _build_imported_compare_response(results[0], results[1]) + return { + "status": "loaded", + "source": "examples/studio_demo", + "count": len(results), + "results": results, + "compare_ready": True, + "compare": compare, + "deployment_decision": compare["deployment_decision"], + } + + @router.get("/studio/api/jetson-command", include_in_schema=False) def studio_jetson_command() -> dict[str, str]: command = "\n".join( @@ -233,6 +256,24 @@ def _build_imported_compare_response(base: dict[str, Any], new: dict[str, Any]) } +def _load_demo_result(file_name: str) -> dict[str, Any]: + path = DEMO_EVIDENCE_DIR / file_name + try: + raw = json.loads(path.read_text(encoding="utf-8")) + except OSError as exc: + raise HTTPException(status_code=500, detail=f"demo evidence not found: {file_name}") from exc + except json.JSONDecodeError as exc: + raise HTTPException(status_code=500, detail=f"demo evidence is invalid JSON: {file_name}") from exc + + try: + result = normalize_result_schema(raw) + except (TypeError, ValueError) as exc: + raise HTTPException(status_code=500, detail=f"demo evidence schema error: {file_name}") from exc + result.setdefault("legacy_result", False) + result["_source_path"] = str(path.relative_to(DEMO_EVIDENCE_DIR.parents[1])) + return _with_compare_keys(result) + + def _with_compare_keys(result: dict[str, Any]) -> dict[str, Any]: enriched = dict(result) if not enriched.get("backend_key"): diff --git a/inferedgelab/studio/static/app.js b/inferedgelab/studio/static/app.js index 336d28c..c021bd2 100644 --- a/inferedgelab/studio/static/app.js +++ b/inferedgelab/studio/static/app.js @@ -355,6 +355,33 @@ async function copyJetsonCommand() { } } +async function loadDemoEvidence() { + const button = document.querySelector("#load-demo-evidence"); + button.disabled = true; + setState("#demo-state", "running"); + setStatus("#demo-status", "Loading: importing bundled Runtime evidence...", "loading"); + renderPipeline(); + try { + const payload = await fetchJson("/studio/api/demo-evidence"); + const results = Array.isArray(payload.results) ? payload.results : []; + importedResult = results[results.length - 1] || null; + compareData = payload.compare || null; + setState("#demo-state", "completed"); + setState("#import-state", "completed"); + setStatus("#demo-status", "Success: demo evidence loaded.", "success"); + setStatus("#import-status", "Success: demo ONNX Runtime + TensorRT evidence imported.", "success"); + renderImportEvidence({ result: importedResult }); + renderImportedResult(); + await loadCompare(); + } catch (error) { + setState("#demo-state", "idle"); + setStatus("#demo-status", `Error: ${formatError(error)}`, "error"); + } finally { + button.disabled = false; + renderPipeline(); + } +} + function renderPipeline() { const target = document.querySelector("#pipeline-flow"); target.replaceChildren(); @@ -411,9 +438,11 @@ function renderRunPanel() { document.querySelector("#run-button").onclick = runModel; document.querySelector("#import-button").onclick = importResult; document.querySelector("#copy-jetson-command").onclick = copyJetsonCommand; + document.querySelector("#load-demo-evidence").onclick = loadDemoEvidence; setState("#run-state", "idle"); setState("#import-state", "idle"); setState("#jetson-state", "idle"); + setState("#demo-state", "idle"); } function resetTransientInputs() { @@ -566,8 +595,8 @@ function renderCompare() { const sameBackend = normalizedBackendKey(base) && normalizedBackendKey(base) === normalizedBackendKey(newer); target.append( - compareMetricCard("TensorRT", tensorRt?.mean_ms, normalizedBackendKey(tensorRt) || "tensorrt"), - compareMetricCard("ONNX Runtime", onnx?.mean_ms, normalizedBackendKey(onnx) || "onnxruntime"), + compareMetricCard("TensorRT", tensorRt, normalizedBackendKey(tensorRt) || "tensorrt"), + compareMetricCard("ONNX Runtime", onnx, normalizedBackendKey(onnx) || "onnxruntime"), compareSummaryCard(meanMetric, speedup, base, newer, sameBackend, judgement.overall), ); } @@ -614,16 +643,33 @@ function detailNote(title, message) { return note; } -function compareMetricCard(label, meanMs, backendKey) { +function compareMetricCard(label, result, backendKey) { const card = createElement("article", "compare-card"); + const meanMs = result?.mean_ms; card.append( createElement("p", "caption", backendKey), createElement("h3", "", label), createElement("strong", "compare-value", meanMs === undefined || meanMs === null ? "-" : `${formatNumber(meanMs)} ms`), + compareStatList(result), ); return card; } +function compareStatList(result = {}) { + const list = createElement("div", "compare-stat-list"); + list.append( + compareStat("p99", result?.p99_ms === undefined ? "-" : `${formatNumber(result.p99_ms)} ms`), + compareStat("fps", result?.fps_value ?? result?.fps ?? "-"), + ); + return list; +} + +function compareStat(label, value) { + const row = createElement("div", "compare-stat"); + row.append(createElement("span", "", label), createElement("strong", "", formatValue(value))); + return row; +} + function compareSummaryCard(metric, speedup, base, newer, sameBackend = false, overall = "unknown") { const card = createElement("article", `compare-card highlight ${compareTone(overall)}`); const diff = formatLatencyDiff(metric); diff --git a/inferedgelab/studio/static/index.html b/inferedgelab/studio/static/index.html index fc2700d..f167aac 100644 --- a/inferedgelab/studio/static/index.html +++ b/inferedgelab/studio/static/index.html @@ -137,8 +137,8 @@ } } - - + +
@@ -253,6 +253,21 @@

Runtime command

+ +
+
+
+

Demo Evidence

+

Replay validation evidence

+
+ idle +
+

Load the bundled ONNX Runtime CPU and TensorRT Jetson results to reproduce Compare and Decision locally.

+
+ +

+
+
@@ -316,7 +331,7 @@

Future Work

- - + + diff --git a/inferedgelab/studio/static/style.css b/inferedgelab/studio/static/style.css index 1d51200..10bd5df 100644 --- a/inferedgelab/studio/static/style.css +++ b/inferedgelab/studio/static/style.css @@ -214,7 +214,26 @@ body.file-mode .file-protocol-warning { transition: transform 160ms ease, border-color 160ms ease, - background 160ms ease; + background 160ms ease; +} + +.demo-card { + display: grid; + grid-column: 1 / -1; + grid-template-columns: minmax(0, 1fr) minmax(240px, 320px); + align-items: center; + gap: 16px; +} + +.demo-card .card-header, +.demo-card .body-text { + grid-column: 1; +} + +.demo-card .form-stack { + grid-column: 2; + grid-row: 1 / span 2; + margin-top: 0; } .panel:hover, @@ -579,6 +598,28 @@ body.file-mode .file-protocol-warning { line-height: 1; } +.compare-stat-list { + display: grid; + gap: 8px; + margin-top: 14px; +} + +.compare-stat { + display: flex; + justify-content: space-between; + gap: 12px; + border-top: 1px solid rgba(148, 163, 184, 0.14); + color: var(--muted); + font-size: 0.86rem; + padding-top: 8px; +} + +.compare-stat strong { + color: var(--ink); + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", + monospace; +} + .decision-card { min-height: 156px; } @@ -619,6 +660,23 @@ body.file-mode .file-protocol-warning { min-height: auto; } + .demo-card, + .demo-card .card-header, + .demo-card .body-text, + .demo-card .form-stack { + display: grid; + grid-column: auto; + grid-row: auto; + } + + .demo-card { + grid-template-columns: 1fr; + } + + .demo-card .form-stack { + margin-top: 0; + } + .pipeline-card::after { content: ""; } diff --git a/tests/test_studio_routes.py b/tests/test_studio_routes.py index 43073ff..61cf6d8 100644 --- a/tests/test_studio_routes.py +++ b/tests/test_studio_routes.py @@ -60,10 +60,10 @@ def test_studio_route_returns_local_studio_html(): assert "Import" in html assert "Jetson Helper" in html assert 'data-critical="studio-dark"' in html - assert 'href="/studio/static/style.css?v=13"' in html - assert 'href="style.css?v=13"' in html - assert 'src="/studio/static/app.js?v=13"' in html - assert 'src="app.js?v=13"' in html + assert 'href="/studio/static/style.css?v=14"' in html + assert 'href="style.css?v=14"' in html + assert 'src="/studio/static/app.js?v=14"' in html + assert 'src="app.js?v=14"' in html assert "file-protocol-warning" in html assert 'placeholder="results/latest.json"' in html assert 'value="results/latest.json"' not in html @@ -74,6 +74,8 @@ def test_studio_route_returns_local_studio_html(): assert 'id="import-backend-preset"' in html assert "TensorRT / Jetson" in html assert "Lab's local gate" in html + assert "Load Demo Evidence" in html + assert 'id="demo-state"' in html def test_studio_static_assets_are_served(): @@ -121,6 +123,9 @@ def test_studio_static_assets_include_redesigned_ui_contracts(): assert "runOptions" in app_text assert "resetTransientInputs" in app_text assert "No guard run is required" in app_text + assert "loadDemoEvidence" in app_text + assert "/studio/api/demo-evidence" in app_text + assert "compareStatList" in app_text assert 'aiguard: hasGuardEvidence ? "completed" : "optional"' in app_text assert "#0b0f14" in style_text assert "grid-template-columns" in style_text @@ -130,6 +135,8 @@ def test_studio_static_assets_include_redesigned_ui_contracts(): assert ".file-protocol-warning" in style_text assert ".evidence-summary" in style_text assert ".compare-card.improvement" in style_text + assert ".demo-card" in style_text + assert ".compare-stat-list" in style_text assert "justify-content: flex-start" in style_text @@ -304,6 +311,31 @@ def test_studio_jetson_command_api_returns_command(): assert "--output results/jetson/" in response["command"] +def test_studio_demo_evidence_loads_compare_ready_pair(): + app = api.create_app() + route = _get_route(app, "/studio/api/demo-evidence") + compare_route = _get_route(app, "/studio/api/compare/latest") + request = SimpleNamespace(app=app) + + response = route.endpoint(request=request) + compare = compare_route.endpoint(request=request) + + assert response["status"] == "loaded" + assert response["source"] == "examples/studio_demo" + assert response["count"] == 2 + assert response["compare_ready"] is True + assert response["results"][0]["backend_key"] == "onnxruntime__cpu" + assert response["results"][1]["backend_key"] == "tensorrt__jetson" + assert response["results"][0]["mean_ms"] == 45.4299 + assert response["results"][1]["mean_ms"] == 9.9375 + assert response["compare"]["status"] == "ok" + assert response["compare"]["judgement"]["overall"] == "improvement" + assert response["deployment_decision"]["decision"] == "unknown" + assert compare["status"] == "ok" + assert compare["base"]["backend_key"] == "onnxruntime__cpu" + assert compare["new"]["backend_key"] == "tensorrt__jetson" + + def test_studio_importing_two_compatible_results_returns_compare_data(): app = api.create_app() request = SimpleNamespace(app=app) From 5917d936838dd9177de2a53607233cb22526a207 Mon Sep 17 00:00:00 2001 From: hyeokjun32 Date: Thu, 30 Apr 2026 21:12:59 +0900 Subject: [PATCH 08/10] fix: clarify local studio queued and optional states --- inferedgelab/studio/static/app.js | 22 +++++++++++++++++----- inferedgelab/studio/static/style.css | 6 ++++++ tests/test_studio_routes.py | 7 +++++-- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/inferedgelab/studio/static/app.js b/inferedgelab/studio/static/app.js index c021bd2..e743701 100644 --- a/inferedgelab/studio/static/app.js +++ b/inferedgelab/studio/static/app.js @@ -508,17 +508,21 @@ function renderJobDetail(emptyMessage = "Select a job or import a Runtime result const compareMetrics = result.comparison?.result?.metrics || result.data?.result?.metrics || {}; const input = selectedJob.input_summary || {}; const inputOptions = input.options || {}; + const hasMetrics = hasRuntimeMetrics(displayResult); - const metrics = [ + const identityMetrics = [ ["model", runtimeModelName(displayResult) || input.model_path || input.artifact_path], ["backend", runtimeBackendName(displayResult) || inputOptions.backend], ["device", runtimeDeviceName(displayResult) || inputOptions.device], + ]; + const resultMetrics = [ ["mean", displayResult.mean_ms ?? compareMetrics.mean_ms?.new], ["p99", displayResult.p99_ms ?? compareMetrics.p99_ms?.new], ["fps", displayResult.fps || displayResult.fps_value], ["compare_key", displayResult.compare_key], ["backend_key", displayResult.backend_key || normalizedBackendKey(displayResult)], ]; + const metrics = hasMetrics ? identityMetrics.concat(resultMetrics) : identityMetrics; metrics.forEach(([label, value]) => { target.append(metricTile(label, formatValue(value))); @@ -529,7 +533,7 @@ function renderJobDetail(emptyMessage = "Select a job or import a Runtime result target.append( detailNote( "Queued job", - "The local API accepted this analyze job. Runtime metrics will appear after a worker/dev completion flow or after importing Runtime result JSON.", + "This is a request record only. Runtime metrics are not attached to this job yet; use Import or Load Demo Evidence to inspect actual validation evidence.", ), ); } else if (!selectedJob.result && hasRuntimeMetrics(displayResult)) { @@ -611,7 +615,7 @@ function renderDecision(decision) { createElement("p", "caption", "Decision"), createElement("h3", "", "UNKNOWN"), createElement("p", "body-text", "No Lab comparison decision is available yet."), - createElement("p", "caption", "Import two compatible Runtime results to let Lab compare latency. AIGuard evidence is optional."), + createElement("p", "caption", "Load demo evidence or import two compatible Runtime results. AIGuard remains optional."), ); return; } @@ -622,7 +626,7 @@ function renderDecision(decision) { createElement("p", "caption", "Decision"), createElement("h3", "", decisionName.toUpperCase()), createElement("p", "body-text", decisionReason(decision)), - createElement("p", "caption", decision.notes || decision.recommended_action || ""), + createElement("p", "caption", decisionNotes(decision)), ); } @@ -714,11 +718,19 @@ function extractDecision(payload) { function decisionReason(decision) { const decisionName = String(decision?.decision || "unknown").toLowerCase(); if (decisionName === "unknown" && !decision?.guard_status) { - return "AIGuard evidence not provided."; + return "Lab comparison is available, but AIGuard diagnosis evidence was not loaded for this local demo."; } return decision?.reason || "-"; } +function decisionNotes(decision) { + const decisionName = String(decision?.decision || "unknown").toLowerCase(); + if (decisionName === "unknown" && !decision?.guard_status) { + return "This is expected: AIGuard is optional and only needed when guard-backed diagnosis evidence is part of the review."; + } + return decision?.notes || decision?.recommended_action || ""; +} + function extractRuntimeResult(job) { const result = job?.result; if (!result) { diff --git a/inferedgelab/studio/static/style.css b/inferedgelab/studio/static/style.css index 10bd5df..4f6e681 100644 --- a/inferedgelab/studio/static/style.css +++ b/inferedgelab/studio/static/style.css @@ -140,6 +140,12 @@ body.file-mode .file-protocol-warning { gap: 16px; } +.future-heading .section-kicker { + width: auto; + min-width: 62px; + padding-inline: 12px; +} + .future-heading h2 { margin: 0; font-size: 1.2rem; diff --git a/tests/test_studio_routes.py b/tests/test_studio_routes.py index 61cf6d8..2aa2781 100644 --- a/tests/test_studio_routes.py +++ b/tests/test_studio_routes.py @@ -113,7 +113,7 @@ def test_studio_static_assets_include_redesigned_ui_contracts(): assert "markFileMode" in app_text assert "parseJsonResponse" in app_text assert "renderImportEvidence" in app_text - assert "AIGuard evidence not provided" in app_text + assert "AIGuard diagnosis evidence was not loaded" in app_text assert "compareTone" in app_text assert "runtimeModelName" in app_text assert "Same backend" in app_text @@ -123,6 +123,8 @@ def test_studio_static_assets_include_redesigned_ui_contracts(): assert "runOptions" in app_text assert "resetTransientInputs" in app_text assert "No guard run is required" in app_text + assert "decisionNotes" in app_text + assert "request record only" in app_text assert "loadDemoEvidence" in app_text assert "/studio/api/demo-evidence" in app_text assert "compareStatList" in app_text @@ -152,10 +154,11 @@ def test_studio_app_preserves_selected_job_detail_contract(): assert "selectedJobId" in app_text assert "loadJobs(payload.job_id)" in app_text assert "Queued job" in app_text - assert "Runtime metrics will appear" in app_text + assert "Runtime metrics are not attached" in app_text assert ".detail-note" in style_text assert ".inline-fields" in style_text assert ".future-heading" in style_text + assert "min-width: 62px" in style_text def test_studio_jobs_api_returns_json_structure(): From 7d6bcd816f59f922c6e4e4b8fb1b9df966761321 Mon Sep 17 00:00:00 2001 From: hyeokjun32 Date: Thu, 30 Apr 2026 21:39:00 +0900 Subject: [PATCH 09/10] fix: persist studio demo evidence as selectable job --- inferedgelab/studio/routes.py | 81 +++++++++++++++++++++++++-- inferedgelab/studio/static/app.js | 30 +++++++++- inferedgelab/studio/static/index.html | 8 +-- tests/test_studio_routes.py | 36 ++++++++++-- 4 files changed, 139 insertions(+), 16 deletions(-) diff --git a/inferedgelab/studio/routes.py b/inferedgelab/studio/routes.py index 78ac2ae..5f9acb6 100644 --- a/inferedgelab/studio/routes.py +++ b/inferedgelab/studio/routes.py @@ -1,6 +1,7 @@ from __future__ import annotations import json +from datetime import datetime, timezone from pathlib import Path from typing import Any @@ -24,6 +25,7 @@ "onnxruntime_cpu_result.json", "tensorrt_jetson_result.json", ) +DEMO_JOB_ID = "demo_yolov8n_trt_vs_onnx" STATIC_ASSETS = { "app.js": "application/javascript", "style.css": "text/css", @@ -63,11 +65,13 @@ def studio_jobs(request: Request) -> dict[str, Any]: store = _get_studio_job_store(request) jobs = [] if store is not None: - jobs = sorted( - getattr(store, "_jobs", {}).values(), - key=lambda job: str(job.get("updated_at") or job.get("created_at") or ""), - reverse=True, - ) + jobs.extend(getattr(store, "_jobs", {}).values()) + jobs.extend(_get_demo_jobs(request).values()) + jobs = sorted( + jobs, + key=lambda job: str(job.get("updated_at") or job.get("created_at") or ""), + reverse=True, + ) return { "source": "/api/jobs", "count": len(jobs), @@ -77,6 +81,10 @@ def studio_jobs(request: Request) -> dict[str, Any]: @router.get("/studio/api/job/{job_id}", include_in_schema=False) def studio_job_detail(request: Request, job_id: str) -> dict[str, Any]: + demo_job = _get_demo_jobs(request).get(job_id) + if demo_job is not None: + return demo_job + endpoint = _get_api_endpoint(request.app, "/api/jobs/{job_id}") return endpoint(job_id=job_id) @@ -121,6 +129,7 @@ def studio_run(request: Request, payload: dict[str, Any] = Body(...)) -> dict[st if isinstance(options, dict): analyze_payload["options"] = dict(options) job = endpoint(payload=analyze_payload) + job["display_name"] = _build_analyze_display_name(job) return { "status": "created", "source": "/api/analyze", @@ -150,9 +159,13 @@ def studio_demo_evidence(request: Request) -> dict[str, Any]: imported_results = _get_imported_results(request) imported_results.extend(results) compare = _build_imported_compare_response(results[0], results[1]) + demo_job = _build_demo_job(results, compare) + _get_demo_jobs(request)[DEMO_JOB_ID] = demo_job return { "status": "loaded", "source": "examples/studio_demo", + "job_id": DEMO_JOB_ID, + "job": demo_job, "count": len(results), "results": results, "compare_ready": True, @@ -190,6 +203,7 @@ def studio_path_fallback(suffix: str) -> RedirectResponse: def register_studio(app: FastAPI, job_store: Any | None = None) -> None: app.state.studio_job_store = job_store app.state.studio_imported_results = [] + app.state.studio_demo_jobs = {} app.include_router(router) @@ -205,6 +219,14 @@ def _get_imported_results(request: Request) -> list[dict[str, Any]]: return imported_results +def _get_demo_jobs(request: Request) -> dict[str, dict[str, Any]]: + demo_jobs = getattr(request.app.state, "studio_demo_jobs", None) + if demo_jobs is None: + demo_jobs = {} + request.app.state.studio_demo_jobs = demo_jobs + return demo_jobs + + def _get_api_endpoint(app: FastAPI, path: str) -> Any: for route in app.routes: if getattr(route, "path", None) == path: @@ -274,6 +296,55 @@ def _load_demo_result(file_name: str) -> dict[str, Any]: return _with_compare_keys(result) +def _build_demo_job(results: list[dict[str, Any]], compare: dict[str, Any]) -> dict[str, Any]: + now = _utc_now_iso() + runtime_result = results[-1] if results else {} + return { + "job_id": DEMO_JOB_ID, + "display_name": "Demo: TensorRT vs ONNX Runtime", + "status": "completed", + "created_at": now, + "updated_at": now, + "input_summary": { + "workflow": "studio_demo_evidence", + "model_path": "examples/studio_demo/*.json", + "notes": "Bundled Local Studio demo evidence", + }, + "result": { + "runtime_result": runtime_result, + "comparison": compare, + "deployment_decision": compare["deployment_decision"], + "summary": compare["judgement"]["summary"], + }, + "error": None, + "links": { + "self": f"/studio/api/job/{DEMO_JOB_ID}", + "compare": "/studio/api/compare/latest", + }, + "next_actions": ["review_compare"], + } + + +def _build_analyze_display_name(job: dict[str, Any]) -> str: + input_summary = job.get("input_summary") or {} + model_path = _first_display_value(input_summary.get("model_path"), input_summary.get("artifact_path")) + model_name = Path(model_path).name if model_path else "analyze job" + options = input_summary.get("options") if isinstance(input_summary.get("options"), dict) else {} + backend = _first_display_value(options.get("backend")) + device = _first_display_value(options.get("device")) + suffix = f" ({backend}/{device})" if backend or device else "" + return f"Analyze {model_name}{suffix}" + + +def _utc_now_iso() -> str: + return ( + datetime.now(timezone.utc) + .replace(microsecond=0) + .isoformat() + .replace("+00:00", "Z") + ) + + def _with_compare_keys(result: dict[str, Any]) -> dict[str, Any]: enriched = dict(result) if not enriched.get("backend_key"): diff --git a/inferedgelab/studio/static/app.js b/inferedgelab/studio/static/app.js index e743701..5cd2618 100644 --- a/inferedgelab/studio/static/app.js +++ b/inferedgelab/studio/static/app.js @@ -366,12 +366,15 @@ async function loadDemoEvidence() { const results = Array.isArray(payload.results) ? payload.results : []; importedResult = results[results.length - 1] || null; compareData = payload.compare || null; + selectedJobId = payload.job_id || payload.job?.job_id || selectedJobId; + selectedJob = payload.job || selectedJob; setState("#demo-state", "completed"); setState("#import-state", "completed"); setStatus("#demo-status", "Success: demo evidence loaded.", "success"); setStatus("#import-status", "Success: demo ONNX Runtime + TensorRT evidence imported.", "success"); renderImportEvidence({ result: importedResult }); renderImportedResult(); + await loadJobs(selectedJobId); await loadCompare(); } catch (error) { setState("#demo-state", "idle"); @@ -469,7 +472,7 @@ function renderJobList() { return; } - currentJobs.forEach((job) => { + currentJobs.forEach((job, index) => { const row = createElement("button", "job-row"); row.type = "button"; if (selectedJobId === job.job_id) { @@ -479,14 +482,35 @@ function renderJobList() { const main = createElement("span", "job-main"); main.append( - createElement("strong", "", job.job_id || "-"), - createElement("span", "caption", job.updated_at || job.created_at || "-"), + createElement("strong", "", jobDisplayName(job, index)), + createElement("span", "caption", jobCaption(job)), ); row.append(main, createElement("span", `state-pill ${normalizeState(job.status)}`, job.status || "idle")); target.append(row); }); } +function jobDisplayName(job, index) { + if (job.display_name) { + return job.display_name; + } + const input = job.input_summary || {}; + const modelPath = input.model_path || input.artifact_path; + const modelName = modelPath ? modelPath.split("/").pop() : ""; + const prefix = modelName ? `Analyze ${modelName}` : `Analyze job ${index + 1}`; + const options = input.options || {}; + const backend = firstDisplayValue(options.backend); + const device = firstDisplayValue(options.device); + const suffix = backend || device ? ` (${[backend, device].filter(Boolean).join("/")})` : ""; + return `${prefix}${suffix}`; +} + +function jobCaption(job) { + const timestamp = job.updated_at || job.created_at || "-"; + const jobId = job.job_id || "-"; + return `${jobId} · ${timestamp}`; +} + function renderJobDetail(emptyMessage = "Select a job or import a Runtime result.") { const target = document.querySelector("#job-detail"); const selectedStatus = document.querySelector("#selected-job-status"); diff --git a/inferedgelab/studio/static/index.html b/inferedgelab/studio/static/index.html index f167aac..495feba 100644 --- a/inferedgelab/studio/static/index.html +++ b/inferedgelab/studio/static/index.html @@ -137,8 +137,8 @@ } } - - + +
@@ -331,7 +331,7 @@

Future Work

- - + + diff --git a/tests/test_studio_routes.py b/tests/test_studio_routes.py index 2aa2781..625bb3b 100644 --- a/tests/test_studio_routes.py +++ b/tests/test_studio_routes.py @@ -60,10 +60,10 @@ def test_studio_route_returns_local_studio_html(): assert "Import" in html assert "Jetson Helper" in html assert 'data-critical="studio-dark"' in html - assert 'href="/studio/static/style.css?v=14"' in html - assert 'href="style.css?v=14"' in html - assert 'src="/studio/static/app.js?v=14"' in html - assert 'src="app.js?v=14"' in html + assert 'href="/studio/static/style.css?v=15"' in html + assert 'href="style.css?v=15"' in html + assert 'src="/studio/static/app.js?v=15"' in html + assert 'src="app.js?v=15"' in html assert "file-protocol-warning" in html assert 'placeholder="results/latest.json"' in html assert 'value="results/latest.json"' not in html @@ -127,6 +127,8 @@ def test_studio_static_assets_include_redesigned_ui_contracts(): assert "request record only" in app_text assert "loadDemoEvidence" in app_text assert "/studio/api/demo-evidence" in app_text + assert "jobDisplayName" in app_text + assert "jobCaption" in app_text assert "compareStatList" in app_text assert 'aiguard: hasGuardEvidence ? "completed" : "optional"' in app_text assert "#0b0f14" in style_text @@ -216,6 +218,7 @@ def test_studio_run_api_creates_analyze_job(): assert response["status"] == "created" assert response["source"] == "/api/analyze" assert response["job_id"].startswith("job_") + assert response["job"]["display_name"] == "Analyze yolov8n.onnx (tensorrt/jetson)" assert response["job"]["input_summary"]["model_path"] == "models/yolov8n.onnx" assert response["job"]["input_summary"]["options"] == { "backend": "tensorrt", @@ -236,6 +239,7 @@ def test_studio_run_job_can_be_listed_and_selected(): assert jobs["count"] == 1 assert jobs["jobs"][0]["job_id"] == created["job_id"] + assert jobs["jobs"][0]["display_name"] == "Analyze yolov8n.onnx" assert detail["job_id"] == created["job_id"] assert detail["status"] == "queued" assert detail["result"] is None @@ -325,6 +329,9 @@ def test_studio_demo_evidence_loads_compare_ready_pair(): assert response["status"] == "loaded" assert response["source"] == "examples/studio_demo" + assert response["job_id"] == "demo_yolov8n_trt_vs_onnx" + assert response["job"]["display_name"] == "Demo: TensorRT vs ONNX Runtime" + assert response["job"]["status"] == "completed" assert response["count"] == 2 assert response["compare_ready"] is True assert response["results"][0]["backend_key"] == "onnxruntime__cpu" @@ -339,6 +346,27 @@ def test_studio_demo_evidence_loads_compare_ready_pair(): assert compare["new"]["backend_key"] == "tensorrt__jetson" +def test_studio_demo_evidence_is_listed_and_selectable_as_job(): + app = api.create_app() + request = SimpleNamespace(app=app) + demo_route = _get_route(app, "/studio/api/demo-evidence") + jobs_route = _get_route(app, "/studio/api/jobs") + detail_route = _get_route(app, "/studio/api/job/{job_id}") + + demo = demo_route.endpoint(request=request) + jobs = jobs_route.endpoint(request=request) + detail = detail_route.endpoint(request=request, job_id=demo["job_id"]) + + assert jobs["count"] == 1 + assert jobs["jobs"][0]["job_id"] == "demo_yolov8n_trt_vs_onnx" + assert jobs["jobs"][0]["display_name"] == "Demo: TensorRT vs ONNX Runtime" + assert detail["job_id"] == "demo_yolov8n_trt_vs_onnx" + assert detail["status"] == "completed" + assert detail["result"]["runtime_result"]["backend_key"] == "tensorrt__jetson" + assert detail["result"]["comparison"]["base"]["backend_key"] == "onnxruntime__cpu" + assert detail["result"]["comparison"]["new"]["backend_key"] == "tensorrt__jetson" + + def test_studio_importing_two_compatible_results_returns_compare_data(): app = api.create_app() request = SimpleNamespace(app=app) From a5e14bdc64274a74b174bf77ad57107cab9c17f7 Mon Sep 17 00:00:00 2001 From: hyeokjun32 Date: Thu, 30 Apr 2026 21:58:24 +0900 Subject: [PATCH 10/10] docs: package local studio portfolio demo --- README.md | 35 ++++++++++-- Roadmap.md | 54 +++++++++++++++++- assets/images/local-studio-demo-evidence.png | Bin 0 -> 166232 bytes .../portfolio/inferedge_1page_architecture.md | 3 +- docs/portfolio/inferedge_pipeline_status.md | 8 +-- .../inferedge_portfolio_submission.md | 11 ++-- .../inferedge_resume_interview_summary.md | 3 +- 7 files changed, 98 insertions(+), 16 deletions(-) create mode 100644 assets/images/local-studio-demo-evidence.png diff --git a/README.md b/README.md index 5897f41..558faaf 100644 --- a/README.md +++ b/README.md @@ -61,13 +61,13 @@ Implemented today: Lab API response contract, `/api/compare`, `/api/analyze` in- Runtime identity polish: when a Forge manifest is applied, Runtime now preserves the manifest `source_model.path` identity for comparison naming. A TensorRT artifact such as `model.engine` can therefore keep `compare_model_name=yolov8n` and `compare_key=yolov8n__b1__h640w640__fp32` instead of degrading to `model__...`. This is provenance/compare-readiness polish, not production SaaS infrastructure. -Not implemented yet: real worker daemon, full automated Forge/Runtime execution from production Lab workers, DB/Redis/queue, file upload, SaaS frontend, and production auth/billing/deployment controls. +Not implemented yet: real worker daemon, full automated Forge/Runtime execution from production Lab workers, DB/Redis/queue, file upload, production frontend beyond Local Studio, and production auth/billing/deployment controls. Portfolio entry points: [portfolio submission](docs/portfolio/inferedge_portfolio_submission.md) · [resume/interview summary](docs/portfolio/inferedge_resume_interview_summary.md) · [1-page architecture summary](docs/portfolio/inferedge_1page_architecture.md) · [pipeline status](docs/portfolio/inferedge_pipeline_status.md) Interview one-liner: **InferEdge is an end-to-end inference validation pipeline that converts, runs, compares, diagnoses, and decides whether an edge AI model candidate is ready to deploy.** -Final interview angle: InferEdge has both macOS ONNX Runtime CPU smoke and Jetson Orin Nano TensorRT smoke evidence, while production worker daemon, persistent queue/database, frontend, auth, and billing remain future work. +Final interview angle: InferEdge has both macOS ONNX Runtime CPU smoke and Jetson Orin Nano TensorRT smoke evidence, while production worker daemon, persistent queue/database, production frontend, auth, and billing remain future work. --- @@ -84,6 +84,23 @@ TensorRT Jetson was 4.6x faster than ONNX Runtime CPU in this real image input b The benchmark uses end-to-end Runtime latency, not trtexec GPU-only latency. The full pipeline portfolio summary is available at [docs/portfolio/inferedge_pipeline_portfolio.md](docs/portfolio/inferedge_pipeline_portfolio.md), and the detailed Runtime comparison report is available at [docs/portfolio/runtime_compare_yolov8n.md](docs/portfolio/runtime_compare_yolov8n.md). +## Local Studio Demo Evidence + +InferEdge Local Studio can replay the bundled portfolio evidence without requiring a live Jetson device during an interview walkthrough. +The `Load Demo Evidence` flow imports the ONNX Runtime CPU and TensorRT Jetson Runtime JSON fixtures from [examples/studio_demo](examples/studio_demo), refreshes Compare View, and keeps the demo pair selectable in Recent jobs while the local server process is running. + +![InferEdge Local Studio demo evidence](assets/images/local-studio-demo-evidence.png) + +Verified demo fixture values: + +| Backend | Device | Mean ms | P99 ms | FPS | Compare Key | +|---|---|---:|---:|---:|---| +| ONNX Runtime | CPU | 45.4299 | 49.2128 | 22.0119 | `yolov8n__b1__h640w640__fp32` | +| TensorRT | Jetson | 9.9375 | 15.5231 | 100.6293 | `yolov8n__b1__h640w640__fp32` | + +Studio reports this as a `4.57x` TensorRT speedup for the bundled demo pair. +AIGuard remains optional in this local Studio path; if Guard evidence is not loaded, the deployment decision explains that the Lab comparison is available but diagnosis evidence is not provided. + --- ## Reproducible Review Flow @@ -372,7 +389,7 @@ More details: [FastAPI API usage guide](docs/api/api_usage.md) ## Local Studio -InferEdge Local Studio is a local-first browser interface for inspecting the existing CLI workflow, API/job contracts, result metrics, and Lab-owned deployment decision structure. +InferEdge Local Studio is a local-first browser interface for inspecting the existing CLI workflow, API/job contracts, Runtime evidence, Compare View, Jetson command helper, and Lab-owned deployment decision structure. It runs on the user's machine through the FastAPI server and is intended as a local workflow UI foundation, not a production SaaS dashboard or cloud dashboard. ### Run Local Studio @@ -387,7 +404,17 @@ Open: http://localhost:8000/studio ``` -The first Studio skeleton uses local static assets only and renders demo placeholders for the pipeline flow, evidence summary, result metrics, and deployment decision. Future work can connect these cards to real `/api/jobs`, `/api/compare`, and `/api/analyze` responses while keeping DB/queue/upload/auth/billing outside the current scope. +What works today: + +- Run creates an in-memory analyze job through the existing `/api/analyze` contract. +- Import accepts a Runtime result JSON path or pasted JSON payload and adds it to the in-memory compare-ready evidence set. +- Load Demo Evidence imports the bundled ONNX Runtime CPU and TensorRT Jetson fixtures for a stable browser demo. +- Compare View shows TensorRT vs ONNX Runtime mean latency, p99, FPS, latency diff, and speedup when compatible evidence is loaded. +- Jetson Helper shows the local command shape for running the Runtime on a Jetson device. +- Deployment Decision stays Lab-owned; AIGuard is optional deterministic diagnosis evidence. + +Current non-goals remain unchanged: no DB, queue, upload service, production auth, billing, or production SaaS worker orchestration. +Jobs and imported Studio evidence are in-memory and reset when the local server process restarts. --- diff --git a/Roadmap.md b/Roadmap.md index 03289b6..ebc25de 100644 --- a/Roadmap.md +++ b/Roadmap.md @@ -89,7 +89,7 @@ Improve usability, discoverability, and expansion paths beyond the core CLI work - [x] Provide richer CLI presentation with Rich - [x] Generate HTML benchmark and validation reports - [x] Run automated benchmark / validation checks in CI -- [ ] Add a web dashboard mode +- [x] Add a local-first Studio workflow UI for portfolio demo and browser inspection --- @@ -124,6 +124,56 @@ Improve usability, discoverability, and expansion paths beyond the core CLI work ## 🔭 Future Direction - [ ] Complete full RKNN runtime backend integration so curated and runtime validation share one end-to-end device workflow -- [ ] Evolve the current API adapter into a foundation for a web dashboard or SaaS-style validation surface +- [ ] Keep Local Studio as a local-first workflow UI, and only later evaluate whether a production dashboard is justified - [ ] Add memory profiling so deployment decisions are informed by both latency and resource pressure - [ ] Explore multi-device distributed benchmarking for larger validation fleets and lab-scale experimentation + +--- + +## Cross-Repository Roadmap + +The current portfolio boundary is intentionally local-first and evidence-driven. The items below are future development directions, not current claims. + +### InferEdgeForge + +Forge should stay focused on build provenance and artifact handoff. + +- [x] Emit manifest and metadata records for source model, artifact, backend, target, precision, shape, preset, and build id +- [x] Provide worker/runtime summary data that can feed Lab and Runtime contracts +- [ ] Add stronger build reproducibility checks across repeated artifact builds +- [ ] Expand preset coverage for Jetson TensorRT and RKNN build targets +- [ ] Add artifact package export suitable for sharing with Runtime without manual path coordination + +### InferEdgeRuntime + +Runtime should stay focused on real execution, profiling, and Lab-compatible result export. + +- [x] Provide C++ execution/result export boundary +- [x] Validate Lab worker request payloads in dry-run mode +- [x] Export compare-ready Runtime result JSON for ONNX Runtime CPU and TensorRT Jetson evidence +- [x] Preserve source model identity for manifest-backed TensorRT engine results +- [ ] Harden Runtime execution error reporting for failed engine/model loads +- [ ] Add memory/resource profiling to complement latency, p99, and FPS +- [ ] Complete RKNN runtime execution so curated RKNN evidence and live Runtime execution share one path + +### InferEdgeLab + +Lab should remain the comparison, reporting, API/job contract, Local Studio, and deployment decision owner. + +- [x] Compare Runtime result JSON by `compare_key` and `backend_key` +- [x] Generate Markdown/HTML reports and API response bundles +- [x] Provide in-memory `/api/analyze`, `/api/jobs/{job_id}`, and worker request/response mapping contracts +- [x] Provide Local Studio for Run, Import, Demo Evidence, Compare View, Deployment Decision, and Jetson command helper +- [ ] Add optional persisted result storage after the portfolio demo boundary is stable +- [ ] Add production worker daemon integration only after Forge/Runtime handoff is reliable +- [ ] Improve multi-model evidence browsing without turning Studio into a production SaaS surface + +### InferEdgeAIGuard + +AIGuard should stay optional and deterministic. It should explain evidence risks, not replace Lab's final decision ownership. + +- [x] Diagnose provenance mismatch with rule/evidence based detectors +- [x] Preserve `guard_analysis` in Lab reports/API/deployment decision bundles +- [ ] Add more detector coverage for missing manifest fields, backend mismatch, precision mismatch, and suspicious result deltas +- [ ] Add clearer guard evidence examples for interview demos +- [ ] Keep AIGuard optional in Studio until the evidence contract is strong enough to justify a UI action diff --git a/assets/images/local-studio-demo-evidence.png b/assets/images/local-studio-demo-evidence.png new file mode 100644 index 0000000000000000000000000000000000000000..abf2f38ef627babe5b07b64369438d690c7d8ee1 GIT binary patch literal 166232 zcmXtAV{j#1*9|ARvF+U0zOilFHfO?#ZEIpLC3tV&R+k^g zo#n5tsSC<>%k{4!3u;7~fz(edtjM(rT zEj_=kg6NyO_W*FFnQJnkkVv7HRsB91h$3yo33LLj^9#Pkr`~}-`TD};Ct3EhjJiwQ zj4($MOK*#l2V^XMZa;^w@ve6GT^qb=%3HKOAa71F4Ky3}7+mf2BFEB*tX|;itcFp# zE>CM;XcBNP9rs002AD2f#Z0*ANdC)VS{2Q6cT8?6xLjh{U^3t%LJkBw2S-+4pzi1= z70xU<$?rq=RgX)t$}yZg=1=Sakbx^9=K14HKK?ubmkjOFHgJAbC{+A~E%=R7_?mj2M%fafMij)=ju*Ty;yVPt@6 zD9J7o7x3nI{b(Tf0|rPzge8DQYU+#`!R3uqmqFo0qN|T{|HMKD1B5ElS`Oz!VoPIF$yd)HO!S6kuWlXt>2R*)50fy3wXZ{li40-S#qT!<-3_SY% zsa`mr0^g8;sGZ_y;Xo&9Kzd04W52{VO&^##t@aj;v4mp^CA@C5!B;B+=1(u+2D}F8 z&~ge^kQKZRJI;ktfUG=SZsO&~$4_qYPs#t9umXT$AG_ z477i^HWoL0H#c~w{|J=d~c^+YT$nt zVj&%PgnCXM7lDlcnpfO!?*TE!abcKk3_SF{B{e;BV-%iZy%GWmP`@=e_cSQ^#&csHR+g_*an?i4FPUYo>q*f z6k0dUbuD7QqYOxP%d*1Ta&*wI`F2uGK>3Ya+KAX|prpWh?AZwrtF{3316k}hnDagv zKnyMYCqN33{*IgAvOh1?ZE6o@y|BG+#H4N@Z3$&8OuZY0v|fxCeZu7oy~c$IcY25& zQPA%k<&dNLOn&JAGkdbq0Y?l)mgU4W?D;0ydgPbjo1784svWZhBIlWiE`tgxSve~8 zVpVu!78$Fb*%^qAs)y&E0L%U_lzFNI9Rkw}eF_&MjtYROmG4 zB9w5^lBbbupbuI(nY)Oze{NM~TXyGb+{0@fG>U1t z+UWOA=H$qH;TIwEw=AP@g=!E=KMz_H8EQsHxf`o@LFkA$JD-?8=)>ZVw2p{Wx)d;+ zZ-PYnNvuUH2*N>J&>#z@hmI(qVQs9sXD>wmWZP)^7nb_R9G7}zrwy+J;WE^2gvF=sg%NAIW zvA1HyJ{$<-D~8&4P<*0@Cxf5t8-aXwMI%;uicmZWhx|EGG#-o6+XrL?dF4o<3B6ti zWriA5X)Gx95ry;Si&>8$uU%zZYnvk$N~ky{O3#ofc{{Z^s1SM|htiIx$u)|$Vznik z&g|#VXGa%UU5kj|!m6LhN_P-K9D!M9-KcWF{QAspNbFab6I)|Vc(R~H9E2@5WjQ|=x$SePMe zSvVvY<+o-!DXOzUi)kE3f^iU43@ipk>Ts;f&3>asWr!z09-b{PAsQA4VJMFNhpFC( z6^a?kI66>C4uS#%_!}1lfiU(C><@-beujg@!F?ikH<8|kKHV$~H8@527s>;yr2me@ zu0oSd*$BhzRVUH*)(rX?*fe%h=>0sQM7vbXRf0=&azHXTLrwg|!L1UB0F*QxAT%%LFC<_m zpe80%lwBnP=~hm!Y7=PBlO2G;y=e1&xeuAfCEseA3gZ0c6f_}{4^K%E<}sW+49v%H zf=}{zN^;&#ReVd(NF{4vUY0A66;_4{ADg`?H*l;qu%tYfbdG}ubO_xOag5>Kt2pSq z4@6;4W%H(K+MLbLt9|z9sf`W~4^L+EYStLUr1q$NTS)hLa%?@oD#&)<^lW#(@_jie zRmO(O@wpvYj=|@0SZ}h&r#!el7<+wrvBFRS@%}hp?(k%z&Fp~967>7X;&#o`piTL4 zvD#=kl`}IlqsK@^(*yY-Q!GMqKQuJt?d63f*2Gy6ge<5@vAv?Y(S0H^=1Hj zMmKHG&5iWW%G=Y^F(fkIyxS<-3qK15b#I1cj+8D=RBlxC56AA+a!vL7mHs3-J!V8`b@bwdN#f4M0_| zd78dNc(j}Zoo2OIXkTBSZZjVn`A6X}RXkKIo>#da27U^>|DP3l%y3J0_vR%H+|`R{ zlNj1NY(}u$S&fDyfbsRa!`^)+A*I)5KY9V!bw<62Z{$*oD6M zF+4UErOjL^llJRR>@uv?zsf;;me-oJ2X2&WPFZ+uwxJbK0k(~EmnUc9NbO;dJq*#` zzki>R0ymQmhi(XaG$A$=w1a?9hXx;mEA$CJ|B*jpf_ zJlGDk7D;3q<$^rdM(~3KVU>P6R;HOSg#=ZhW=;2pmw(JRfyZ@MMgD(k^QbS8L>vfw zZ`Xa)X5o2hsf_w<)^n80eNN(r8_q+7MP3JVh7q)(x1osmz7KzL=jyZkKAz*fPRmPY zt}$RJQy!vUW84NPdOQ;0TZ&;HH@%rM<3)^FjWm?xa+2@m9lg-kH@(iPh9w_(jlhn5 zzgo+GE_}ja1qzED@0*XMDnq50riY*X{xhl5XoFebTQ5{gts5yVCnFP!lKj$k3}h&ozQvdWK44qIl*^0Teak4ItFe~CQuKcT_Uj;QT#p=sxh z2ETjs1~tg$E5b@|gYW*6pvb7tbjW6Vr=*m;*@wp!bfQ}z%XJHCJ!m@O0$798`4ABJ zui|FhK9&V1)D%Ev{UmICy~35yYW+Ppt+MNh<>&B9-puZ(NEF3xV*d8K!w>Muy+Aa7 z#SvQb*0<`_Ny*@9<;IsJt6E-_CT-{RuROPaO}~21xP4i>baNrfvCn^ zi31S`N%_Z78yRR3!x0;()UuFyHrgZtMatk+iBb#>5ds_1#u%aaZy04!R`YC=pdcPe z3JGjfDQwgbQ4m~~=C$0gsvY^jF347SM*lN`SfEHWoWx+$MS zDJ_+EapQPFQJ2#n@}V#sLDoWhsG?X5cV7)Ns0|1W6G+Y{$0r|^5(%x&|3+BeHj;c! zO)lOeoQk#IzV*&nP7R+3UQm?y8!ZXJI8kN%7ndRuG2iR?pJSCLs5i5)%n(>We)M<( z=T2>8ZXmhzs$_heFlkJ7S!#DUh12}BEJtD_296TEr}&go24Ay!rCPRE5Knf)Kxy`x zyPeOebp_4npA{$iK06L=!g;T!zhR6W*CI9y9Qg8qGNcgtdcO_jB;A<8LqLaQ9;PH= z0k{AhvOQ8s1Q#+h$B zU|Ri9Nln(c&ibmR41&{rt+_$AV4{Xb5l-bgK3M|U=#7b5Jq$_ARaD-TQy#}={3*@E+eaz3{#1X5HkxhLI>uOqd;ZCVv zBG1eEsTJc9fKnnJu)q>Iykno!_lxE&q@UE&Nn+Zm#;v=0o0X6DNSc z8pB4yJmnZ#eBLz2A01QjqBL}eB*aTh_8O24X0eanhybjO7iLYRDo_6uV!Km+z^}`i zVYAAgQ{Y965xeNV>Uyn>A30d+{@~!r*k4|l7M?<3Wnm3mO*jo#a|#(GPsze9aSp&e z22=WrDAEJU%H4=!kp)l#^DXVxKDz$O6i;>qINcbo+d|u^ zgUkTsf%8TcB;S*5#5(&$4=NY&=1H|V%5`xTri*BJ~QE1cEmCP1Jc+q%?R~$plu#B;c&ra zxX@zJE+*MM^NEiF6M86RZ2cG&Q9U_;$fJbRV>WbIrpEqkgMGgHi|eruMs)2VA_ye8yRy5N4*CjkUC2IunfH| zRU=9Wxv`wjG(2p%52%8Ex;Pe1_*AHZoah=%2MCrosQDJ06ih375Q^7t&WP3)pBPSo z)Y^mQY!vE54d$`JE)pTCN;R;m8Uy=RK^YE*Fc* zMQmCl1Tnt#525B_1z^FrIWtXsn8mgEmE2~W@LUv5I*DC3$M?XCT`A)?zA$LEKFhO&3fx4T ze-Q);Bs@c~n*E6*Q?1of8R~w}>IqOxOLb!&U+TAFsPt%EJe{+c>9d7-`x`1;hdclH zR&|Jgn`wNXF?z&&kZoM8e7~pszgFW01ky`UvIH&NB8~&?LQ|Lx&nVjyv1IrWsMeRypx}^Ib6^p<0V4^XQzX)`j&L` z=);m7{O${~JhOiQsi21c3>X{UAAd?g{Gkhg?espN6NZQNOiNGpNPX85$$8{O5z1>w z^}JQLvYkwhj3}l_hyq^o#tQ{+c8XZ&sB4WG3u+W#3tHXtiloV?U442pn~Fir2qHbw z4g<-0?~J?r3WmSjPb-=K*j&?A7mj?4TNB0-Po_e-o4nzKK^%g3-$V|GyuO8-U$%}$InvFEoeiOALOD9|WLVh_Fa z7p4brlHp4mEmEN5yDy3pn14CJsX%7adzZyI(oc4A0?6V&hS_KSzZ`lSPE%+V9me*8 zC34nBEc;EXEXb{Y*>_`lFfMJ~mNxtp2~Vm;SSnA->N5{a4I4rknk|!=PUXIh3`;7R z|1spQyN{-6$_Gdz6{8FW(eQ#p_mYbHuvM>xM&v4*O+W|S;H>?);gr`8qU&&imGyZ3 zBj1Vc;X|o3njJK`k~al7zg2)lO3o_YBL5=19U?2lCiiPOj7KKs6i>EzR?iXlQVaV? zp#jpt5Oxb`G@y^7Yhr1LL;HTQY07(t>Os74P~NKT=F1QVNP zI{<-VMGp_FMA%P(zDYX>qX%U={ngr4W?=GXP1z zC-u;9Slk?zh3IyB$$yp5M7ffviT|gA@O!|3!x6MdzCBdTm5a3dP;_YDg`m~^QAl9Q zVE0qfLAbpXw88#Z{*of`(Ya50RuHTCeH_GQ!m!piLh-PmE|*{6H^Lm&!r1iE-N6*O z+cqX9Swdhm8Ns6U-*%|gsguZaJqAL^ROQD)F+4))&_psitR#oChrezIW_+|K{lYZf z_OdxOXXYThqJpq=n^i#d0>;fVT_X0YDb+`T}4>Q zfJjkf*U1)_hfnQ%mxv@B1u1J7S}?aLm6DwLdNiqH7>@u638{yaG(LnS6SA7A$a-6P z4s!CDmXsl2b#U3Cxzz_YbR;1dbU8U7h%cxs#+C?%<`HnrZ}ZM|Pt*z#8pYK0R8d)7 za2h0C0s5(u(-Q>SbDR5dk_)T=hxFJ3U*o)xi1**T603zDd~C^6IBHum#_t7-|L{#TwflX z!)oSBEc>|$?l0KuiRkxzgE;?G43a-OiTT1(`FnKLZ!S;%M6Bq+pz9gLgOz{&q(GAt$ z&|C(B4GKR1wQKgLxL+XMEQCJUUK$UUEKX(-1*w_&ng2o7nC0fvAQ#%^9Hc3dv%y`a z=K0E<#`eo%bJzF2P(`ujmm&%?t`n(p9)(k>$3zP)@8s+1V}B5la|>+;FtWG9T6 zlBsRM%6gZ{u@Y*umQnS- z&a&IkI+JW&-CRVxZdC(`{pbDc#RaO}%KpdH<1^`t+TFhIX9kZv5YEe;emB^D0pDYC zpxV9^`&F30Jt>MJ%;*$4O|*;sBt=Sh9q{_s$dyz zwYz%<(NSm#Tp!GJ5Z7|usSmM#s07+V%wxjV0Mbo)NeH%oRx(7v-Tl$M>n_{#?oiqm z_Z?yUOB~dQ5U77{T=}Ni*5^J}u>coO#Ia*pyO`X~1o&v-XLiFLwYT8S`U zr#5$(uhg5Xy`NRLBdvdIY`qhrK8;xA~WobN**a z0De{Qb!Dpe+f7)gOCo6Twp8%l8V?(fvUdmlf<^yDUbBFqUmF zrByqQ_uKVvgU#E5@|vKZ37wP9$D%<5@peX*xSM_Vx;`Cgp!|`t9KxQe9V6 z`XYC(&dp_y7||}ZY{n6t4+(y-48|P$bbY>G>2`RqfOTK@!ecQKEZ3Qy$9){@ACD|` zUN$NUzO!HJwz&cHO=e3}Ml5qyURIa7LIX^CwJpro4u_?l3!*05FWZO%UC*yor0F-G z#=764L`cIfH#)t!9y|+`$BzEQSQJ?xKdwpa3~539srF_86~9X}B@wvUgGr>DWrD1Kb7OQS{G3&F6?^OI06fdr-yq8#1)q}y zpKs#)=l&(>NMFgIzh}vi@d)qb^9LiZb|1p zth-J@GjGR(z`m^~LZ#59a6&*>7=GNQbw3R-`JTucng~3)vJ}`x3HiY3BYYY(Nh7a_n?NW6JO#LBdpcvRit+Sn9 z4)voHgk*fr??P<)05~Nmpi&Ax*JaOLLYz;qHU4zF+lh*W7Y?-u!&C-222+p3>yA=4 zR4)%oZ}yk>Xf>Jpes0;5*h*{y!7NdM6J8Ji7AA+s|Fj)MZRQ{O_wDP}K_n}4pdL16 zo3Ed5Gsop-!Vbnx;?Nh$$TbwOW#B>- z&-SE-^8?$<45JO^DZ?Q=(i_E6r6l}z^SQw~gM<9Qn0VL1fSAw{XrM`oYY0u-@t3lN zo&E;TJBx+_E7axk;VhLFNWj$2t}MS?HwJQfkpxAGo0ak^1amY@R-qBHBo$m7@VX$O zheebL4=p-?YZ`X*csH<*JUnHX1nV{R2^66S@DBI46p)s>15mw=OOoLH7sCSC?ezRK zMrsl$Um;DN2PW8l@ya@q++Cf~ZRDS4}u84-I5-EIwDjT)Y9l zA1r|Q*9XLnmH`WvGVGrkETqO0VS_6|K#d|y_U1HSa4iMF1OXtBy(bd@bXX4?zY`&S z4<1wO!Es6y;}N2}p(jO@FEExx{~&wvEFcs6UnFt8z?VPlq#3-RhQXDyqQM*js1;RM zpctHG!=rnW6#aetUJ?Wod!iH}V1$m{wfQ8{fy-b(B*E(>B|ad2nK_sudzVZVycGZ_ zmT1xjE0{Ncx)U!}(DOxw5p0nP-)$rjhaL@T9lFzO)U$5W$QrFaTZZOSdTI z5!0D)B=&%RX(4ZaP(hj$#8-8M>^?_GP@~@l7yt=G$$tcM7Gp|)QXu*7*Z|+(;R{j_ zFjCvKcbzc(t5*X{5YE3GbQk)?yLJVX9C(aOg*{}Mu>VDli2;B}*f+ZouhKuD?^2L= z_zMYpGqMkK48;HO>UC5`ss*4qib}>5cT%>FWW%iof-bhk0;TZLu&;J>q`FHH6VjJL zb;cKXE*q2Vd_$FEF+WvS*>F39*WiTYcXd994r$fhWZ2Er8Z2oca9MO#BA1G!;IUCE)Kya z!G!7jNeqGlVxDpnc0Jm{>c0s`7c%&ZM!!mx0rlKAG+N|&`v#E2e6itlc+K`|PqXPK0=~?f!#BtetX21Ys5Zfqu zxMZ~DAymJ|8vuC3qRew7sHzwc*aBARQtOLiY(O2VOUTC%Lp=-ykbelxN?S-(GzyFm z+yF&5ER`9VR;(T{U`ydYws6TZOsL`<#jNl@>G%pl$DGLg!QVXAThP&^m4tT{=l?O+ z&)!Jm&^n>Uz|McSX*NACB*Y3=oE_*rG`bq|rDk4Zl;KzEaaz+1u|S_bsCN`*6N8OY z>I~9QBh*>CHU%iogz)?KVy?rJ0BULQbW&ZpjS|j#L&-1TqM`97;;me%skBiH?O#ci zMDHsCY8t0MVS$^R{q<9wc3~nYa!F~mtiefBN+7b=pU?>1gzrNiI*-SyanwYrfN`A8 zS+pd+FpeO22WbCdbUgasY2L;(zcqeqW3Iym=b4nRg88>0$cWMA^{7jdK)$MOPprtE z_Lzv=A~H;h6YN)*9sb#&-;gFC{^AT{P&TsnMqgc+% zX$=I*-Y9|+WCAtzDb8b$2-X2A#@0FT{Hp?)aa+el4Sl({T!S%W1d{*J7nLWQ_NXm% z2@5FblIp{WyPyb20;6&TibMdMV2AVlcxelJi~_yTC%-DR@0W8*1uju>q|(=fWby(7 zU}&)9#43ZU@vPypu3$d3Nu;4(`&|wVMR=2+Mnq8wW~|ArigbQ|nKy2k(>l!q*&jlX zrUD>{)>vkOD8;s$s%|h3X;6!5t3%-1N0w*v2WD-QY?&a3lb?osQu{;i4Bf}$!7QZWVf^Vdkutn?Hu&=&IgW=+&KZY}LAE zrj)l|Xsq9UZ^z^st5V?0sPvSPQ`FWOb=uQjs*MUbZBDH7NL0~Ww;Ni-vl$$JarnKc z;=`|e(VywnwmZP96A(4MYtvkc-(_5(Q7}3J?wsE|pt0X%1@mw@kMR?F2M02*b1$73 z366y!>MS|~1ixG(XFDigMnEFX!VlU$ez;6RygjRV-o!s8Lih^xE8K5mCWgLFkKf%|2I`>o6!EJD(!f zoE%~?E@C6ogAN)zx@_&-64y|D=urQRr(1)`(OY-#i44Q=%TPIFT!5~nioHW69;4Zl zG6FCw>%Ul})*Nd|gYkEHgPM(Qa&}n6|F92)b1dY)#TPCHNDU#PSA_4D8e^m6QG`O%fFSN9&MYgNFw-ETRZGKmQF6aU_H?G zAb~Tzmzng6wNG)}1=tiNCjAS|l);Qr9TAuH($sUg6hETp_LN?O<8w|hQoRdwU}Jk2 zuU_+ei;{F`pS$T`A3i>%%cf5Ku1~(mipj{1B=YVmPv?@}gv ztQS~-sl^uLAJ&nHAi#ux;9r9)2)PJ`VsM)(2V1-qS?>GaTA99z1>Q^^_O$jeI)4`F zn|Q0s2j~}=*D_ZkkJw$8UP`;45|F9d?L?iU_SWiKgBQcE94Acq!}#SiM64ps-OrS~9x5@OWy3RtkANbxGb}_f@%#6lbdb0s)42G=a zN^ezDGOdg6Rhj%Ab32#>-HlpQRM1PpvB>bx>SQ4_DK=&S#gp1~1Rb%d1TOD8PWSMMaZh;(ZP~7+sA&(HJq5v<;lP{pe8UG-2gr1ud+9|G z7NI$QPQ#?-)RoLG@g64oEXWqy@mFWIl{KIr$WSHVRC4{ktX}Fq$~4q_e7D%94X||? z!P($vIYnp1`SA>K7@@3^;Z~@H%XgC8+;#O6z1s5eq3v7tLh4+(naT=M&}ilf^{G~4 z!P?<1=2*%P+t(6#oKc*;Op{r32hX}Y#+LKhxHnJj3ptO8b0K2P?is7h)Q#J>#3i|V zj7VWs`PGRg_ni>zP3OLefrvT{SG&ty3hzj9x#i}!_tG>2ml>-y#mS2rPezhIcI_uF zs>Y2g*5v1@M5f_7ibafF`?G5`-2!Qo9F-{uTxe}br$5?=>Durbg4yE>cu9QG1LQJ0 z(%}Y=`)|jr+nDnKqy+s1cEX?|k9^Z-25B%egTQ{-d zrMY-h7LN@bKgK4kve}K8QcRIdc{NzG>3A60<1J@y#9CWS4kD?pT6urf(iQHFw4@ua zo-vc-QeeR+1!S;2jpC{n zp;ovaA$Z7_ZrqMe=%nf5Gb}>zw%(7@O?eEY2s5rJtQMl9`xee5NpXY@aq_>2tx6%d z{j+J)*nukvlnNW;kS8WF zdH+6~ji{m1BwbdsYH;<8h(NE~)!m5VRK{+sn!k|vec7Xra47~8+weBh-9Qw*-ga%V z*!5-BcejA9`l;q(E!$*eMt)%aFxT6_W+k%j)QK~}gxOY@(R1msrv1@-Y2X-fzVfH7 zqW2xl2X!`A&%@=$ub1fl73|y}pWnAmT^o`MyqsUN?hMdyBxbB}Qa4hhkgW&PJ`Pp; z6!{`H_xZ-&ZZREw9yy}Y=y5M59ao!7W9=EZpUuRh*2)`NZrAGHkk}MW(q(v1%Fmod z*(!Y1jgSriWtk>^n5SGB3a;ifrph>->AgM&0FnTcVvPeY<384B8T269COQ1fUbN7% z4h*=7$=k+hb)0=!AXpU+4m`wgqL|h9)!5bE}}Us|xp8KR#$mxpm4nA4U`(99Q?$ z3B=icIBqr>FXcSTVj)jttI}cWHr_^xM-h1*2b}I>{1J+@&h|b~kGQOsNV8yBQmNUx zHx%@^AL62J)R_JEyG4M5Tg66ALEy^mu$V(4F^~j*>9XT_u(~|ef=b5b{I=4sJE7J` zR8ktx;OOs}qt|ZgTJnv_U%6SrD*H7e*cI`* zH+iv3gd^@#UWoN709~c4pA{&R{L!IY-XBL(=$5MWc|o9X`|*L^JR}52L2J%j-&Jed zMUtuk5)Ff($gJsNK5M9ON?n*tFrl%Z!`KFHGm)5dEkw9v@nqNyAYVCfQlk&?%crCs zyAy*{lu(87vs&`7#xNIotyt3In9;b9)1**R zjLRQg_CNnKPcTC^V<5!@x%U zG@gWP@S4Izvz8-oTPHSFI^{s6Ld$lPZ$9FMxxtxMYbMLnt{nq;a-@hfe$FcwRt zh`9wf*341<$CN5w-SYu-T7A2}<^`}rB2t>FZS61!>XNPVt+u>9Ip^%|h*U{U8s+#b zYxZ7pOlka%dnFU_NAye!nc8f)^QS=@8*s98^9ptBo*T$rpLe;RZZa8Wwj%4p&ub5E zQ-(zz)n^>*NUBXE+)=eHM_9HKBOBguI_Ed9m+Eqb8g`kX6XCb}$HQ5zQO#HDoPL)| z-(ASKn2iu8%P&^Y`{he_sDHfebCd$TZmo40;^JNdmTWWNzVZrO2@8er*>lzJCKcGN zHn z!utv7s{G_KTH1M*goauQd>7UHx3HQ|-*g z5@r^XL6dN^NwRL%cCs^%Rh_WXo~9e?3=-(&T}3_{;jS)FX@ z^cdarzy{?C2uEf3R4?dp=DjXMSCzgJ+eDJ>`SxHqNUQpN$dZ%Co}%k+fa#&6s`&2Z zmqLv8?i^#sQ?X|Egic3qevGuaX2?GXR6iQn-lR`^$paK>l%u>PQGh}hGll(e}|8@~+2@D2Te zviR+tLC`zWRgTjIhY4pdRHb}%ak0|mt#xuSwYK>5aPfYJjiiX_t}$xM*mmFM_b#)b zq9Q7q;-zmc)414jt6yU*Wm%zd1;nWr3ZDC1E!Eg5J3?N<8BtwU_h3~` zMXuu8vs)8O{m>*{g>{zC%rmy(vv&>=)4h9|u0n}l?M#F`#@V>kBQWvvCBzSpcr$^S zCuF^+dPL>Vf<{|g_jR|xd&%QlUEgq0ijmQAQ52(o)8Tn_b#>^POEL+^a4F81q{lP71cc#WHve-k!z+wXz=8*bCgsv!!jEb7)Alu@yUxq{5g-2`;6(j}jh zKL_ZNI^Sc5imH{FRrYk<1%1cdi9B~B#;Q9`@6+`7_ynq#E!w90MjPiAb3UJ@YHF4; zrN`uOoo1`s%m@2=`}|^$@bGUQvY;&ZY%mMB2+rfliA-n$v=+is2TIiKemN^Mm1KK9 za7PamSg5!jad-I*4q$J%e|9P~9+#r|j&u)M(>6EJKckk?s?h`GQHxnu@MG8B`i`iW5g-CA2F{V!E;B>43DwsEO#x#YgE}78%^TTT7 z)-AtXnTBG#MUdm){Du3^Ggm{8b4_%mIv3b7LodlMHkWy7tSuDedhpQmh( zn@@AEj@?J&bCV4w5{wC#lNLh{=VlZWs?8g+;D!%jj+)U8dkyGcLj`_+?6wPf9hMOP z0h;gWKVpsjOQ}>G1twx8sisuu+an16YuKIFM%w+MhtV!uO8E-_>Y;NP(t-HHVS{;D z1aR+UfyVuqR(Z$!?Ng?fq`#4xt@C+zX6W}%H_0OLB!VGDrgTB(hvn91@f5T}P)3Xx zpYrezcN2@o15B@Ws!Yr&clC{0G(a5@Zg#sPwwNqBzWcHpP$4d=(tX40uJVw9W;sKL z%?J%-Fr0gLs`;@w=G(1Cr>g6XJyA&`voUmjn9f}@U?3=q&$Kw6qeq|ApvF*c4OkGW z($q!dvY2bNqq(S8tx1rbmC1a#-cL@EGS$d+9iiyXSzYmZU}>k?TAeed@hCqj>)gn0 z`Ei@cfw0eNF0-^!pCq^R^_`L#lLSN_cfwwGv5SEcs%aScmW9PPvH?w6Po=z`v9Mh zsLs%COAq#?t+8HcEQb|?yl90Dzdojc*j4`nVzXX7R2y2WfwlYK6&(-{XYtVQj~wv1 zdF_3rn@S1IHgoL|n`rYIt)o9x9Ixbf*OaK2bfan*6lt@?XNn)nd^gkKA&7WDWQ!5R&uHSv+ zm|lOxUP$Z`w13`|E7o-G7k{6!WDF&}v}i4WF;L`0hwqr}r>>l4m3sqyDTb^zpOeJ@ z14Kc)zNa>BXpE1rw3^8-wohpB1Oo#~+f_A|mWVYi^G`o_&f=AoT66B;Ks#_By6u-+ zZo9M4iMtcUl$Bh#botQ2h3$mNsg(?UZF%gmXKuco<1@>(J7A{(KO1W0w`OpJn8`n=g_Rz@i zjJQ(^mV+kSg0yBnnQ&_jh3!8bhP={&FlXIFF6Y+$n|}Di+iw1)7wecQ zGu_vF-W9K)_Gj!YD<077nL_Wo-uLI9|DPX7q&yZ#xVrxSPky>*(Xz=(GnenFOclHB z7HL!=6bY zHBRV2e8RpETk|_=w!1JgIc}M>SbPo4n9{}4j#g{;mMyd+JA9WtjjRW)HL9JK+xFCM ztd&v?Tb}Ml(ZSfswRJ23HE^9&BJxqajiW>}Mkh?eFk{^-cPX%fLKuEx z#9Bg3(Zx;~Q9)Uoz^)u|!gAet;E_iMdVA*Qa%RivVFQ5c=GjSKa&P&;&%Xbaudg}h z{6#0NV%1;VZw+eJ_8p9&>`W9B^?ES88(Z|rJ8s`KIbn23mJYDQf8NT4&phz3vU4QUHCU_pb5@;No|rN- z$(HM~N2H1_i*%9Euz$#kb)4ZN1#F*)FB_|b>PcJJ(2 zxT;!j6!#2Ye%47_Hg4Tmn`-*%Wfxw^S_T%MY#SZ#&1Vd+eftl-uh@FpZEzHOzHe~r zSn-(~ZyLVnGWGlm7^q@FW!5lHUUSxMKl%~9Tc%rqE=`*_enf0gz*^;xHEL}7|j8;jz14BT}Fo_xU(B^5=tuPoP3=i4U#fSj&;i3U$^<&z>H^%&`B7}`wAqXD{5pWlVSax8H-~*rn8xJ?~t0wj7=7k*-UX{ zbo7Z0qn^ifWjg3o-9SmGi(Y<7rz_D;#(@sKuHtxQY;v?(FDfZ(WPB`}>nm65-96ot z#nCyr{M4=;Mk<}iXVX3N&89M0YV@A8YV6r{im5;I)Ot0U(KM^Fdqm0gzUmFHO?KzU zow^b@j9+bh_)$HXOcj#wbM*&4!T}oj{(z&Ghc|5b>Sz9`>NL`^PAXwm8|?Wtl%~>X zR%3SDt$96j=Rdk(M|NO{U69P*qzk!hSJ&3jV&Bl*MtM>NC&PQ}j9Fk~L#aHxc@vAu z8i{0Sbb$K_I2F({8U?x%#~y{_~M)qtM$wQZE~R@c;e8 z=SH91LcOh4in)bLN~5E%dfV?iTBqJ_ra0PWY$R~YyT*6eDg!B+F?X=s*83MN86Vjl zBva4s9_{Ms_G7URef%#2wiM1;>;xJsgdVu}?(4tyA1rJ{Ir$Ao>+V^$_MCy`ON)wU zrmVE8+s(>Dci%;au;ly2olbJWK)K@*!?CtpZ!-@1|2#X-1reqv&bL1x!yz$n+KPbE%1rSZhW+v~;to}P3jF;=Ro zeYv@d7w^$^w!kMD2I#b$B5SbhnBQg|l_gb&a_x8!*SOn(O$DOA!5BMQmi-K3-yq}d zLdSLjQx#V_0B_W+*cBpX=)x{bNRjrGVR^f!FjgwR>b=)AVoYzP19o3FoEv_4-Im+# z*>cNWH>UHMRVV$AfB5w7dd+p!Y_eOaF{0=rr?y{Z)4pah>aWoaNh-OJYV^dDAU-5~`=`r(Si{hray3-~5Tc{EIJtY3WNY zQmkar@i27nyzc?Kzi0i(uBmuuq~o9W`l~MbjFxzqy>$Mrx&s zQ+1oQjq4uUy>m;u)8zCkB^94)OtR!xEA&k&ac$oG*M0cU7hUo4%3%Kl`*nr}yG}p- zkN@RgioPGjV-2^);^&4lxo!K?O~>_JfAhwtEnQ8-6{j}kwi#nIrPV%582=5i735vyI7) zG0c<6ZH#bqFUB+DS_qaCFj=pX@R)dxaiVRgdl`{eYKDScfk*x^ti`KCA0(4m`?AX~ zeeD~To_pTOm%jXrD_`Ap(y4mYOU9C#r&YF(RCbPKW67RGL1}W;lh|#+1RCdoh*?0f zU6Hc^xDFz6CyWGUtb0+k09dRO3Kvw!f@173R9mmr6WIcb8auW6Pp`k>b#H$M$4P7{ z*1PgOzLt2;CqKyoN9I?X>_JZ?D~)E_v@-O%?A)^tKCu198&cVXH#HewdiG_nf1_(9 zRZX9qnmYT6D>vMGm&I_3mA>iPAH3q~cjdbKCfG@r&2Fz$m!5m>%f5DMGMk%hRwmeI zQ($vKwYO&=!344PA z9a@^d5TZh7vf_#cRQ`zpK>Fm^Hf&?SB$iF2Fr-CSS)2sOG7r?OI0_-$8bYU+R}5@% zRYly&8mUL_ePqQ27Y6YZnl!NNxBk_~y+@vW=&{FHjD7ayFHiRmj?}9)uW7ZKm12o$ zxO%l@b9YNz^O_#ZH?wLgTIhVjV3*CLGvyWB%Ff#~ZqW%;N@j7HU}a<46ic0Rx!dlz zW5Jnc_0L_P=7$2CV{=j?m5EBDqFQkddt$kd)oIT$G9#Ow?ao;L^7(&@cUXH|W{Tn9fPG6W4y}tCzm@9fdh_Cxe!m%9X2?K+{KjuO8Rh zRz^vr66`lw^z#PCcJASoPIv1ygrD)R=op{j9T=ELyyI=lJf)rl0Q0 z@!hX~^^!NgNsskpGFGM8 zNN4iJYF+P7FX_qME?wy*NvaO^T z9c{W_`|M|**swlsrgn}@aAu8XC$+A8a%iyB@i|Avi0if8V_mBG#gBecohX|uZltRg zk4;UK>J7Kzv{K!@yQZcZ)WTvFd$Ut>b6Vnon{WF+|Ifb_i{(3`*%{f*Knb_KvAa zk(1{J=T(}1DxJ6Ec2c+7jr!OB{mZ|;@#l)~luOlIuE75LjlaD4XFvV{y=c#KFgB|k zuxi@1xS?bV#dgfmb!JZ0bb`fXQ^TV_{N8tO{r(UA@i8WGZNo$Zhq4mw910c@VydEn zX$r-FGb)tMZm56(Du0+YJcVi%hs%rOqxoaL36&e3BDPQPY(*A|mj&TUbG*E;(EseC z>%aAtZv-5?5x2^Lq7}Ne#S5P-O^$1w3S-J~mG+I(6TbS@FW-IB&zPCYq1j zKDS`6vO0a0$%W14ymEDZX=+ZrVwd;a`O|B^^_9=o#dT+5 z2AM6-K6vf5Q@eJu>d|L+pv77xr?%~>(fe*&;+9X3cTVb{Y^8!+p3vRv8Y64K~Ra%=DSP z-7lLrpgTKP8(wZ~wD$PZl^x@?(W)54(zG>8=dbEd&y72M&g49|QCq)$`?{x?1Lt@v zM)*%#I&aNT*Sr`!*YdS;b8Pt5dvEP&8=erZh^3-Ze>#mIF zieqzY<>I~f-Tvh-@4W4vq~A`^FtDPOo%&4D$AV_OCrWicQTTj;^Sv`Z zQ&Nn(%*0K_lU6F#Y`J#AUUkyyOeVEwc-Q3k7~9r8r@@v$fE8oEZp&v?1IJD5@O(z0 z85U+bwAE-Z0>v;;!b+gDbPhUK+b-Q$b`2zwObN3iB7f@1`E1UJX%l;fhqr8RmDxI{ zC3W4aH^TATz2iV3%R;d@Hrx3FPS8u_ySn-ZGTmLAgUMmBm2$B(Ho^8F#c?xn&8D;2 zKwmc!w(hV$NK?&ZipABnR+GctRnzPq9Ln}|=exQ&NgaaKYJGTYv@|-tP;++g+{Mu| z@|0PQ-Jmzoa+*wra{`8-!wJk9$9uW-Wf{;eqG>gQJyz|yYj)I>o#ayBy4P}prrzBp z^gLs7n6yl@HJZ6(#?tkX(GewWC%OxqI!l8Ww`{-Cz@knva2x~;&fhjQGnZldjzgk* z7cXbztX3?K?b_XO+!h0D^+qO{U_lwzBhm*CS73+J24c~x!aj|e{Jc@zS&_gCJX3P^ z(MwO4MZydzX-XmuayZeskxVA)qvK{W!6GWf7sos^SVMoX;|J_#XTLo9C1ECba!COv zrFt$n^+Ao;WnfrbQBrf7zQ?Mdn8Jaq%#YHw<%A4Xpsww;fIXXOo~xSSR(L7qrlvMI z8=9FH!_-nV0T#o|*!jYq5VK(%?&|9-|7y2sR?ylpQ*Ux&4TmtYT$|Y=E+ybvQ<%l( z;HC(Atz<2ZgT&JHId$4>*O>C<3B)Nps@)QQp5S-IYX&AUMdK8&df~zXH)FIIIs#_DCXbzur zJ65~HdGK*Yw=~_=f*Q+G+F=jY&RY3Ghm9b$s>Mne8cIwF0Pl*Mo}>*Qj%Dnf2Hby8 zV^%6=MDztn;02<0#-mm*AnOC7B;zb9Vr{;dByKlq;7VZ`l<(*A`A*wsJeGAx2@YWr zJ7(KXt09heXtg-`l)B|M9Par525zIl;ZFiYZ2eQXBtf+`4u)oM*D5x`TF5#FRWmTI z8UNwx?Qlf|`A*l%u(v7TsjD43o#1RoD2XGZ+I3;t8mM~k+Yw76m`Py?1SjTjzH&RL zHX0oJhEd=Jev|WOnR?|2Sv~HsPl;v6qUTDZqMDp3#mVm+d(B*v#~w#T91{ofI0aYq zHAG%!f_Yyi)o6;sv(#B)Vp(ogY!ZsY1ZWfI&;^=h6q3mX+csM4BeYDN6<8dR5hxta z*{IhsbU8E=h&v8yQtWqZb5^`g*H<)W41}pMgCh?(dDlwl39D9e*cB`mdk2A;PMKP~ z*>oG_lFdTru=_2V&9FBvMMNJcawm!eW*xmI)eaXPN6dk4iF68Va$5ig9W(dAEQ+nO z4Od~tf&m^zP&76WbyzPS@1Q-JO|0d#%@zcs#R6cl>sMuABcn15-*Wm4ws(?^_Dov> zH>}NOlT}9?nwT)+Z1<;HQWepbVo9Rtql=2kRY|O51V8MRq7TaAFs6q{%tA47%yGC5 z*%D{7gBNUeU!xg8t+qMbkr`49RW;ze*;L^}v#9;OrrL#|Ss=(3&%`I_#KYqY#@1$qX=4XbM70)WXbZ)YCD}A&D{U&x}~y zW!&3KV#=HPw0s!4Kt7?AgnXi2FG4s`+^nP5t(C>$hN1mgo2*c+(LcHoXRj=MLS3A$ z7-xqmz(dh75be~f0Is9cw~eQgHqTa57?>;(HVjUKWK99)v!Zja0{aJBf#!Ocg3O7c zb=mI6I4)@6BpSo!1WQ!#b3cT51!dewqUoG>0aSn@Co2OghLdbYlf;b*Hm{t0)ATsZ zo*_MIIv1LW>j;F!%hfWl{(srK4>&uI@?QMAd&=(FuGOj+OYXe^#syP?!2|=QC6quC zE-kd&KXB=AQ!xoW!CXQJ7f8qr5E4Q&fk2Eg?gnFQ<1QE3R^8REww*oa>^cAMyt8|> zmUd+c9T`b}-_PpRyz|aH^UQOe-#pKhcc!(irM-iv0It3xu9R#f?K3{fGd5Bm2nVp) z1jJSzGv}4mEVwEl#QbU>@W9`ehIg&q*}P?o*=W4lGXRCW9qq#>9vPe9b#``@aaPm7 zWlJN)(Xrv?w$91oc#eCYt=X(6TgY+Si&M4%0O3+TW1AZT?M>D{XW-cQp%%%J5y>9z z#`B$|5-JitiS&U(UW*oG60Pp8QwfQ{(%XS6f&Gr&#eEC-gUppPK-zuy!#LPbZhS0^ zz}Hw#^|cKF%mZ4xz3N(X zg|_a=079eBJ|*VK>nhv2nLXAV1|EFtCw*BRGfmlCu5V$VR%6ltVUR%BELwZyY_sq= zM7V-iGIOPNcSaHi$^UyjV>I)t?`i!QNVb!I+SVEzX2)yk2I^ae>C)EL4wJy}#q1`> zcEIJBl|ab=kCZSSR_<3p!q`&4ttAI`tAdAk{s8y_wUN^>!p{7)hRv2alKf5f4U5VA zmix334*w$(?5G8i{KxPGlT&&p$92pd+u$HIrx~!{q07q_HD{or4XY&|30b;`y56;Z zG7GH9NIJAS*LV*n7X%M9Gm)5R+`9BFUCM%0<`;TJY68$_bp!0frQ~YZ*)n*`EjD)4 zcp=%=UKkH24|22f0gQa_aQIMB6^i9a&0VWCFQFNmV&uaxadN(i7+IQ_SuRTGSGU>{ zh(L0@iZ;fUk0NydZs_g?+-i7&a6BgehsD+D^&6MKfih!ci2-Xu9JD0*W}(kIb5SS3 zxN<1QmRXQQf#Q8va}Z5N-C|_InIhAv!pKuBDWRVf7idqHn-mb%G9?EuB_GFA-e8;) z3Ns(&g|$ysAchrDGG=-AJ1+B2J05mv$h-rLomsB4Drcrc{53#`s$I%*XK^_#sPC}Yk+ z+Y@b>hS0;MVz!am(Kh$`Ro5gjVex$OwtkPv>S;=R-eqc7}=ryf!b^|3dh!?o)O3chsott*!6PHFN{T$$`aa)q^L+>hUPT! zz*e&2N*%r0sSuC#w@AeVkwRF)m0U80;$}oWTi`)%Gcue`o&I+)NMOJBd-Rc9>HOh)IYU)5{s zdG_0wW!JX>{H_^_^#pbK1ny#zSo1`{b3%avL&dD z_lF6hu5EQSimzk*?0bx#m5c^5JpA6I105{t4*E%{L2IK{&*TdHps7>Mayz)F&Q(}@ z9MqbJ48}?B(5-$LtJ4n;FGXa?sIJNDIdkkpC8mtTF%#=JJ59aZIp0dT`vlW(LuQPH zx~N^^O2qn3XP=SDH0QG!NW%xX1ok_612BU21iKXUHHK%TBC4)}^|+b99{r#672(ar z2#0$T-uV{c#IRTx7?RQE;DUw;;iP5RaZf-M-kLdePqIu?i>$_bvcNFlKN#o4y@=Ki z81~$JNnE$>5xvvEN{|@~ie>M3J0me~>%fyMCWv>h>TiwvB|pJ*K>@up6sBvH{1=;& z*)eJOz?Z=OM=x~=+MS4^4&d+<)M~bZCMkAkWJoc;PSq7RGzSS{6U7bCoyJ43HsOjy zBB&CygztO8i{zfT10hw2n09)@Cek6sJ?thO5uTJGI=MpW0i_{jNb7EjHna$Vfml|E zpZ`*`K*S1-ghJ$16+-?*EOq#K-05vh5b82hRw!`>z;%(vw|3r%@(FYk@-yv4pc;9Y zU(89S^md?1V85d`(Uqu^K?X@EYGaIy;T__c(XD#o9j{`juATAGF)ER2G&>@JD!fX7 zGy~3Q<@SOf88e0?Nl3p-MH>BLF zt3E1*w|Z_2V52^H;}Mg_(&FWR9tZa7e9G%FR68M&Fc~OFVEJ^EPrWIekx8iEtW3#M zdON5ju;0-;4cG*>(c6qVH!Y=I}gdaV<1 zMR8cnB;f9<)^_aGFs)&`)f*e2?ww2>B0T36d3iWgB(y(NNS^3)%vWb8rrq%YWb&f9 z64olayj9Z9Rj6lLX61WWFd_1=nDk*EBw%S<%ncn@07ZN+s|ZlYnJ*uo{-s%`#~ot-#xNNi+<-7Sl#9BcqxeNXDOqf&0#7N+FfNK`H^? zqD0Q4J&Pu-A<#v(Vd%A>7jDB6cuVGeRWQ@o9Hc@|*-j|;IC?5PBPBNBju*?#D3jO;r<4MQ*6i<{WY)L9gh+TMNtkx~i=uLL4=>n1H4 zwg`}YuT;a(tKS=)fq1V2I6m$>qbY?{0tcf6_?~s!TZcvj^S)Q7VPEIZiPAYE_nqmK z!l5StBc0s0IKOD5Cw9baiDV`+Y;@pw%4F)%)CN1@5hT3jmqJ2kXD5L;{M{Z*=olP| z<3r+Oq-3(lXu}i4B@?5#XmUiNW^fqNB=ZV2gRcTEIxG@FX=2fPCHy*c<-)3KPqOdf z*ZBOajs9K_p-gR1gAq0!%9usSEH#pn?sIK`DH$&i9ggjTeukO@UF$r4H<(pxZ#$>55?1V{;FcSVB<)pcNdrFHPC9wQ4< z$i_ac2ErqPh{wc;$l|UViim+ds?t6r+C%iF{oZ|$ahkPp5&#uGEl`0m1Ly*z=-je+ z(4{EG5W>BRu5UEF5ETOvkREk!3m% zWL|N_6#?Z*zgIHM!8STTD8X50ou#kq5tzBh^4@U64bZENSh{rS6Q1w{2KI+P{Gq`r zk=l;o;o&De=}9M?Z~`Cx+H0@9>86`x85tP?VAzIa#1)|l*pE`!OVTNN_flYK^8F(L z1{QRm{p@Fd_`@HDx9B9Fa>^+%128LBuKer2{%fWWui7z08 zG+_vQ!yDf4%x9iAHZ}o%j5CnKvoxRh#3#-^`)sJT!S?_Bpa0YN^Y8!u@85gxy}$k2 zzx~C7|FbY|5%-}Ux?`O25G&aHE1R<*RSzt^jty66|bxa0ZH ze?ANViMJ6j8h1z#NmXE`r>C2#2r%pEp^a~Q+uN?Y?mD&?ojY#5wSUQy*S+p_`m|64 zLjU~F|9r&JM;&p*5la8&H@|uL<(E@McIfwh@Av-VFaCmZB8xuC5(74`erRA&7;%yR z`mg`WK5-Ckzx{S^1%C01U;MBC^}oD&+~XeizW2Rv$&w|7j&8O1(wDyU=}&#CXTbtC ztj_Sh|11qduPUiF84=l2p8T|p8#gkit5&VLW8Gb3=Pe||r+WVPfB!cb-FfGox*unp zamJNbUa6AHmM#0YfBQG$b5xO-zx%ttd-9W?{HcrohM2m0dkJyhtC~_ckR_n8hA5c4 z<(6B3gf*i!G=wPwPOmm^9$3A4HRWL&{{HX({)=DyBBKWhfLFI>ddv6Ue;;*XLfDKI zD|#=w=pq#EzWeTXzVn@(Jw4F}l#w}MHaX6j?(s?*cqSAGkGa5}nEZGS z+Y}??VTT_1iYezy0y^mv_)MuMA!)$ z_~IA8c*TkpV8+0((n!%#Y-kbCgkdhZLJ`prY}l|tEQq?Ypp23j0q28g6RC;zdK}*h2u|ufYplhaaOPfA9x?@Rql{<)Mcj8XVki^aK*( zMLmFl;?Ws+6JX+g@{^x@-~%6!<&&Sh81N*Zjhhqw1nGCY;~nRpf4;~+{^LKs`R1G1 zSa~%KM!E01?>-6C)QGfrL-QAr~%Kt}s?b;QF!3=c2 z=tVD*=B~T$LRKr|Rinh%FxyLBE+SA#ZJVZu6f{CQ1{xtP;&;FM-LHJ*E3bX+Yc*61 zwbn>e$EhHCqWR8uzN3=MmoFdK6#N;|(V%ji0N5{@qPJ1M_W?^sb007;JVIxvI0a~H z%YuYQdIiSseeZi=m1@BoFH}5tm6|cjRE5@2lEg%Z4lRu7)4~blfBMs(=FOgV+G&*S z$fJ*@b((-b{KG$#1x@3o`gaO~Ocac4fApgt<)VYJNPRtCH;5ug54noNgWEJK`~uAi z9;~c%_X-a{?x1O}aI4k$vp@SYl<{n2`|*!|{2%?%A7Knl)rm{8_x-nMnC*?M;G4C? zA0w<*>NznsJ~F(0)rysUy$gPJ-SuDk!WY-wb+`TeZ@KB_fBov$Hf`AW!2RnT>;9jY zeDAZL`8#1U{l+)Gq31v%x(A5F{=>dkHKlMMOMv#Xx;jt{AoNmn1{R*_1EpdBy^?0* z0}q&$6%XD#SdF`|5zv46mw)-(=RRld$3XSy=$Io~SFT*;gQ8;5DauWU9k#}r$h+@e z=haJJ`m#Uzqc=F&{KFsq$iMu{SDbu&&+T`%=G#s^<+PUO%%c9q_T=whxTsQ|TE1-2 z;zdh}6O-QB3%IkRYhdf3CDrZi9hpqFr>EB}5;|>d1!z>_$jGR#A^Q6lZQVNH?|IL= z@Z58sZ1x1((5rP&3qOI{(dvf$77eq#xFJVPk=72oC!c(>At)SY^BIQs`s=UPCYf*t z%dda^>qM_6Z-4vSqm5;;pY^O~Nn?7gS}_q=W)z8~ z<=;v})4u)fZ=?L~cfXsyDeFyddXus&`=GRjNZicslatYL$`3s7Ky>CAWEaKOG-z%JR?`ESd`4wabsgQKk3C*59zXGw7Doc5<89x6 zh=$o-++z!K+!ZiZ0f6Lz5WRTQLZB#;j0~)+iJ^aQiCB7~Iq}334es+a$l92@ig8^y z*nZTA@O`gpO5s43K-3?K0bt;VRa!weD33^8O2{$9%dMmLQ1P;ty^IlqRlor0jT^T` zOhO$*#rTB_+x5lZ9}(lG`fgB1MY(mBWJDk-g}RD28^U<5UcHhb6$6u8XaT-1%9IdG zvUoI}>TM(y8yY#VDuG4^GAS)K3}0CR!uQ^^X|oYBJm5+$TCH_x$Zyfy*|h+n`Y{=G zqRuhE&)PLd3=9lGWHy`dXA|@0FyQv^D>MOQq>?J5V*lsUf5&qAJ)m)HV)Um!{iy)} ztdrC$U-`Pu5Ly2@xrldD`! zhJvDZuyDZwE`TnsvZM!fk2vu*>Z&#c|B7RW@Nm;Dw?ZsZL)*6XEnJ9Ba7dHHyOFrv z1XQQ1qYbMqnb3qeE@Q`)9E3rV4MVR2aF02a`Sit~`Nx0!N8r@P437@-OyHADjj)rQ zWcbjUsuULbmiS??G?^Hd%P55Ej!x}fOTJ(hy?o;&c_z@4r(0u5HEBcym4HGJ~ z0%oECG!O-8-a;BRpbP>a4eeAeI@l4nR7kGwu3ia9hUQ2ULF+L9U}g_=>5-+Av~hg0 zxaFaT)*gK{BO#KOq62=E(}kdx0zu3uR2mDypb>|WW<3p%7Wlc(eIDONeXuxHEgN?7lXy#NxQAf@&5tY) zOCqYLN-jUrXtKmKz&LD!5U@(cnJb+TBx_10U-8V6dDO1Yxy z>R}0LM|*prt*ts$&9r1jM@F-m?AYj7J8PVrXBmFlS_^R?MIsXu<60?~A7F;@6umQ& zI5_{G553fePW#3E`c$6+2tX0I!hlC$pz#1n8Kk5Y?u;AgisY;Tuw=%E0iGq0T^5n9 zgz`iLjDk3Ka@&_6DZQe7DO&=h*Yk;$V!iA z*%@JuCmpU-gCFj#%Uk8jg1z1UdXF}GNm=z^7?%${xPdhBCq@UJ6x7En)gVw+b&(Fa zsEM}(=kn->$%g;)L_jEx#FbK{OM;}rDSCJRyC3Lp=R+@q(CmC`_=Yx7hm4r$b1`uu zRZKu6l@RdAJkTpK!V=IW>Dvq84m`}D$f#Yw7QI<{4V{XlQL#B_D->dYzRAd!22O81 z;s+-m@5k+{! zEleuW9VtX4R?5ieC}zABDP`nbGK#&MU+d9EZxjmV@MKOdAM zCMH^AZ(S-QUd=g28qwJ7O;y9T3EoBlCACe_yPF^VAb2$&dZ|Z1W@|3C<`gOF5*30a zW`G@|GZaHO!qO|4q-k*ihEh?LLR-)xYUZuMrZX+Ugg2}$fq+#fdEOPNy#3EZ$?}K31(^=#98_j{(dzkjs6nWSTvdz;9dv(92t{JIo&Uou!7MH?jh; zk*$U_E&E=*ltLa!Qwcm85=hbeXh=CNBbC7ZkU)yw z{UOzqW-5V4Ljoy!9}Ov|Wuy|=9}<{%Un}#<=97DS7T9Upyt0qx!=zJ~^fhNS$DfzB zmbcoaR3e+&(;%%VwHt z)qF1N{*))5`L)XJo~p# zKkbzJ?!9NKQmR#_@FSNJ*RDD2yr(}+p$|T=zPqb)s!~QoiMdR!JXy*#hdo{$(eUu9 zHP@UL92QxGH6E?Wz8`j_bInW@$DdKwuq)lXU*|XZ41cP2*y^?RbGAWP+|RjY9b~e@ z-L9%&`vM!3*k?egHvW12>tAmZK>J^R|NGyyQ-MuPY*rwF%?Io`V5b6G4M=S35c>m( zv}3(TTa)DTZMzarV;-{-m~RJJY8H0DXw5nt%Na#B*|4?6Pk;JTXA}ujHXCj@lGl!N zRNsOp=KzrU>H>~!8tu4a|9;l&1o$Xe2 z$DOzS!$17PdFTCBLMm)>>U3WxZ#idA(Oq4g0|SH7+cD?4&wXzERMt-JPkiDN?F7P- zF?f!uw)F=+_ZEooW3Qa#H{ID*c`4r4`QXsqS+H@k{hY&stpM1WYi2*^oioq8-|PPY z&$5zO2S<{Ft|JkC@{^ymWOKcJ3&$pkRU265@>BNwY-%1aPUc$Mob|bR>%fAA{mWOb z+OTP}_@fR4d%)H}?8 z8EDy<=BdfGY}u0k{LjneI^&F!oPGpm+5*&EC`Uu$EtR&h@w1=(Y)2(^cXxT^EN|3o zQBJgyLHM`7{hd4RxWnF(Ow>m{@)0OK_uO;OJMTOj?l_EFBZ3a_qoeJBf7zM3hWs)O zv%MN5oROU)h@cmXS6_X#vlVl#*)?m{IAF#>OvG)UXS=lHUIGYba=lfPYp=c59{DO< zDo!?}VkwbBLjv>JUUq`YZ66tOhK^kw9W`>?amP6d>#c8nD~y5SjW^y1HqH}bC#fQH z>cDq6blk4CepB#xo>rOlEae9apa(v9jcL7~s4EfFj*EIa}ip!2V zYUL%D{ODD$dKDj*p2yO)4i3tMe5UI^{^LI!62kmCSQIl3@}UW_?RPb^f^ui`xmUJ#-gVA<``hzX!X8|O0ujKl77>QEOzR(d@P#k>ohLu# zY5WIm&(qI)<_~}PLuaw#{&^R^;Plf^cW8yOo_OZjAN=44Nh<`L_|FG7C?teuUWJ7o_jVpr9mgr&x2d|Z^-YoGxUFLqPWB{ z^lF5(g;;cOIFrkrP#J9!aK7g~?{T)6^NPq_~wA#C?@@Ce@n18ttJ-Ac>ncO2a&|B7h`8GT_=IhEb90Q=j@2{bXHXOfGMwp}=q( zHJR#)6l0TTF)ps6QeN|#*PL_CIkM1sU&T^y4l{GLb?df|fBfUx0vJZbF`5q6qxSZh z)&0h#7KFc`IA*LNzf{A}tCB7#B?7EtQEkaz7+xnL*J@5Qyz5JEGKh@Oy$xmH;)wS1+rEYUWe$~eS5t>H9fPvQW`@l?J3?`w` zkk81-u>Jf82DZH9CBHL1KKjjXeuFQpYdko()uVGf&ph*tVsZS_pZ?T_4V$18Ch09Y z(P2gzeVtpqRy1ukzI=Hvl)mI8FBu+oX6V@KUiW%OFNEosTGOBW$)BEe*4d2qr#^M@ zHP>95&F22~U%zf6YG<;%<~6U?3CAQ)UO=nCJ3!ODAwN;W&>IoUoMKCb9qS^%0^{3B zYKG3pSHG9fKniTEz=uBcAtu+UZEUjeiWMt}uOYQci5&70fC1VC59})3LLh^|Y0eHqM(U9W_C!Bx+`RWzpUJOa8kFhwo zb+)QU9!{g$r1_Xk;}01Lung#>X&A-`O(qw!NDPOKi6Dy-N?w;;fEjT zVClQ>zGvIE;fEgD=tu^CU;EnE&pYp#h`<;{MM_&>Kw8(&RFl>L+8K87)RlT0UPa$Q zfjmljE(?uyez(#uy6B?MfBy5b&~R^QxB)7oV$Sb%G#LX11;)3$LI=Yvd_#VShK*XO zxvFALLj}O9p?YeoCK^0vlN)AG2P1M)8etb{Ebg}3Zu{ETzNXq52=&x?7#Y~okcy>5 z4s8k0t}scNZ8opLPOyHkarW6~JO0jP;^PND_yGfJqR6!6^5y!vqfVuzpN3i(M*8`) zpZ$yvNIx9`52Ds0u*1v@9}uLaWWYCX-Uzyerr-PC_x{#z{gx5zWy@Ad%%Zw~_`@GY zN8)$9;~hqMgekdsDKQezb3XxU7^CDsfqt-0wQ#&kLw=okZSUSv2R!K24uPn45$h_C z@1D+%`_|ok@7;I(?u%adJ1=}exj6BafBtfFb?VOBZvEvAKVQ3g)d$}D9837P)NjGkFMs*VFMs*V%na29Edd1SEd+w1MuB?_-j`l_DSOKUU%aUE z@}FGs=YRH>lg0ATw(Tv=*|yg9e6DS(QWG%Y1e{LND@|6GEnTs&Z;{sF^2;yhR3pwr zxr45zry;-Eyz{+ILUC5h(k08cZy&SPDw&$jmP=Fft*6is8nu^)PapddVzg~XA9w5< z-tY!}4AmeM-n9U~n#8qLq$8mBYdB5|%A;_6XhSNN5;-&^FyDMHU7}r+r7%51qjXcD zf?mXsZ zXog>HyJe#`4?f^<>2Ow9P{rVfH?u>{Emb`}E1+2BTYq&I>~S7V5m8|6g>rRqF~D45 zAo`dlpZVBcUG|epwF?j2dvE{JrQA{pU|UAaCqK~6=5w#+d*@&09MId++1ce_w4}2e z8oBCJZN6p5nkX5xf5adqKLIQ~TGVpF@yA{E<4Y}Uw)AxU`uk1ja!iU77O9SpjapxNXsBIUi-t(RYTjMw1bqY>7+AW@YR3)W1i)Ye$vxc*z>m9Yov-D} z;ncxNlfYXUXYt&_a@m^Zieq$dx#br2X>2?!v1Fg)B$MNeN{9ad06+jqL_t(9O+`t- zmcXq+XqYr!ZH&qYx49@r71|}H+lq6S9a%b$9{~^*W{|k_`npcB#cFR~UnJ=6oHPk$ zDz$U%_G+DT!_dpGzz`Fv(ZXVMY_vVqS~xdZpVk1j1VUU8E4u%LR3}8Uk8q9fVLVK)UbV^_FFDiN$9!LD|N~Cm0gGw3yACw{Q$Q zIy#}z!e)wEt%b2wU)Oj=Evd(2HGd4=2H;ZV{+jFCSjqdB+_k zy$y$;YvMFl#R=yz4i9Z}HdB{|s=3(+dZDe&mm-4$TOD+|%deG2Ja#28A9}%nH#!55U{EJNdq|rzZ?4O9tLR z)zjVEY6X5`LUjGOhd{Vx>t?+|?yOy7dtObrjT_rWW7}%dB#j!|wrv}YjmEa!*fyTV zHk&j!yYIQqKR6%qCD)_bd-lxS>s}yZ-_x-&gL$M8*cf07WQ!warCrWk@XI#RL%+GNBUc7sBl1(dU$8YfaxEuZDMuIJZKYu3@ zYA}ePHYAO{IxC^nn!enx{^;{LF6ONRSCoY=h9HJyTAXnNbD5zCEix0?Wn3M^HEhAG z=^_{I+u*{aVqOC!Bq&nU4v10xh{j}NUCgSkN>0l|NYa8o;f-s;)I`jOEY6P(;-74+ zzeJZIWmWwx#1<1I$#Jti|8`Hq_6+_6*FBZ9rCX+wRX}k$F)YP&rUBI*G=L)frT2?} zpoIs`ueq(_=GAU&Rd+M{r?G`rps1#Wo4gaf=yGL{Q#H`@26wbptL6A>`a`NvIY+t& zWEpo924f7GSE+u=btmWE;C0Ihk{mV;bF@bi zDX^~SP+1WIh8~f@x;l~7ebR0vIG}VCMwiNVu6alf==pr#4uR;-h}`5JC(Cu14p80< z)4O2aLd5y-1t`+*KU5(8N%pPiD$Pmgp9&Zc1u0waT*Zxvv83T^2q?xNb zCa3OHaxeaLKtUgy3KkJ2j8Se@Xy&f#&S)WYuVyW$rlBck$P~>Rrl;po*VmaSW&H1V zShPXKsNmtE_^K{Q8=hO6577I8YB5ccSZ}%pnw$o!nd+u)@uw}MSyDvk0%-!FY7D;` zbEVP7$^B8|HQtlO5OkI+o;LS}q}`4Mu~l0CQcv2VQ{Kq3(xeM~qYX4!83t?Ov4Ree z&O&i_-Qeer8KIWw0FcSC6h7AaNis}Hkr{Z$GmcmqKx&40iY+{DL%gd z3?Cg-el|3&@I9{h*x2OpAswNCj}*4Qb#EYYtxDJXX)@V;lPsDz(@I-b9XhNmx`j9w z8{*_6bgmxGPjC@=;l9a7vUw6E=sdWTEP0;U-=`R%qnDtdV3w5_Wqv}f^D!>LQVfEr za(BVq@Lof7J8SEVcPVqB%Ei`gRtOhR6SN}bo?&ugXi^bf0aek z&cr4Hlexis5&7@Pu^HR*VI7D)tF?Vhv~(^|33FTV(g7aiv) zGg$7EQ3ZB?y=pqkM3i<+adpwHYw`5~yTp|ljrxr#s|+%&8DW^ev|)Z8KYzk_lpr=j2TS6$<_I5>oe$k9*r)g$k%ChqLfbZxgnWkJ zEJy~TaYQE(;lX+EoXe_eN~YX*w&AKRyX{rB(l-hePPob7 zT2IN8g63cb@Q9@w7g75j-DA?L8R@Q9~H zRqfrC_A9GfgB&y5&>Y{1WrPkrlO#&fdG0A^H#wg8BA%`EHnLEHj=0kA66E)kfk%PI z;WJC^kpM}wL0*b?3y1x7(N1fv&8i1|< z8t3{Weh8?O?t<-mv-{e@B5*sc;3frE8nfu8c)2;;Ult#y8PhnpQu+d!12MZlz1t@{ zrAr&w!2x$lo5|7!K*#hU?jF4v$55?7o0yn)juZLLzA#^4>elsKl{|;?zwlc&lu%*6ASZa zR~3bv64@^}&;|M z@^2<0>BA1Q*vMccX~K5XyRwoM4y8;j)m?ZyySniV7d-)2J^-q}B|b82#xV~8w+_P* zsIWMhoM1Rgx2IElF55fz3RYC`wpB1e}lqM z{t!xPq1TA55JFfK_2&1t6^TdvZXr?T-DMu9N~K^;mm^8_UFu_evrLAM6Vp`3eGXYup@ zEzW&+vUtQlGz;TCr#$0m)X}z9H*Y246yei)6=Wur%aA5b>C2T73C9d;MY1I#Ai6k* z-7#`xSRGwkl@(&LD(5u6eReCdzS*)l;{G$K6|TjVWKWOj)ZQgYAWhzMR8>U#?=f1{ zhJ%X8DBGC~lV?TY&fVqTXhp(JrojWx4fnZ^Ep1JdoyLe^z5D#V@V{59!(H3zimc4d zf!m!^_O5eMZ?zj!#`>y+Z5X5slcMLuk7pPC8yCRHM1nicgUH-U+d)R$^OfhjD^T~> z`p)OIlTW~H`2DdwD@h*z-xx$7KZCW(*3je2tHNuj-S6Y zr1P3K@ZVcO>df6O!@yE-P^SRsI<&^O~m~0ZaP6SWTi~} zcSW<&12}7H)X|#Giz$^Pat>v?_&4~?!~XZp05DGLh4O=h+!TPB^<+rDUgao(OQS|*1D>eZ$=0STc?^e& zzIpjE?!tJlIaO9$(!AZuIo1sP1WL>4S(*pbs+pN^6RD$ZSfFBOHzuD-*X~20BqwBG z!$^7tN*sHpmrwm_WZ3_xm66@u%*WR$IYMO=bQAfF6xw4N83u%y*3=+hCR`0^i5ZSI*K@S4DUm66gQf@IyuW~NmkqXNM*<= zOSW9)hPJKOBtv)I?F#izAXZS@^(RL-umQaZaMJ~UcKZSR<1n36xBJJ(OtqG8C_&V@ z9=kh|#B)na2aw@YS=_&UpSGU1-1B^?;{g;7AW{L5evyt>mzRF0%6V2{FF;k3~w#%s4b70WsPE8TMe3MEiJ z2SQ=C*#Gt$A##tvsp}GV^w0q4v0+FARcm?xZ8LLV#SS0@K5p0xAEq0Vi)N=|w3ht^ zu-KHtf9$=}rOG+=@xH!*b|(aNg`SW1zxLe^%l~q^*Ysa^afG({9u}3cqHY0}AN|^r z?lAj|$2t&er>d^c%Esmd@FAJCTNpObg;p5Sr~@B?dKzJ3r2@+maAk$H&9(3CH|d57 zh7^m&Vg83x$2ggrKfOGhU1_*)bU%SA83zARITN{ZBWd6>q{hxeFys{q>1*2hwS6k0 z|Ej9%a&ibn7*@r_#!@0liupY!kJ`&Z06n1MqGgvjqa2^xa)5l|)Ki@TW6dZpaI=4T z2?7%G@=q@=v>Oi{`c~Tdq!g8{*w!54-xeP}q74BguH?OX&b1pYorXqZFQ~I&=pN!s(h#7I3eCFLXpCgGDD_gVubJ+k)A9?8cl1uHt$ z=HnAHRmZ}2{jHT1|25OJ@(6WlOUw2!!dpgVAP8@0=3zy@HXpOeS^6(&tPQRgElq6X zi{r~l>B6D5ooSNaSN$1)Tw4;%St3#e4(-I6-ZzXWu{gJA(IiOh|9Zo4#Mrv|WkQ#L z+pfq57y%@s2?s7=0y-pEkW^qjOAIUX`3C@Ky7c8gAtsR5g_PiWfXT~7Esi6iporP~ z`wLSH50ldbszaa%-lMAIdjfv`bTB-R{a4G8vjAwf#dZTT z+>Kbq|!x$=v=E1M;jsXhIr%%0^px=AlCW*W+d11Fazi`#G|8$ z?`!{lS^h?yGgt_In`PglSyZ5k_fZt}Eln$8=Wm3H=$8SF|5OngGoOgda4z(Fe0V<( zz{olM?y@|q?(OjPscQ3C8<&-dBeAc{xfm#vByl~jNC|IlZc#7IH1a9dv7N%vYIwF% zo=_YiZ1p&C+IgJ5tdAVKRbd1?BF4Vgo*$o{=y$oSMX4g-Fg~B8sBb{8$ed^+5Oshc zH^R8>i5O12Tip+}5HfTsg}2Pd1h~wd+#ni=e1uk7^4$$B8|F8Y9rasWhQ_FKqUdjM zY0^rll9kIj>j)0DG<|CA7bp)7 zjc1?bbq9qxt3E^v(FTuGh_BlhOxgJsCF~epoHy{SCNx)yk|eBcwr4YbkL=gR9nBU` zTb=J|+#p%IZb9D-nD7*y2H*2x{ml?_h(V>-Xx)-N^Dm#MrT)4?alrePXEe-zpcuh% zR3U}wrG_Ndo5he78xpQkzD%K7zLzskxqYJzgCKHSHM~g>LL(C+Gp5Re^p4|>Pk=7@ zJxQyJBcgB>*C>sksfE+9`xAobZNpsfO!FlQJ%6uS3QR;(Q)&3|E8_KNsMty{iV#hw z5X;}g6pmF?=Greq3hvor`M54RMkY88IKwGXW#v8G`E81Kf2NwWu zS2wI)37IW&0IGA2NTBgZ05;Y@$;p8b5!~SW9Cg{S5&=fi>Gpe_4JU%#~-A@)6=ACg(wYOd+*oFHB)ETkB|%Afq**Ov;!!ycYi0#35i z{A=HlM8D4jefbG_+suZN=3Upp3e(YtvfWHHq0T@=v>_zKy1upq+SrOFDR#%?15Sn* z=WFJ7^K9BQx}XqoVRj(t6To-F(VPDMY}o#nscf*UuoR5>>r z4Z%wUgtA^rySg}*Q%r1EC#z^4#c~Dn?e+E75qJg5=CSC{(QirNX(A4CLRHm|T0EsM zQw!|CEu!I)qiq@Q%}BKTeR**arxerF8=}S3oU~9lt^u==3LwFZQQYIjj>YIAqsf37 z-LM!bQb$#Cg4>37jE9iuGMVE~Q78>zvZXW@Vub1XDXw&K~`M zL=nh`pG!9;PGkmXi-0#amXo1KHxGiko@a4xyQnwC&Tjo@^tGPSa~_BLIpU7 zv&`B}j(5^TkpTho7dhd8C{TJltEFLF_%74&NpQ&S{@~WxC7Xu;A6LJDsdGmC&73~S zr4YQjM$3G-D9Up-wPai+eR_#@qr-Iq*aJP6HO(E3tqq+gPv?tefUT@18Q{vI-wJ-b z-tqIT1JK@}?)kaNNQ|qq1%1)Hh?iHV^Z_}}ueG?a{09i084L{Lfcsc`A4rQOh*3_8!oocT zd~jy;>w?n=G?*43QCFsBsZzb?I&@}*)QXg^laFVH5Hj)n*rpedjrh5qF)E=8$(Nw# z8Sa%JNfNm(I_G)^&NX07qSPrEh?d{be7IHY{3!Nb%6+&itZ>Y zpT@l_h?g9@XQ9O-GJ-E-C`+g1Y@V3kWyqAgIhP>&JwL{YjeBN=fNz7vim9!uBI13s0YCka z%5aSrb->OOfb_TYUDZ-O1$-V4%GoOB$iKSrOpQe&sJH*yoj|Zo4cax09Wm1`xfrO@ z`3Z-cm!7Yu$UGGd-^8mxTWhcR)1)cNPLb%6q{C&a@V-Bp_LL+zKweV)Xn9Ke1&0|Q z4E`Of)y$z@^=rIILzr?>D!f>K8Xak`LSA=4uo{2VEsr5>eFJ`+5sSJw0RbAJ z!KvDlODX0)3XCp)DAMVK6~uBl-L(6QCK>FVlW+Sp1#DFWq2&fK3SxH%EIuk9vg5Ym zYITt2)tOIW)^z(RbwEW04jN)VDG%t<+J0--`)P2Wj;eFava;$tgj>An=WPL1AJuRk zxFPM}ixu8O#)^d&C$)^d4JCsfffjeGZefx?3!>|;`Y9qJGgO(GQY8hUG=skG~&po!W-1`SuVH>%2z1odP@)^C2#*OYFT{e_=EhlzV} zx*Cyi3he*8nb-~Jcy>d06qaGjm=?!vQGp>hH;RnOu9%l)J9y!dwup^`Kz4OxEIcuT za>Za=To7Me3}O-kyuluB3p4_D0p!Fyt~vswUW)W!7Sw50a|~++4FtiXFrq?hYb8!Z zg?Djc@lLEB&((iYj%OVq0iMGbn z7DT*|Yv;e|cs1UO(BCsM1^8RF!ztK0JLw~VR?*E>uxrs$gvwC}KuS%_l3H5M*Ac~X>t z_o$#cMGZxc?I!C~dlt&!M~&5SwUxsY$FRx~kuon zB-2NO-)Ld^%Rt5^_CQdv4u(Hn*uOL|kg|N8SVyS;osuO^Dz z;{Df~np&(B$BIe@!)xy1LKm*pKp0|jIJJsOHPisNv}BS%vJK{*O8pu=NCsVEF-D7$ z9BGQVJc#D3(w{K1wTtYnuXcIc?dZe0t+qaQ4HRykHtguz|z%8`0?w-b4YZksI`v@3@npQ6%}KO_I*iF zua>HGoO}tH=o$ffDJYmoPfk8cT~tPb!gtBu5@fn49XpV&_T%$tyI6LELSKx8I4(i| zXaPl_WGMUyhsWJ9W5!<|vSnDg+r&P46G!qiGy3+@R!A9$Nb{G`T%bdSAQyQM!ChhG ziE)<59EUVV*AQ7DdvuHxy6~Kw>=Yc^YE(LN_+_XG{1R+gGe9Bneo% zB<9!`s=3Eo?}vKhXt3_v-Oo=rn}Bm4f>~JQacZN=nJLivi8b7L@>#XGBi?IXZiAu);F{KJM4J}38lr*z1d2Ee4imx&^uVswws0NCvN8Ls$B8IG_F0F%7)-`QyO z%b|$B{UbAe`*QLZKp-msFp0RVS9V+-U^yoKqeUhvya)lXHQhhh`jk28Hs$~MD+YR| z^J+=;$%r-7@2akn26oE$bvuVArF)gv+leo__U_z&@BTFc9X<5KJU#fls`=II@38E5 zcg%jT71M;68tW-=lSSId72n$U3YjEGIgnrw-T*|rxkBn{G;23^Gw%u!JkEj{iT-$zay7D8ir}8t(bAN1VHb#QO?$W=a|ZnHeiA~Kc$&Yp^M!x zh7+G{XmV7i^X`mxykHIFFr*6))E@i=WJg1Sh8w9)Mh(j-Sx||E{c~km?WDGu@yxr5Aea%`}q0CTj zr@0eVxQ8N5L0L}J)E7cox`3+87&W`Q&co4PAlb$2)d5HAuD(vDKO>^)S5Cs9@%x7g z7Jd~3vYx~J&+bFdY5zar{(m9?wjuCWP@O`cBP-365@0NNh!Nz7!<09MqD(r*AjRWu z%)LMi%G}}TsaP@pF?`}}qz8~vgr2{d-x1_|J2}=m3Sbil)VeOx2hNGn-p{{K{C;>_ zo;g!L@pkj>T&oOSASB@YJ1-SzM0cY z%NkL*3tPILtNR0;3fgcKQ8ay_l&UfXM!VBDS-Vq&VyjQtQEE|8U8dd*&bg-kTW5Ml z_uEzvMElz*UWUwew>7)b*o#~_@pfH;VhfkVL_Kt_j4fba76fwA?yJ^Z+|-d9R7v7NTyQh@{7 zKc0_`dEK@fv+;iS6rN}!xO``qJ)QPDUob;*3S&#mzC1Qtuk>F7wQ>ahb6EsfNHC#` zFQD#Jw1!od+*Ut;syy&05|ka6Vnf^4!nWL;8wwk2i!IbyRiFrs64j9*i^lzOou2)x zrBzry^L;k?Uky_-0G>3N86Ej|pIX?mY-wjhyTzvGvcuZj+xyltd{geV_v2{>xr>B= zz;#v+;4OcR?(q9~(g%Q;?>@&rKS@foxVy6Q8$&{BQ~u~FE%gS@7w1#f`hPQlQvVf+ zfS3+*J(WqfADdOC%2HDD?}Xl?(qImg)BE#`z)r~CS3RGbQfx+}VefzHTq_R`*z${w zaBkc62q7`}M`RL0eEiqvnOb*o∩#ox}k&fs^W6cG`+T`mcQcHI!zgJ_C1y#VGY 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%0D~~96UhaY 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 literal 0 HcmV?d00001 diff --git a/docs/portfolio/inferedge_1page_architecture.md b/docs/portfolio/inferedge_1page_architecture.md index ffd3a14..ad1e17e 100644 --- a/docs/portfolio/inferedge_1page_architecture.md +++ b/docs/portfolio/inferedge_1page_architecture.md @@ -40,6 +40,7 @@ ONNX model - Lab `worker_request` / `worker_response` boundary - Lab -> Runtime dev-only minimal execution smoke using `yolov8n.onnx` (ONNX Runtime CPU, success, mean about 47.97 ms, p95 about 51.80 ms, about 20.85 FPS) - Jetson Orin Nano TensorRT Runtime smoke using Forge manifest + TensorRT engine artifact (success, manifest applied, mean about 14.00 ms, p99 about 15.50 ms, about 71.44 FPS) +- Local Studio demo evidence replay at `/studio` using bundled ONNX Runtime CPU and TensorRT Jetson result fixtures: 45.4299 ms vs 9.9375 ms mean latency, 49.2128 ms vs 15.5231 ms p99, 22.0119 vs 100.6293 FPS, and a 4.57x TensorRT speedup for the demo pair - Runtime source-model identity polish for manifest-backed TensorRT engine results (`model.engine` can still keep `compare_model_name=yolov8n` and `compare_key=yolov8n__b1__h640w640__fp32`) - Runtime `worker_request` validation and `worker_response` dry-run export - Forge worker/runtime summary @@ -53,7 +54,7 @@ ONNX model - full automated Forge/Runtime execution from a production Lab worker - database, Redis, or queue - file upload -- frontend +- production frontend beyond the local Studio workflow UI - production authentication, billing, and deployment controls ## Interview Explanation diff --git a/docs/portfolio/inferedge_pipeline_status.md b/docs/portfolio/inferedge_pipeline_status.md index 8669498..077eb6a 100644 --- a/docs/portfolio/inferedge_pipeline_status.md +++ b/docs/portfolio/inferedge_pipeline_status.md @@ -61,7 +61,7 @@ Current role: - runs compare, compare-latest, report, and deployment decision flows - exposes `/api/compare` with the SaaS API response contract - exposes in-memory `/api/analyze` and `/api/jobs/{job_id}` workflow stubs -- exposes a local-first `/studio` skeleton that presents the CLI/API/job/deployment decision workflow in the browser +- exposes a local-first `/studio` workflow UI for Run, Import, Compare View, Jetson command helper, demo evidence replay, and deployment decision inspection - maps analyze jobs to worker requests and worker responses back to job results - preserves optional AIGuard evidence while keeping Lab as the final decision owner @@ -95,7 +95,7 @@ The current cross-repository loop is covered by documentation, fixtures, and smo - Forge summary-origin Lab worker request validation in Runtime - AIGuard worker provenance mismatch diagnosis - Lab deployment decision/report evidence smoke for AIGuard worker provenance diagnosis -- Local Studio skeleton for viewing the Forge -> Runtime -> Lab -> optional AIGuard workflow, smoke evidence, metrics placeholders, and deployment decision ownership from a local browser +- Local Studio local-first workflow UI for viewing Forge -> Runtime -> Lab -> optional AIGuard state, creating in-memory analyze jobs, importing Runtime result JSON, replaying bundled demo evidence, comparing backends, and inspecting Lab-owned deployment decision context This means the current product boundary is testable without running the production worker infrastructure. @@ -124,7 +124,7 @@ Demo readiness: `scripts/demo_pipeline_full.sh` now provides a guided end-to-end - Manual Jetson TensorRT Runtime smoke using Forge manifest and TensorRT engine artifact - Runtime compare-key identity polish for manifest-backed engine artifacts - Guided end-to-end demo entrypoint for portfolio and interview walkthroughs -- Local Studio skeleton at `/studio` for a local-first browser view of the workflow foundation +- Local Studio at `/studio` for a local-first browser view of Run / Import / Demo Evidence / Compare / Decision / Jetson Helper workflows - Cross-repo fixture compatibility across Forge, Runtime, Lab, and AIGuard - Rule/evidence based provenance mismatch diagnosis @@ -136,7 +136,7 @@ Demo readiness: `scripts/demo_pipeline_full.sh` now provides a guided end-to-end - database persistence - Redis, Celery, or another queue - file upload handling -- production frontend beyond the local Studio skeleton +- production frontend beyond the local Studio workflow UI - production authentication, billing, and deployment controls These gaps are intentional. The current project fixes the contracts first, then leaves infrastructure choices for later. diff --git a/docs/portfolio/inferedge_portfolio_submission.md b/docs/portfolio/inferedge_portfolio_submission.md index ef0ca7e..3d29176 100644 --- a/docs/portfolio/inferedge_portfolio_submission.md +++ b/docs/portfolio/inferedge_portfolio_submission.md @@ -14,7 +14,7 @@ InferEdge is not a benchmarking tool, but an end-to-end validation pipeline that - InferEdge 전체 흐름은 Forge build provenance -> Runtime real execution -> Lab compare/report/API/job/deployment_decision -> optional AIGuard diagnosis evidence로 구성된다. - Lab은 InferEdgeForge provenance metadata, InferEdge-Runtime C++ execution output, optional InferEdgeAIGuard diagnostic evidence를 하나의 검증 bundle로 연결한다. - `yolov8n.onnx` manual smoke에서 Lab -> C++ Runtime CLI -> ONNX Runtime CPU execution -> Lab job result ingestion 경로가 dev-only minimal Runtime execution path로 검증되었다. -- 현재 상태는 portfolio-grade pipeline foundation이며, production worker daemon, persistent queue/database, file upload, frontend, auth/billing은 future work로 명확히 분리한다. +- 현재 상태는 portfolio-grade pipeline foundation이며, production worker daemon, persistent queue/database, file upload, production frontend beyond Local Studio, auth/billing은 future work로 명확히 분리한다. Pipeline: @@ -93,13 +93,14 @@ Rule + evidence diagnosis layer. Forge summary, Runtime worker_response, Lab res - Runtime worker_response compatibility ingest in Lab - AIGuard worker provenance mismatch diagnosis - AIGuard guard_analysis preservation in Lab deployment decision/report smoke +- Local Studio browser workflow for Run, Import, Jetson command helper, demo evidence replay, Compare View, and Lab-owned Deployment Decision inspection - 4개 repository README pipeline summary sync ## 6. Validation Evidence Recent validation evidence: -- InferEdgeLab: `poetry run python3 -m pytest -q` -> 245 passed +- InferEdgeLab: `poetry run python3 -m pytest -q` -> 262 passed - InferEdgeForge: `python -m pytest -q` -> 89 passed - InferEdgeRuntime: `python3 tests/test_lab_worker_adapter_contract.py` -> 12 tests passed - InferEdgeRuntime: `scripts/smoke_default.sh` -> success @@ -110,6 +111,7 @@ Recent validation evidence: - Jetson TensorRT Runtime smoke: on Jetson Orin Nano (`Linux 5.15.148-tegra`, `aarch64`), the C++ Runtime CLI in `~/InferEdge-Runtime` executed Forge manifest `/home/risenano01/InferEdgeForge/builds/yolov8n__jetson__tensorrt__jetson_fp16/manifest.json` and TensorRT engine artifact `/home/risenano01/InferEdgeForge/builds/yolov8n__jetson__tensorrt__jetson_fp16/model.engine`. The output `results/jetson/yolov8n_jetson_tensorrt_manifest_smoke.json` reported `success: true`, `engine_backend: tensorrt`, `device_name: jetson`, `manifest_applied: true`, input shape `[1, 3, 640, 640]`, output shape `[1, 84, 8400]`, mean latency about 14.00 ms, p99 about 15.50 ms, and about 71.44 FPS. - Runtime compare-key identity polish: InferEdgeRuntime now preserves Forge manifest source model identity for compare naming. If `manifest.source_model.path` is `models/onnx/yolov8n.onnx` and the explicit TensorRT artifact path is `model.engine`, Runtime can keep `compare_model_name=yolov8n` and `compare_key=yolov8n__b1__h640w640__fp32`. - Guided demo entrypoint: `scripts/demo_pipeline_full.sh` summarizes the full Forge -> Runtime -> Lab -> optional AIGuard flow and can print the Jetson TensorRT Runtime command without claiming production worker or SaaS readiness. +- Local Studio demo evidence: `/studio` can load bundled ONNX Runtime CPU and TensorRT Jetson Runtime result fixtures from `examples/studio_demo`, keep the demo pair selectable in Recent jobs while the local server process is alive, and show TensorRT Jetson vs ONNX Runtime CPU comparison in the browser. The fixture-backed evidence records ONNX Runtime CPU at mean 45.4299 ms / p99 49.2128 ms / 22.0119 FPS and TensorRT Jetson at mean 9.9375 ms / p99 15.5231 ms / 100.6293 FPS, a 4.57x TensorRT speedup for this demo pair. The direct Runtime execution result includes `deployment_decision`. Its `unknown` value is expected before Lab compare/report because the worker response has not yet been compared by Lab. @@ -136,6 +138,7 @@ Forge summary - **SaaS-ready API + async job workflow:** Lab has API response contracts, in-memory async job stubs, and worker request/response mapping without prematurely adding DB/queue infrastructure. - **Deterministic rule-based diagnosis:** AIGuard uses rule + evidence detectors instead of vague LLM judgement. - **Deployment decision ownership:** Lab keeps final deploy/review/blocked ownership while preserving optional guard evidence. +- **Local-first Studio demo:** The browser UI can replay real validation evidence locally without adding DB, queue, upload, auth, billing, or production SaaS infrastructure. ## 8. Current Limitations and Next Steps @@ -147,7 +150,7 @@ Current planned production work: - full automated Forge/Runtime execution from a production Lab worker - database, Redis, or queue - file upload flow -- SaaS frontend +- production frontend beyond Local Studio - production authentication, billing, and deployment controls Next practical step: @@ -164,5 +167,5 @@ Next practical step: - "macOS ONNX Runtime CPU smoke와 Jetson Orin Nano TensorRT smoke를 모두 확보했고, Jetson에서는 Forge manifest + TensorRT `model.engine` + C++ Runtime CLI 실행으로 mean 약 14.00 ms, p99 약 15.50 ms, FPS 약 71.44 evidence를 확보했습니다." - "Runtime source identity polish 이후에는 manifest-backed TensorRT engine artifact도 `compare_model_name=yolov8n`, `compare_key=yolov8n__b1__h640w640__fp32`를 유지할 수 있습니다." - "AIGuard는 LLM 추측이 아니라 artifact hash, source hash, precision, shape 같은 evidence를 비교하는 deterministic detector 구조입니다." -- "아직 production worker, DB/Redis/queue, frontend, auth/billing은 계획 단계로 명확히 구분했고, 먼저 contract와 smoke coverage를 안정화했습니다." +- "아직 production worker, DB/Redis/queue, production frontend, auth/billing은 계획 단계로 명확히 구분했고, 먼저 contract와 smoke coverage를 안정화했습니다." - "이 프로젝트는 AI inference engineer 포트폴리오 관점에서 C++ runtime, Python orchestration, schema contract, provenance validation, SaaS API boundary를 하나의 제품형 pipeline으로 연결한 사례입니다." diff --git a/docs/portfolio/inferedge_resume_interview_summary.md b/docs/portfolio/inferedge_resume_interview_summary.md index cc9069d..1e6a954 100644 --- a/docs/portfolio/inferedge_resume_interview_summary.md +++ b/docs/portfolio/inferedge_resume_interview_summary.md @@ -5,6 +5,7 @@ - Built InferEdge, an end-to-end Edge AI inference validation pipeline that connects Forge build provenance, C++ Runtime execution, Lab comparison/report/API/job workflows, optional AIGuard diagnosis evidence, and Lab-owned deployment decisions. - Validated real execution paths on both macOS and edge hardware: `yolov8n.onnx` through Lab -> C++ Runtime -> ONNX Runtime CPU -> Lab job result ingestion, and Jetson Orin Nano TensorRT execution through Forge manifest + `model.engine` + C++ Runtime CLI. - Documented Jetson TensorRT smoke evidence with mean latency about 14.00 ms, p99 about 15.50 ms, and about 71.44 FPS on a Forge-generated TensorRT engine artifact. +- Added Local Studio as a local-first browser workflow UI that can replay bundled ONNX Runtime CPU and TensorRT Jetson demo evidence, showing 45.4299 ms vs 9.9375 ms mean latency and a 4.57x TensorRT speedup without claiming production SaaS readiness. - Polished Runtime provenance readiness so manifest-backed TensorRT artifacts preserve source identity: `model.engine` can keep `compare_model_name=yolov8n` and `compare_key=yolov8n__b1__h640w640__fp32`. ## Role-Specific Resume Versions @@ -19,7 +20,7 @@ Built a multi-repository edge inference validation workflow that connects model ### Backend / AI Platform -Built the Lab-side orchestration and contract foundation for an edge AI validation platform. InferEdgeLab exposes compare/API/job/deployment-decision boundaries, maps analyze jobs to worker requests, ingests worker responses, preserves optional AIGuard evidence, and keeps production infrastructure concerns separate from the contract layer. Current scope is SaaS/API/job contract foundation plus dev-only Runtime execution smoke; production worker daemon, persistent queue/database, file upload, frontend, auth, and billing remain future work. +Built the Lab-side orchestration and contract foundation for an edge AI validation platform. InferEdgeLab exposes compare/API/job/deployment-decision boundaries, maps analyze jobs to worker requests, ingests worker responses, preserves optional AIGuard evidence, and provides a local-first Studio UI for browser-based workflow inspection. Current scope is SaaS/API/job contract foundation plus dev-only Runtime execution smoke and local Studio demo evidence; production worker daemon, persistent queue/database, file upload, production frontend, auth, and billing remain future work. ## Interview: First 30 Seconds