217 lines
7.9 KiB
Markdown
217 lines
7.9 KiB
Markdown
# 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}`
|
|
- `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}`
|
|
- `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}`
|
|
- `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
|
|
- `GET /api/review/stats` — Review queue statistics
|
|
- Response: `{total_in_queue: N, annotated_today: N, avg_confidence: F, confidence_distribution: {...}}`
|
|
|
|
### 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
|
|
|
|
### 7.3 Data Export
|
|
**File:** `src/review_export.py`
|
|
|
|
Features:
|
|
- **CSV Export:**
|
|
```csv
|
|
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:**
|
|
```json
|
|
[
|
|
{
|
|
"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:
|
|
```yaml
|
|
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:
|
|
```yaml
|
|
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://<server-ip>: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
|