Skip to content

Repository files navigation

PSC Coach Installation + Deployment Guide

PSC Coach is a full-stack Mandarin speaking practice system. The project is not a single web app binary: it is a static frontend, a FastAPI backend, an async worker, a PostgreSQL database, a Redis-backed queue, and an S3-compatible object store, plus a small but important set of repo-local source files used at startup to seed content.

This README is deployment-first. It explains what has to exist for the full system to boot, what can run locally with Docker Compose today, and what needs to be packaged for a real production deployment.

0. Thorough Local Install (Submission Repro)

Use this section if you want a clean, reviewer-friendly setup from zero.

0.1 Prerequisites

  • Docker Desktop with Compose v2
  • Python 3.11
  • ffmpeg (needed by worker audio normalization)
  • Optional: Conda (if you prefer environment.yml)

0.2 Bootstrap the repo

From the repo root:

cp .env.example .env

Then review at least:

  • OBJECT_STORE_PUBLIC_ENDPOINT (for local Docker, keep http://localhost:9000)
  • STT_PROVIDER (auto for local bootstrap, real provider for production)
  • REFERENCE_AUDIO_MODE and REFERENCE_AUDIO_LOCAL_ROOT

0.3 Ensure runtime data exists (critical)

This project requires local runtime source data under ALL-SRC/ and ALL-SRC-EXTRACTED/. A code-only checkout is not enough.

Minimum required files:

ALL-SRC/99_indices_and_design/cloud_inclusion_manifest.csv
ALL-SRC/00_cloud_core_content/stage30_s3_usage_and_oral_builder/s3_task_spec_from_briefing.csv
ALL-SRC/00_cloud_core_content/stage40_s4_reading_fluency_bank/2026_reading_passages_50/普通话测试50篇朗读范文短文.docx
ALL-SRC/00_cloud_core_content/stage50_s5_topic_expression_bank/2026_topics_100_300/命题说话题目汇总.docx
ALL-SRC-EXTRACTED/stage10/parser/stage10_paper_section_segmentation.csv
ALL-SRC-EXTRACTED/stage20/cleaned/polyphone_hand_cleaned.csv

Without these files, API startup/import-time seeding can fail.

0.4 Start the full stack with Docker (recommended)

make up
make up-app
make health

Open the UI:

  • http://localhost:8512

Run end-to-end smoke test:

python3 tools/smoke_user_flow.py

0.5 Alternative: run API/worker on host (infra in Docker)

make up
python3 -m pip install -r requirements-dev.txt
uvicorn apps.api.app.main:app --reload --port 8000
python -m worker.main
cd apps/web && python3 -m http.server 4173

In this mode, web UI is at http://localhost:4173.

0.6 What to include when publishing

  • Commit code/config/docs only.
  • Do not commit large media archives from ALL-SRC/ or ALL-SRC-EXTRACTED/; they are gitignored by design.
  • For deployment, package only the minimum runtime data subset (Section 5), plus optional reference audio if needed.

1. What You Are Deploying

At runtime the project is split into these pieces:

Browser
  -> static frontend from apps/web
  -> calls FastAPI
  -> uploads audio directly to S3-compatible object storage using presigned URLs

FastAPI API (apps/api/app/main.py)
  -> initializes schema + runs migrations + seeds runtime content at startup
  -> reads/writes PostgreSQL
  -> enqueues analysis jobs into Redis
  -> generates presigned object-store URLs

Worker (worker/main.py)
  -> consumes Redis queue
  -> downloads uploaded audio from object storage
  -> normalizes audio with ffmpeg
  -> runs STT/scoring pipeline
  -> writes review results back to PostgreSQL

PostgreSQL
  -> source of truth for content, attempts, reviews, user progress

Redis
  -> job queue, retry queue, DLQ tracking

S3-compatible object store
  -> uploaded attempt audio
  -> optional reference audio if you choose object-store mode

Repo-local runtime source bundle
  -> ALL-SRC/
  -> ALL-SRC-EXTRACTED/
  -> required for import-time manifest loading and startup seeding

2. Deployment Constraints You Need To Know First

These are the constraints that matter before you pick a host.

2.1 The frontend is static

  • apps/web is plain HTML/CSS/JS.
  • There is no Node build step in this repo.
  • Routes are hash-based (#/home, #/path, and so on), so a static host does not need SPA rewrite rules for the current route model.

2.2 The backend is not serverless-shaped

  • The API is a long-running FastAPI service.
  • The worker is a separate long-running process.
  • The worker depends on Redis, object storage, and ffmpeg.
  • A frontend-only host is not enough for the full stack.

2.3 A Git checkout alone is not enough

The code imports and seeds against files under ALL-SRC/ and ALL-SRC-EXTRACTED/.

Two deployment-critical consequences:

  • apps/api/app/source_registry.py reads ALL-SRC/99_indices_and_design/cloud_inclusion_manifest.csv during import. If that file is missing, the API can fail before the app is usable.
  • db.init_db() calls seed_runtime_content() on startup, and that seeding logic reads staged source documents and extracted CSV artifacts from the repo tree.

Because both ALL-SRC/ and ALL-SRC-EXTRACTED/ are gitignored, a code-only deploy from Git is incomplete unless you explicitly package the runtime data subset too.

2.4 Startup writes artifacts to disk

During database initialization the API also generates coverage/demo artifacts under:

  • artifacts/coverage/
  • artifacts/demo/

That means the API container/file system cannot be strictly read-only in its current form unless you change the code or mount writable storage for those paths.

2.5 /healthz is shallow

GET /healthz returns {"status": "ok"}. It confirms the API process is up, but it does not continuously probe PostgreSQL, Redis, or object storage. For real validation you should also hit content/home endpoints and run the smoke workflow described later in this document.

3. Relevant Repository Layout

Only part of the repo matters for deployment:

apps/
  api/
    app/
    sql/
  web/
worker/
shared/
infra/docker/Dockerfile.dev
docker-compose.yml
Makefile
requirements.txt
requirements-dev.txt
.env.example
tools/smoke_user_flow.py
ALL-SRC/                 # gitignored, but needed at runtime
ALL-SRC-EXTRACTED/       # gitignored, but needed at runtime

Useful service-specific docs already in the repo:

  • apps/api/README.md
  • worker/README.md
  • deploy.md

For full-project deployment, this README should be the starting point.

4. Runtime Requirements

4.1 Required infrastructure

  • Python 3.11
  • PostgreSQL deployment (local Compose uses Postgres 16)
  • Redis deployment (local Compose uses Redis 7)
  • S3-compatible object storage
  • ffmpeg available in the API/worker runtime image

4.2 Required runtime services

  • One API process: uvicorn apps.api.app.main:app --host 0.0.0.0 --port 8000
  • One worker process: python -m worker.main
  • One static web host serving apps/web/

4.3 Required writable locations

At minimum, allow writes to:

  • artifacts/coverage/
  • artifacts/demo/
  • normal temp space used by Python/ffmpeg, typically /tmp

4.4 Recommended but optional runtime assets

  • local S4 reference audio directory for first release
  • generated S1 reference audio directory
  • local faster-whisper model cache if using a local STT provider
  • Vosk model directory if using STT_PROVIDER=vosk_local

5. Minimum Runtime Data Bundle

If you want the full project to seed and run correctly, package the following files/directories with the deployable artifact.

5.1 Strictly required for startup/seeding

These are the important minimum paths to include:

ALL-SRC/99_indices_and_design/cloud_inclusion_manifest.csv

ALL-SRC/00_cloud_core_content/stage30_s3_usage_and_oral_builder/README.md
ALL-SRC/00_cloud_core_content/stage30_s3_usage_and_oral_builder/s3_task_spec_from_briefing.csv

ALL-SRC/00_cloud_core_content/stage40_s4_reading_fluency_bank/2026_reading_passages_50/普通话测试50篇朗读范文短文.docx

ALL-SRC/00_cloud_core_content/stage50_s5_topic_expression_bank/2026_topics_100_300/命题说话题目汇总.docx

ALL-SRC/01_cloud_optional_reference/stage20_s1_s2_supporting_docs/other_word_materials/普通话水平测试字表.doc
ALL-SRC/01_cloud_optional_reference/stage20_s1_s2_supporting_docs/other_word_materials/普通话常用平翘舌音字表.doc
ALL-SRC/01_cloud_optional_reference/stage20_s1_s2_supporting_docs/other_word_materials/普通话水平测试难点音字表.doc
ALL-SRC/01_cloud_optional_reference/stage20_s1_s2_supporting_docs/other_word_materials/普通话音变(轻声、儿化).doc
ALL-SRC/01_cloud_optional_reference/stage20_s1_s2_supporting_docs/other_word_materials/普通话音调(阴、阳、上、去).doc
ALL-SRC/01_cloud_optional_reference/stage20_phonetics_docs_for_tagging/psc_improvement_guides_and_audio/普通话水平考试常用儿化音表.docx
ALL-SRC/01_cloud_optional_reference/stage20_phonetics_docs_for_tagging/psc_improvement_guides_and_audio/普通话考试易错字词.doc

ALL-SRC-EXTRACTED/stage10/parser/stage10_paper_section_segmentation.csv
ALL-SRC-EXTRACTED/stage20/cleaned/polyphone_hand_cleaned.csv

If these files are missing, expect one or more of the following:

  • import-time failure in source registry
  • empty or incomplete seeded content
  • broken guidebook/content metadata
  • startup errors during DB initialization

5.2 Recommended for a complete first release

Include these too if you want reference audio to work immediately without separately uploading media into object storage:

ALL-SRC/01_cloud_optional_reference/stage40_s4_reference_audio_sample/reading_work_50_audio/
ALL-SRC-EXTRACTED/stage20/support/s1_reference_audio_generated/

For a first release, REFERENCE_AUDIO_MODE=local is the simplest option because it avoids pre-populating object storage with reference media.

6. Environment Variables

Use the top-level .env.example as the main template for full-stack deployment. Do not treat infra/.env.example as the canonical full-project env file; the top-level template matches the current app config and compose setup.

6.1 Core application settings

Variable Required Purpose
DATABASE_URL Yes PostgreSQL DSN used by API and worker
REDIS_URL Yes Redis DSN used for queue operations
OBJECT_STORE_ENDPOINT Yes Internal S3-compatible endpoint reachable by API and worker
OBJECT_STORE_PUBLIC_ENDPOINT Usually Browser-reachable S3-compatible endpoint for presigned upload/download URLs
OBJECT_STORE_BUCKET Yes Bucket name for uploaded audio and optional reference media
OBJECT_STORE_ACCESS_KEY Yes S3 credential
OBJECT_STORE_SECRET_KEY Yes S3 credential
OBJECT_STORE_REGION No Defaults to us-east-1
UPLOAD_URL_EXPIRY_SECONDS No Presigned upload TTL; defaults to 900
CORS_ALLOW_ORIGINS Yes in production Allowed browser origins; do not leave * in production

6.2 Queue and retry settings

Variable Required Purpose
QUEUE_NAME No Redis list for new jobs
QUEUE_RETRY_ZSET No Retry schedule set
QUEUE_DLQ_NAME No Dead-letter queue
QUEUE_RETRY_COUNT_HASH No Retry counter hash
WORKER_MAX_RETRIES No Defaults to 3
WORKER_RETRY_BACKOFF_SECONDS No Defaults to 3

6.3 Reference audio settings

Variable Required Purpose
REFERENCE_AUDIO_MODE No auto, local, object_store, or none
REFERENCE_AUDIO_LOCAL_ROOT Required for local mode Root directory for local reference audio
REFERENCE_AUDIO_URL_EXPIRY_SECONDS No Signed GET URL expiry for reference audio

Recommended first-release setting:

REFERENCE_AUDIO_MODE=local
REFERENCE_AUDIO_LOCAL_ROOT=/workspace/ALL-SRC/01_cloud_optional_reference/stage40_s4_reference_audio_sample/reading_work_50_audio

6.4 STT / speech analysis settings

STT_PROVIDER currently supports these values:

  • auto
  • faster_whisper_local
  • local
  • vosk_local
  • iflytek_iat
  • iflytek_rtasr
  • iflytek_ise
  • none
  • mock

Important related variables:

  • LOCAL_STT_MODEL
  • LOCAL_STT_DEVICE
  • LOCAL_STT_COMPUTE_TYPE
  • LOCAL_STT_MODEL_DIR
  • VOSK_MODEL_PATH
  • STT_LANGUAGE
  • STT_TIMEOUT_SECONDS
  • all IFLYTEK_* credentials/settings shown in .env.example

Production guidance:

  • use a real provider (iflytek_*, local whisper, or Vosk)
  • set STT_ALLOW_MOCK_FALLBACK=false
  • keep mock for tests/demo only

6.5 Demo toggles

These should stay off in production:

STT_ALLOW_MOCK_FALLBACK=false
PSC_DEMO_DIRECT_ANALYZE=false
PSC_DEMO_FORCE_FALLBACK_REVIEW=false

7. Local Full-Stack Deployment With Docker Compose

This is the fastest way to boot the entire project on one machine using the files already in the repo.

7.1 What the compose stack does

docker-compose.yml provides:

  • postgres
  • redis
  • minio
  • minio-init to create the bucket
  • api
  • worker
  • web
  • dev
  • test
  • test-smoke

The app services use the existing infra/docker/Dockerfile.dev image.

7.2 First-time setup

From the repo root:

cp .env.example .env

Then review at least these values:

  • OBJECT_STORE_PUBLIC_ENDPOINT
  • STT_PROVIDER
  • REFERENCE_AUDIO_MODE
  • REFERENCE_AUDIO_LOCAL_ROOT
  • any IFLYTEK_* credentials if using iFLYTEK

7.3 Start infrastructure only

make up

This starts the core dependencies defined without profiles:

  • PostgreSQL
  • Redis
  • MinIO
  • MinIO bucket initialization

7.4 Start the full app stack

make up-app

or:

make demo

That starts:

  • API on http://localhost:8000
  • worker in the background
  • static frontend on http://localhost:8512

Default local ports:

Service URL / Port
Frontend UI http://localhost:8512
API http://localhost:8000
PostgreSQL localhost:5432
Redis localhost:6379
MinIO API http://localhost:9000
MinIO Console http://localhost:9001

7.5 Local validation

Check health and smoke flow:

make health
python3 tools/smoke_user_flow.py

Or use the make target:

make smoke-workflow

If the smoke script passes, you have verified:

  • API boot
  • content/home endpoints
  • attempt creation
  • presigned upload URL generation
  • audio upload
  • queue submission
  • worker processing
  • review payload generation

7.6 Useful local operations

make ps
make logs
make logs-app
make test-docker
make qa
make down

8. Local Host-Run Deployment (Without Running API/Worker Inside Docker)

This is useful if you want Docker only for infra but run Python directly on the host.

8.1 Start backing services

make up

8.2 Install Python dependencies

Use either pip or conda:

python3 -m pip install -r requirements-dev.txt

or:

make conda-env
conda activate psc-coach

8.3 Run each service

API:

uvicorn apps.api.app.main:app --reload --port 8000

Worker:

python -m worker.main

Web:

cd apps/web
python3 -m http.server 4173

In this mode the browser-facing UI will be at http://localhost:4173 unless you proxy it.

9. Production Deployment Model

The project deploys cleanly if you think in terms of three runtime workloads and three managed dependencies:

9.1 Workloads

  1. Static frontend serving apps/web
  2. API container running uvicorn apps.api.app.main:app --host 0.0.0.0 --port $PORT
  3. Worker container running python -m worker.main

9.2 Dependencies

  1. PostgreSQL
  2. Redis or Valkey-compatible Redis API
  3. S3-compatible object storage

9.3 Practical production principles

  • package the runtime data subset from Section 5 into the image/bundle
  • do not rely on a raw Git clone alone
  • keep API and worker on the same env set and the same code/data bundle
  • make object storage reachable both internally and from the browser
  • serve the web app separately as static assets

10. Production Packaging Using The Current Repo

The repo currently ships infra/docker/Dockerfile.dev, not a dedicated production Dockerfile. You can still use it for a first working deploy if you accept a larger-than-ideal image.

10.1 Build an image

From the repo root or from a trimmed deployment bundle:

docker build -f infra/docker/Dockerfile.dev -t psc-coach:latest .

Make sure the build context already contains:

  • runtime code (apps/, worker/, shared/)
  • SQL files
  • .env or injected environment at runtime
  • the required ALL-SRC/ and ALL-SRC-EXTRACTED/ subset

10.2 Run the API container

Example:

docker run --rm \
  --name psc-api \
  -p 8000:8000 \
  --env-file .env \
  psc-coach:latest \
  uvicorn apps.api.app.main:app --host 0.0.0.0 --port 8000

10.3 Run the worker container

Example:

docker run --rm \
  --name psc-worker \
  --env-file .env \
  psc-coach:latest \
  python -m worker.main

The API and worker must both be able to reach the same:

  • PostgreSQL database
  • Redis instance
  • object storage bucket
  • packaged runtime source bundle from Section 5

Also keep the runtime filesystem writable for artifacts/coverage and artifacts/demo, or mount writable storage for those paths.

10.4 Serve the frontend

Any static host can serve apps/web/. For a simple containerized variant:

docker run --rm \
  --name psc-web \
  -p 8512:4173 \
  -v "$PWD/apps/web:/workspace/apps/web:ro" \
  -w /workspace/apps/web \
  python:3.11-slim \
  python3 -m http.server 4173 --bind 0.0.0.0

For a real production deployment, a CDN/static-site host is a better fit than leaving the frontend on python -m http.server.

11. Frontend Deployment Details

11.1 No build step

There is no frontend build pipeline in this repo right now. Deploy the contents of apps/web/ as static files.

11.2 Hash routing

Because the app uses hash routes, the current frontend does not require SPA rewrite rules for route resolution.

11.3 Pointing the web app at the real API

apps/web/js/api.js looks for window.PSC_API_BASE first. If your frontend and API are on different origins, set that explicitly before loading app.js.

Example:

<script>
  window.PSC_API_BASE = "https://api.example.com";
</script>
<script type="module" src="./js/app.js"></script>

Without that override, the frontend probes:

  • same-origin
  • same host on port 8000
  • http://localhost:8000
  • http://127.0.0.1:8000

That works for local development, but not for most split production deployments.

12. Object Storage Requirements

The object store is not optional for the full workflow.

It is used for:

  • audio upload URLs returned by POST /v1/attempts/{attempt_id}/upload-url
  • worker-side audio download and processing
  • optional reference-audio download URLs in object-store mode

12.1 Internal vs public endpoint

Use both endpoints correctly:

  • OBJECT_STORE_ENDPOINT: internal endpoint reachable from API and worker
  • OBJECT_STORE_PUBLIC_ENDPOINT: endpoint reachable from the browser for presigned URLs

Local Docker example:

  • internal: http://minio:9000
  • public: http://localhost:9000

If OBJECT_STORE_PUBLIC_ENDPOINT is wrong, uploads can fail in the browser even though the API itself can talk to the object store.

12.2 Bucket setup

Local Compose creates the bucket automatically through minio-init.

In production you must ensure the bucket already exists or provision it before the app starts.

13. Database Initialization Behavior

On API startup the app does all of the following:

  1. reads base schema SQL
  2. applies migrations from apps/api/sql/migrations/
  3. seeds static SQL content
  4. seeds runtime content from repo-local source files
  5. generates coverage/demo artifacts
  6. upserts the default user

Operational consequences:

  • the first boot can take longer than a simple stateless service
  • DB credentials must be valid before the API can become ready
  • the runtime source bundle must already be present at startup
  • the API needs a writable filesystem for artifact generation

The code uses advisory locks and a mutex around initialization, which helps when more than one process starts at once, but you should still treat first boot as a stateful initialization phase rather than a purely disposable stateless startup.

14. Recommended Production Settings

For a straightforward first release, these settings are the safest defaults:

CORS_ALLOW_ORIGINS=https://your-frontend-origin.example

REFERENCE_AUDIO_MODE=local
REFERENCE_AUDIO_LOCAL_ROOT=/workspace/ALL-SRC/01_cloud_optional_reference/stage40_s4_reference_audio_sample/reading_work_50_audio

STT_PROVIDER=iflytek_iat
STT_ALLOW_MOCK_FALLBACK=false

PSC_DEMO_DIRECT_ANALYZE=false
PSC_DEMO_FORCE_FALLBACK_REVIEW=false

WORKER_MAX_RETRIES=3
WORKER_RETRY_BACKOFF_SECONDS=3

If you are not ready to use a live STT provider yet:

  • STT_PROVIDER=auto or none will still let the system run
  • review quality becomes more fallback-driven and less production-grade

15. Deployment Validation Checklist

Run these checks in order after every deployment.

15.1 Basic API checks

curl -sSf https://api.example.com/healthz
curl -sSf "https://api.example.com/v1/home?section=S1&mode=auto&pattern=psc&demo_mode=true"
curl -sSf "https://api.example.com/v1/path?section=S1"
curl -sSf "https://api.example.com/v1/content?limit=1&demo_mode=true"

15.2 End-to-end smoke test

Run the built-in smoke script against the deployed API:

API_BASE_URL=https://api.example.com python3 tools/smoke_user_flow.py

This is the most useful deploy validation currently in the repo.

15.3 Manual browser validation

Confirm all of the following:

  • the frontend boots without API bootstrap errors
  • the home screen loads real data
  • a recording can be created and uploaded
  • attempt status progresses from created to queued to processing to done
  • the review screen renders a completed payload

16. Troubleshooting

16.1 API fails on startup with missing source/manifest errors

Cause:

  • required ALL-SRC or ALL-SRC-EXTRACTED files were not packaged

Fix:

  • include the minimum runtime data bundle from Section 5

16.2 Browser upload URL works in logs but fails in the browser

Cause:

  • OBJECT_STORE_PUBLIC_ENDPOINT points to an internal hostname such as minio:9000

Fix:

  • set OBJECT_STORE_PUBLIC_ENDPOINT to a browser-reachable URL

16.3 Frontend says API bootstrap failed

Cause:

  • the frontend could not discover the API origin
  • or one of the boot probe endpoints failed

Fix:

  • explicitly set window.PSC_API_BASE
  • verify /healthz, /v1/home, /v1/path, and /v1/content

16.4 Attempts stay queued forever

Cause:

  • worker is not running
  • API and worker are pointed at different Redis instances or queue names

Fix:

  • confirm python -m worker.main is running
  • confirm REDIS_URL, QUEUE_NAME, and retry/DLQ vars match between API and worker

16.5 Attempts move to error during processing

Common causes:

  • object store credentials wrong
  • uploaded object not reachable by worker
  • ffmpeg missing
  • STT provider unavailable or misconfigured

Fix:

  • inspect worker logs first
  • verify bucket/object existence
  • verify ffmpeg is installed in the runtime image
  • verify STT settings and credentials

16.6 Reference audio is missing

Cause:

  • REFERENCE_AUDIO_MODE resolved to none
  • local reference directory is absent
  • object-store mode is enabled but reference files were never uploaded

Fix:

  • use REFERENCE_AUDIO_MODE=local for first release
  • ensure REFERENCE_AUDIO_LOCAL_ROOT exists in the container

16.7 API startup fails on a read-only filesystem

Cause:

  • startup artifact generation writes under artifacts/coverage and artifacts/demo

Fix:

  • allow writes there
  • or patch the application before attempting read-only container execution

17. Security / Production Hardening Checklist

Before calling the deployment production-ready, verify this list:

  • set a real CORS_ALLOW_ORIGINS value
  • keep all demo toggles off
  • keep STT_ALLOW_MOCK_FALLBACK=false
  • use managed secrets, not committed .env files
  • ensure PostgreSQL, Redis, and object-store data are persistent
  • terminate TLS in front of the API and static frontend
  • monitor worker failures and DLQ growth
  • back up PostgreSQL and object storage
  • package only the required runtime source subset, not the full local archive unless needed

18. Quick Start Summary

If you only need the shortest route to a working full-stack deployment:

  1. Copy .env.example to .env.
  2. Ensure the required ALL-SRC and ALL-SRC-EXTRACTED runtime subset exists locally.
  3. Run make up.
  4. Run make up-app.
  5. Open http://localhost:8512.
  6. Run python3 tools/smoke_user_flow.py.

If you need a real production deployment:

  1. Package the runtime source subset into your deployable bundle/image.
  2. Provision PostgreSQL, Redis, and S3-compatible storage.
  3. Run one API container and one worker container with the same env settings.
  4. Serve apps/web/ as a static site.
  5. Set window.PSC_API_BASE if the frontend and API are on different origins.
  6. Run the smoke script against the deployed API.

19. Related Docs

  • apps/api/README.md
  • worker/README.md
  • deploy.md

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages