From 10ba83348d706e526ff10410aa050a6227573298 Mon Sep 17 00:00:00 2001 From: fengting124 Date: Thu, 9 Jul 2026 08:51:42 +0800 Subject: [PATCH] feat: polish full-stack evaluation demo flow --- README.md | 1 + docs/README.md | 4 + docs/fullstack-evaluation-demo.md | 133 ++++++++++++++++++ docs/project-worklog.md | 59 ++++++-- ...6-07-09-fullstack-evaluation-demo-smoke.md | 99 +++++++++++++ package.json | 1 + src/api/backend.ts | 6 +- src/api/errorMessage.test.ts | 34 +++++ src/api/errorMessage.ts | 29 ++++ tsconfig.app.json | 3 +- 10 files changed, 356 insertions(+), 13 deletions(-) create mode 100644 docs/fullstack-evaluation-demo.md create mode 100644 docs/superpowers/plans/2026-07-09-fullstack-evaluation-demo-smoke.md create mode 100644 src/api/errorMessage.test.ts create mode 100644 src/api/errorMessage.ts diff --git a/README.md b/README.md index bf485f8..b2ba835 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,7 @@ cd model-services\nonescape-mini ``` Detailed Windows, WSL, and SSH-server setup notes are in `docs/local-development.md`. +For the evaluation workflow demo, follow `docs/fullstack-evaluation-demo.md`. ## Documentation diff --git a/docs/README.md b/docs/README.md index bbb0d29..3915383 100644 --- a/docs/README.md +++ b/docs/README.md @@ -22,6 +22,8 @@ Use these when setting up or operating the project. server setup guidance. - [Smoke Test Workflow](smoke-test-workflow.md): end-to-end verification after services are running. +- [Full-Stack Evaluation Demo](fullstack-evaluation-demo.md): admin UI to Java + backend evaluation workflow without model weights. ## Architecture And Contracts @@ -49,3 +51,5 @@ These documents are useful for understanding how the project evolved. 4. If discussing architecture, read [Model Integration Framework](model-integration-framework.md) and [Async Detection Jobs](async-detection-jobs.md). +5. If demonstrating the project, run through + [Full-Stack Evaluation Demo](fullstack-evaluation-demo.md). diff --git a/docs/fullstack-evaluation-demo.md b/docs/fullstack-evaluation-demo.md new file mode 100644 index 0000000..1542076 --- /dev/null +++ b/docs/fullstack-evaluation-demo.md @@ -0,0 +1,133 @@ +# Full-Stack Evaluation Demo + +This how-to shows how to demonstrate the evaluation workflow from the admin UI +through the Java backend and persistence layer. It uses the deterministic +evaluation model client, so it does not require model weights or a GPU. + +## Audience + +Use this document when preparing an interview demo, local verification, or a PR +review that needs to exercise evaluation creation and execution. + +## Current Status + +The evaluation workflow supports: + +- Creating an evaluation run from a CSV manifest. +- Persisting run state and sample rows in the Java backend. +- Executing the run through the evaluation model-client boundary. +- Recording aggregate metrics: accuracy, precision, recall, and F1. +- Retrying failed runs through the same service boundary. +- Inspecting runs and wrong samples in `/admin/evaluations`. + +The workflow does not download or require real model weights yet. GPU-backed +model inference remains deferred until the project runs on a prepared server. + +## Prerequisites + +- Node.js 24 or newer. +- Java and Maven for the Spring Boot backend. +- PostgreSQL and Redis for the default backend profile. +- Docker Compose if you want to run the full infrastructure from + `infra/docker-compose.yml`. + +If Docker is not ready on the local machine, use `mvn -B test` to verify the +evaluation service with the H2-backed test profile. Full interactive UI +execution still needs a running backend. + +## Demo Steps + +1. Start the backend infrastructure. + +```powershell +docker compose -f infra/docker-compose.yml up --build +``` + +2. Start the frontend in another terminal. + +```powershell +npm install +npm run dev +``` + +3. Open the admin evaluation page. + +```text +http://localhost:5173/admin/evaluations +``` + +4. Use the default manifest or paste a small manifest: + +```csv +filename,groundTruthLabel +real_001.jpg,AUTHENTIC +fake_001.jpg,SYNTHETIC +``` + +5. Click `Create Evaluation`. + +6. Select the created run and click `Run`. + +7. Confirm the page displays: + +- `COMPLETED` status. +- Completed sample count. +- Accuracy, precision, recall, and F1. +- Wrong or failed samples when predictions do not match labels. + +8. Click `Retry` only when a run failed and should be executed again. + +## Backend-Only Verification + +When Docker is not available, verify the Java evaluation path with tests: + +```powershell +cd backend-java +mvn -B test +``` + +Key tests: + +- `EvaluationControllerTest` +- `EvaluationExecutionServiceTest` +- `EvaluationMetricsCalculatorTest` +- `EvaluationRepositoryTest` +- `DeterministicEvaluationModelClientTest` + +These tests prove the service boundary, retry behavior, metric calculation, and +database mapping without requiring model weights. + +## Frontend Error Behavior + +If the frontend is running but the Java backend is not available, admin pages +show: + +```text +Backend API unavailable. Start the Java backend and try again. +``` + +This message is intentionally clearer than the raw Vite proxy `502` response. +It does not hide real backend validation errors: JSON `message`, `error`, and +`detail` fields are still surfaced directly to the user. + +## Verification + +Run these checks before claiming the demo workflow is ready: + +```powershell +npm run test +npm run lint +npm run build +cd backend-java +mvn -B test +``` + +For a full interactive demo, also create and run one evaluation from +`/admin/evaluations` after the backend infrastructure is running. + +## Related Docs + +- `docs/documentation-standards.md` +- `docs/model-integration-framework.md` +- `docs/smoke-test-workflow.md` +- `docs/project-improvement-roadmap.md` diff --git a/docs/project-worklog.md b/docs/project-worklog.md index 42aa603..2caac5d 100644 --- a/docs/project-worklog.md +++ b/docs/project-worklog.md @@ -440,24 +440,63 @@ Why these matter: local storage. - They created the foundation for later evaluation and robustness work. +### 2026-07-09: Full-Stack Evaluation Demo Polish + +Branch: + +```text +feature/fullstack-evaluation-demo-smoke +``` + +Commit: + +```text +See the PR commit history after merge. +``` + +What changed: + +- Added a small frontend test boundary for API error formatting. +- Converted raw gateway failures into a clear backend availability message. +- Documented the admin evaluation demo path from frontend to Java backend. + +Why: + +- The evaluation workflow already existed, but the demo story needed a cleaner + operator experience when the backend is offline. +- Interviewers should be able to distinguish implemented evaluation execution + from deferred GPU model-weight work. + +Verification: + +- `npm run test` +- `npm run lint` +- `npm run build` +- `mvn -B test` + +Deferred: + +- Full interactive Docker demo remains pending until Docker is available. +- Real model weights remain deferred until a GPU server is prepared. + ## Next Recommended Work -Continue Phase B from `docs/project-improvement-roadmap.md`: +Continue Phase B from `docs/project-improvement-roadmap.md` with a narrower +verification and insight branch: ```text -feature/evaluation-frontend +feature/evaluation-result-insights ``` Scope: -- Add an evaluation list and detail page. -- Display status, attempts, aggregate metrics, and sample rows. -- Show wrong-sample filtering first; confusion matrix can follow in a later - polish branch. -- Keep the existing frontend visual style. +- Add a small confusion matrix or label breakdown to `/admin/evaluations`. +- Add clearer empty states for runs with no wrong samples. +- Keep the deterministic model boundary until GPU weights are available. +- Preserve the current frontend visual style. Reason: -The backend now has a measurable evaluation workflow. The next interview-visible -step is to make the evaluation result easy to inspect without changing the -project into a broad dashboard. +The project now has evaluation execution and a usable admin workbench. The next +interview-visible step is to make model quality easier to explain without +expanding scope into training, video detection, or heavy model operations. diff --git a/docs/superpowers/plans/2026-07-09-fullstack-evaluation-demo-smoke.md b/docs/superpowers/plans/2026-07-09-fullstack-evaluation-demo-smoke.md new file mode 100644 index 0000000..0ffc3db --- /dev/null +++ b/docs/superpowers/plans/2026-07-09-fullstack-evaluation-demo-smoke.md @@ -0,0 +1,99 @@ +# Full-Stack Evaluation Demo Smoke Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Make the existing evaluation backend and admin evaluation page easier to demo as one full-stack workflow without downloading model weights. + +**Architecture:** Keep the Java backend as the business and persistence boundary, keep model execution behind the existing evaluation client interface, and keep the React admin UI visual style unchanged. This branch only improves frontend API error messaging, adds a small frontend test boundary, and documents a repeatable demo path. + +**Tech Stack:** React 18, TypeScript, Vite, Node built-in test runner, Spring Boot Java backend, Maven, existing deterministic evaluation model client. + +## Global Constraints + +- Do not download model weights. +- Do not replace the current frontend visual style. +- Do not fake successful backend data in the frontend. +- Keep API calls centralized in `src/api/backend.ts`. +- Keep docs concise and aligned with `docs/documentation-standards.md`. +- Verify with `npm run test`, `npm run lint`, `npm run build`, and `mvn -B test`. + +--- + +### Task 1: Add A Frontend API Error Parsing Test + +**Files:** +- Modify: `package.json` +- Create: `src/api/errorMessage.test.ts` + +**Interfaces:** +- Produces: expected behavior for `formatApiErrorMessage(status: number, bodyText: string): string` +- Consumes later: `src/api/errorMessage.ts` + +- [ ] Add a `test` script using Node's built-in test runner: + +```json +"test": "node --test --experimental-strip-types src/api/errorMessage.test.ts" +``` + +- [ ] Create `src/api/errorMessage.test.ts` with assertions for backend JSON errors, Vite proxy 502 errors, plain text errors, and empty responses. + +- [ ] Run `npm run test`. + +Expected: FAIL because `src/api/errorMessage.ts` does not exist yet. + +### Task 2: Implement Reusable API Error Formatting + +**Files:** +- Create: `src/api/errorMessage.ts` +- Modify: `src/api/backend.ts` + +**Interfaces:** +- Produces: `formatApiErrorMessage(status: number, bodyText: string): string` +- `src/api/backend.ts` must call the formatter after `response.text()`. + +- [ ] Implement `formatApiErrorMessage`. +- [ ] Parse backend JSON bodies with `message`, `error`, or `detail` fields. +- [ ] Map `502`, `503`, and `504` to: + +```text +Backend API unavailable. Start the Java backend and try again. +``` + +- [ ] Preserve non-empty plain text errors for other statuses. +- [ ] Use `Request failed with status ${status}` for empty bodies. +- [ ] Run `npm run test`. + +Expected: PASS. + +### Task 3: Document The Demo Workflow + +**Files:** +- Create: `docs/fullstack-evaluation-demo.md` +- Modify: `docs/README.md` +- Modify: `README.md` +- Modify: `docs/project-worklog.md` + +**Interfaces:** +- Produces a durable runbook that explains how to demo evaluation creation, execution, metrics inspection, and known local environment limits. + +- [ ] Add a how-to document with audience, current status, step-by-step run commands, verification checklist, and deferred Docker/GPU work. +- [ ] Link it from `docs/README.md`. +- [ ] Add a short pointer in root `README.md`. +- [ ] Add a dated worklog entry for the branch. + +### Task 4: Verify And Publish + +**Files:** +- All changed files. + +**Interfaces:** +- Produces a pushed branch and PR. + +- [ ] Run `npm run test`. +- [ ] Run `npm run lint`. +- [ ] Run `npm run build`. +- [ ] Run `mvn -B test` from `backend-java`. +- [ ] Run `git diff --check`. +- [ ] Commit as `feat: polish full-stack evaluation demo flow`. +- [ ] Push `feature/fullstack-evaluation-demo-smoke`. +- [ ] Open a PR and wait for CI. diff --git a/package.json b/package.json index 52b2aae..07b1d28 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint .", + "test": "node --test --experimental-strip-types src/api/errorMessage.test.ts", "preview": "vite preview" }, "dependencies": { diff --git a/src/api/backend.ts b/src/api/backend.ts index 8faf6f3..5ebb896 100644 --- a/src/api/backend.ts +++ b/src/api/backend.ts @@ -1,3 +1,5 @@ +import { formatApiErrorMessage } from './errorMessage'; + export type DetectionStatus = 'QUEUED' | 'INFERENCING' | 'COMPLETED' | 'FAILED'; export type EvaluationStatus = 'QUEUED' | 'RUNNING' | 'COMPLETED' | 'FAILED'; export type ModelLabel = 'AUTHENTIC' | 'SYNTHETIC' | 'UNCERTAIN'; @@ -159,8 +161,8 @@ async function apiRequest(path: string, init?: RequestInit): Promise { }); if (!response.ok) { - const message = await response.text(); - throw new ApiError(message || `Request failed with status ${response.status}`, response.status); + const bodyText = await response.text(); + throw new ApiError(formatApiErrorMessage(response.status, bodyText), response.status); } return response.json() as Promise; diff --git a/src/api/errorMessage.test.ts b/src/api/errorMessage.test.ts new file mode 100644 index 0000000..612070e --- /dev/null +++ b/src/api/errorMessage.test.ts @@ -0,0 +1,34 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { formatApiErrorMessage } from './errorMessage.ts'; + +test('uses backend JSON message when present', () => { + const message = formatApiErrorMessage(400, '{"message":"Manifest must contain a header row."}'); + + assert.equal(message, 'Manifest must contain a header row.'); +}); + +test('uses backend JSON error fallback when message is absent', () => { + const message = formatApiErrorMessage(404, '{"error":"Evaluation not found."}'); + + assert.equal(message, 'Evaluation not found.'); +}); + +test('maps gateway failures to a backend availability hint', () => { + const message = formatApiErrorMessage(502, 'Bad Gateway'); + + assert.equal(message, 'Backend API unavailable. Start the Java backend and try again.'); +}); + +test('preserves non-empty plain text errors for non-gateway statuses', () => { + const message = formatApiErrorMessage(409, 'Evaluation is already running.'); + + assert.equal(message, 'Evaluation is already running.'); +}); + +test('falls back to status text when the response body is empty', () => { + const message = formatApiErrorMessage(418, ''); + + assert.equal(message, 'Request failed with status 418'); +}); diff --git a/src/api/errorMessage.ts b/src/api/errorMessage.ts new file mode 100644 index 0000000..6614d77 --- /dev/null +++ b/src/api/errorMessage.ts @@ -0,0 +1,29 @@ +const BACKEND_UNAVAILABLE_MESSAGE = 'Backend API unavailable. Start the Java backend and try again.'; +const BACKEND_UNAVAILABLE_STATUSES = new Set([502, 503, 504]); + +function extractJsonMessage(bodyText: string) { + try { + const parsed: unknown = JSON.parse(bodyText); + if (!parsed || typeof parsed !== 'object') return null; + + const body = parsed as Record; + const candidates = [body.message, body.error, body.detail]; + const message = candidates.find((candidate) => typeof candidate === 'string' && candidate.trim().length > 0); + return typeof message === 'string' ? message.trim() : null; + } catch { + return null; + } +} + +export function formatApiErrorMessage(status: number, bodyText: string) { + if (BACKEND_UNAVAILABLE_STATUSES.has(status)) { + return BACKEND_UNAVAILABLE_MESSAGE; + } + + const trimmedBody = bodyText.trim(); + if (!trimmedBody) { + return `Request failed with status ${status}`; + } + + return extractJsonMessage(trimmedBody) ?? trimmedBody; +} diff --git a/tsconfig.app.json b/tsconfig.app.json index ebde7f0..99984a6 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -24,5 +24,6 @@ "erasableSyntaxOnly": true, "noFallthroughCasesInSwitch": true }, - "include": ["src"] + "include": ["src"], + "exclude": ["src/**/*.test.ts"] }