7.9 KiB
STORY-07: Review Interface
Epic
E2: Routing & Review — As an annotator, I can view low-confidence videos and label them.
Related Requirements
| ID | Requirement |
|---|---|
| FR-05 | Manual review interface for low-confidence videos: Displays video + contributing frames/crops + model confidence; Supports binary labeling |
| FR-07 | Metadata logging & audit trail: Stores annotated labels for active learning |
| NFR-06 | Observability: Tracks review queue depth |
| TC-06 | Network Security: Internal LAN only; no auth required |
Description
Implement a lightweight web-based review interface for annotating low-confidence videos. Display the video player, top-k contributing frames, model confidence scores, and allow annotators to toggle the ground truth label. Support CSV/JSON export of annotated data for active learning.
Scope
In Scope
- Lightweight web UI (Flask/FastAPI) serving on internal LAN
- Query DB for videos with routing_decision = REVIEW
- Video player with playback controls
- Display top-k contributing frames (highest confidence frames)
- Display model confidence scores per frame
- Binary label toggle (True/False — target class present or not)
- Label persistence to DB (review_queue table)
- CSV/JSON export of annotated data with ground truth
- Accessible via internal IP:Port (no auth, no SSL)
Out of Scope
- Frame sampling (covered in STORY-03)
- Face detection (covered in STORY-04)
- Classification (covered in STORY-05)
- Confidence aggregation (covered in STORY-05)
- Active learning pipeline / model retraining (covered in STORY-08)
- Monitoring dashboards (covered in STORY-09)
Deliverables
7.1 Review Backend
File: src/review_api.py
API Endpoints:
GET /api/review/queue— List videos in review queue- Query:
SELECT * FROM review_queue WHERE annotated = false ORDER BY created_at DESC - Pagination: 20 items per page
- Response:
{videos: [...], total: N, page: P, per_page: 20}
- Query:
GET /api/review/{video_id}— Get video details for annotation- Response:
{video_id, file_path, confidence_score, routing_decision, model_version, frame_count, contributing_frames: [{timestamp, crop_path, confidence}], video_duration}
- Response:
POST /api/review/{video_id}/label— Submit annotation- Body:
{ground_truth: true/false, notes: string (optional)} - Updates:
review_queue.annotated = true,review_queue.ground_truth = value,review_queue.annotated_at = NOW() - Response:
{status: 'annotated', video_id, ground_truth}
- Body:
GET /api/review/export— Export annotated data- Query params:
format=csv|json,annotated=true/false,date_from,date_to - Response: File download with annotated data
- Query params:
GET /api/review/stats— Review queue statistics- Response:
{total_in_queue: N, annotated_today: N, avg_confidence: F, confidence_distribution: {...}}
- Response:
7.2 Review Frontend
File: ui/review/
Pages:
- Queue Page (
/): List of videos awaiting review- Table columns: Video ID, File Path, Confidence Score, Model Version, Date Added, Actions (View)
- Sortable by confidence, date, file path
- Filter by confidence range, model version
- Pagination (20 items per page)
- Annotation Page (
/review/{video_id}): Video annotation interface- Video player with playback controls (HTML5
<video>element) - Top-k contributing frames displayed as thumbnails (k=5 default)
- Confidence scores displayed per frame
- Label toggle button (True/False) with confirmation
- Optional notes field
- Submit button (saves to DB via API)
- Navigation: Previous/Next video in queue
- Video player with playback controls (HTML5
7.3 Data Export
File: src/review_export.py
Features:
- CSV Export:
video_id,file_path,confidence_score,routing_decision,model_version,ground_truth,annotated_at,contributing_frames 12345,/data/input/video.mp4,0.62,REVIEW,v1.2.0,true,2026-08-03T10:30:00Z,"[{'timestamp': 30.0, 'crop_path': '/scratch/12345/crops/30000.jpg', 'confidence': 0.82}, ...]" - JSON Export:
[ { "video_id": 12345, "file_path": "/data/input/video.mp4", "confidence_score": 0.62, "routing_decision": "REVIEW", "model_version": "v1.2.0", "ground_truth": true, "annotated_at": "2026-08-03T10:30:00Z", "contributing_frames": [ {"timestamp": 30.0, "crop_path": "/scratch/12345/crops/30000.jpg", "confidence": 0.82} ] } ] - Export Options:
- Filter by annotation status (annotated/unannotated)
- Filter by date range
- Filter by model version
- Filter by ground truth label
- Output Location:
/data/output/reviews/
7.4 UI Container
File: ui/Dockerfile
Base image: python:3.10-slim
Installed packages:
- Flask 3.0+ or FastAPI 0.100+ (lightweight web framework)
- Jinja2 3.1+ (template engine)
- PyMySQL (database connection for API)
- gunicorn (WSGI server)
7.5 Docker Compose Update
File: docker-compose.yml (update)
Add UI service:
ui:
build:
context: ./ui
dockerfile: Dockerfile
ports:
- "5000:5000"
volumes:
- ./ui:/app/ui
environment:
- DB_HOST=mariadb
- DB_PORT=3306
- DB_NAME=videodetect
- DB_USER=videodetect
- DB_PASSWORD=${DB_PASSWORD}
depends_on:
- mariadb
networks:
- videodetect-network
7.6 Database Update
File: db/schema.sql (review_queue table — from STORY-01)
The review_queue table was defined in STORY-01. This story populates and queries it.
7.7 Configuration Updates
File: config.yaml (updates)
New fields:
review_ui:
host: "0.0.0.0"
port: 5000
per_page: 20
top_k_frames: 5
export_path: /data/output/reviews
auth_enabled: false # per TC-06
ssl_enabled: false # per TC-06
Acceptance Criteria
Functional
- Review queue displays all videos with routing_decision = REVIEW and annotated = false
- Video player loads and plays the video correctly
- Top-k contributing frames are displayed as thumbnails with confidence scores
- Annotator can toggle label (True/False) and submit
- Submitted label is persisted to DB (review_queue table)
- Annotated videos are removed from the default queue view
- CSV export produces valid CSV with all required fields
- JSON export produces valid JSON with all required fields
- Export includes ground truth labels and contributing frame data
- UI is accessible via http://:5000 (no auth, no SSL)
- Pagination works correctly (20 items per page)
- Sort and filter operations work on the queue page
Non-Functional
- Queue page loads in < 2 seconds (with 1000+ videos in queue)
- Video player loads in < 3 seconds
- Label submission completes in < 1 second
- Export of 1000 annotated videos completes in < 10 seconds
- UI uses < 100MB RAM at idle
- No authentication or SSL configured (per TC-06)
Technical Constraints
- UI runs in Docker container (per TC-05)
- No reverse proxy configured
- No SSL certificates configured
- No authentication mechanism configured
- All API responses are JSON
- Database queries use parameterized statements
- Export files are UTF-8 encoded
Dependencies
- Prerequisites: STORY-01 (Foundation — DB schema), STORY-05 (Classification — provides routing decisions)
- Depends on: None (can be built in parallel with STORY-04, STORY-05)
- Enables: STORY-08 (Active Learning — provides labeled training data)
Risks & Mitigations
| Risk | Mitigation |
|---|---|
| Video playback in browser requires compatible format | Serve videos in web-compatible format (H.264 MP4); transcode if needed |
| Crop paths may not be accessible from UI container | Store crop paths in DB; serve via API endpoint |
| No auth means anyone on LAN can access | Acceptable per TC-06; document in security notes |
| Large review queue slows page loads | Implement server-side pagination; lazy load thumbnails |
Estimated Effort
- Sprint: 7
- Story Points: 21
- Dependencies: STORY-01, STORY-05