add test module

This commit is contained in:
2026-08-16 15:39:52 +08:00
parent d0310620fc
commit 6e4d93cea6
46 changed files with 3880 additions and 206 deletions

View File

@@ -1,92 +1,82 @@
# Sentence Boundary API
# Oral Trainer Video Service
This service looks up pre-generated sentence boundaries by the SHA-256 hash of
the exact video bytes. It does not analyze media during an API request.
FastAPI service for the Android oral-training SDK. It provides:
## Install
- chunked video upload with SHA-256 calculation;
- files stored under `data/v/` and metadata stored in SQLite;
- background sentence transcription through MOSS-Transcribe-Diarize;
- a browser-based administration page at `/admin`;
- video catalog and HTTP Range playback;
- sentence-boundary lookup compatible with the existing Android SDK;
- student recording upload and `asr-fluency-v1` assessment;
- the legacy JSON boundary index as a read-only fallback.
From the repository root:
## Local Run
```bash
python3 -m venv .venv-sentence-api
python3.12 -m venv .venv-sentence-api
. .venv-sentence-api/bin/activate
python -m pip install -r sentence_api/requirements.txt
```
## Generate The Index
Generate boundaries with the same silence detector used by the desktop player:
```bash
python -m sentence_api.generate_boundaries \
"/path/to/lesson.mp4" \
--index sentence_api/data/sentence_boundaries.json
```
The command calculates the SHA-256 hash, detects boundaries, converts seconds
to milliseconds, infers each `end_ms` from the next sentence start, and writes
the result atomically into the JSON index. The last sentence ends at the media
duration.
For a large course library, run this command in an ingestion worker and store
the same document in a database or object storage instead of committing the
JSON file to the application image.
## Run
```bash
SENTENCE_BOUNDARIES_FILE=sentence_api/data/sentence_boundaries.json \
export ADMIN_API_KEY=local-development-key
export MOSS_TRANSCRIBE_URL=http://127.0.0.1:8001/v1/audio/transcriptions
python -m uvicorn sentence_api.main:app --host 0.0.0.0 --port 8000
```
The interactive API documentation is available at `/docs`.
Open:
## Request
```http
GET /api/v1/videos/{sha256}/sentence-boundaries
```text
http://127.0.0.1:8000/admin
http://127.0.0.1:8000/docs
```
Example using the demo record in the checked-in index:
When `MOSS_TRANSCRIBE_URL` is empty, uploaded videos still receive silence-based
boundaries, but no reference transcript is produced and assessment is disabled.
## Main Endpoints
```text
GET /healthz
GET /api/v1/videos
GET /api/v1/videos/{sha256}
GET /api/v1/videos/{sha256}/content
GET /api/v1/videos/{sha256}/sentence-boundaries
POST /api/v1/videos/{sha256}/sentences/{index}/assessments
POST /api/v1/admin/videos
PUT /api/v1/admin/videos/raw
POST /api/v1/admin/videos/{sha256}/process
PUT /api/v1/admin/videos/{sha256}/sentences/{index}
DELETE /api/v1/admin/videos/{sha256}
```
Admin endpoints use `X-Admin-Key` when `ADMIN_API_KEY` is configured. Assessment
requests use `X-Client-Key` when `CLIENT_API_KEY` is configured.
## Assessment
```bash
curl http://127.0.0.1:8000/api/v1/videos/468a4d064f6ec49942b45e25ab93c500d31870f978c4f28ff8b3b408852326e0/sentence-boundaries
curl -X POST \
http://127.0.0.1:8000/api/v1/videos/{sha256}/sentences/0/assessments \
-F audio=@student.wav \
-F language=en
```
The MP4 used during development is also indexed. Its hash is
`b6631d5cf48f37fed0ecc623563dd48b7ed660689b4d25d2ebac7fecab807ddc`, and its
generated index contains 244 boundaries.
The current score intentionally measures reading content and fluency:
The response is:
```json
{
"video_hash": "...",
"duration_ms": 16000,
"algorithm_version": "silence-rms-v1",
"sentences": [
{
"index": 0,
"start_ms": 0,
"end_ms": 4230,
"text": null
}
]
}
```text
overall = content * 80% + fluency * 20%
fluency = duration * 35% + pauses * 40% + speech rate * 25%
```
Unknown hashes return `404`. A hash must be a 64-character hexadecimal
SHA-256 digest; malformed values return `422`.
`pronunciation_score` and `prosody_score` remain `null` until a phoneme/GOP
model is connected.
When testing from a physical Android phone, replace `127.0.0.1` with the
computer's LAN IP address. `127.0.0.1` on the phone refers to the phone itself.
For production, expose the API over HTTPS.
## Tests
## Android Request Flow
```bash
python -m pytest sentence_api/tests -q
```
The mobile app should calculate the hash from the selected `content://` URI in
streaming chunks, request the endpoint, map the returned `sentences` to
`SentenceBoundary`, and then call `controller.loadItem`. The hash must be
calculated from the exact bytes of the same video served to the player. For
HTTPS course videos, the course manifest can carry the hash and avoid hashing
the entire remote file on every device.
See [DEPLOYMENT.md](DEPLOYMENT.md) for Docker, MOSS, Nginx, HTTPS, large-file
upload, backup, and production operation instructions.