After story 1
This commit is contained in:
+216
@@ -0,0 +1,216 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user