AI-powered police witness statement collection app. A chatbot interviews witnesses, extracts structured data, and produces formal MG11-style statements.
backend/ FastAPI + Kokoro TTS + SQLite
├── docker-compose.yml Runs API + Ollama together
└── src/
StatementAssist/ Flutter app (witness-facing)
PolicePortal/ Flutter app (officer-facing)
The backend serves both apps — StatementAssist for witness interviews and PolicePortal for officers reviewing completed statements.
- Docker with the Compose plugin
- Flutter SDK
- NVIDIA GPU (recommended): Install the NVIDIA Container Toolkit to enable GPU passthrough for faster LLM inference:
Without this, the backend will still run but Ollama will use CPU only (significantly slower).
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit sudo nvidia-ctk runtime configure --runtime=docker sudo systemctl restart docker
cd backend
./start.shstart.sh automatically detects whether an NVIDIA GPU is available and enables GPU passthrough if so, falling back to CPU-only otherwise.
This starts three containers (use ./start.sh -d to run in the background):
- api — FastAPI server on
localhost:8000(includes Kokoro TTS) - ollama — Local LLM server (pulls
qwen3:4b-instructon first run) - ngrok — Public tunnel so physical devices can reach the API
First run takes a few minutes to pull images and the model (~2.5GB).
Set your ngrok credentials in backend/.env:
NGROK_AUTHTOKEN=your_token_here
NGROK_DOMAIN=your-domain.ngrok-free.dev
cd StatementAssist
flutter pub get
# Default (connects via ngrok — works on physical devices)
flutter run
# Debug mode (connects to localhost:8000 — simulators/desktop only)
flutter run --dart-define=LOCAL=truecd PolicePortal
flutter pub get
# Default (connects via ngrok)
flutter run
# Debug mode (connects to localhost:8000)
flutter run --dart-define=LOCAL=trueConnects to the same backend to view and manage completed statements.
If you prefer running the backend directly:
cd backend
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
# Start Ollama separately (must be running on port 11434)
cd src
python main.pyRequires:
- Python 3.12+
- Ollama running at
http://localhost:11434 espeak-nginstalled (brew install espeak-ngon macOS,apt install espeak-ngon Ubuntu)
- Frontend: Flutter (Dart) with Material Design 3
- Backend: FastAPI (Python) with Peewee ORM (SQLite)
- LLM: Ollama (local,
qwen3:4b-instruct) - TTS: Kokoro ONNX (en-GB,
bm_fablevoice) - STT:
speech_to_textpackage (on-device) - PDF: ReportLab for MG11 statement generation
Landing → Form → Interview → Review → Submit
Witnesses provide their details, answer AI-guided interview questions via voice or text, then review and sign a generated MG11 statement. Officers access completed statements through PolicePortal.
When using Docker, all persistent data lives in backend/data/ (git-ignored):
witness_statements.db— SQLite databasestatements/— generated MG11 PDFsmodels/— Kokoro ONNX model files (~80MB, auto-downloaded)
Ollama models persist in a Docker named volume (ollama-models).
backend/
├── docker-compose.yml # API + Ollama + ngrok containers
├── Dockerfile
├── docker-entrypoint.sh # Waits for Ollama, pulls model, starts app
├── start.sh # GPU-aware launcher
├── requirements.txt
└── src/
├── main.py # FastAPI app, CORS, health check, /tts
├── routes.py # /sessions endpoints (auth-protected)
├── police_routes.py # /police endpoints
├── auth.py # Per-session bearer token auth
├── llm.py # Ollama chat with tool-calling loop
├── tools.py # Structured data extraction tools
├── tts.py # Kokoro ONNX TTS (streaming)
├── mg11.py # MG11 PDF generation (ReportLab)
└── database/
└── database.py # Peewee models: Session, Message
StatementAssist/
└── lib/
├── main.dart # AppShell navigation state machine
├── screens/
│ ├── landing_page.dart # Welcome screen
│ ├── landing_form.dart # Witness details input
│ ├── interview_chat.dart # Chat interface (voice + text)
│ ├── review_statement.dart# MG11 review, edit, sign
│ └── sessions_list.dart # Resume paused sessions
├── services/
│ ├── llm_service.dart # HTTP client for backend API
│ └── speech_service.dart # STT + TTS (Kokoro via backend)
├── models/
│ ├── chat_message.dart
│ ├── witness_details.dart
│ └── statement_draft.dart
└── widgets/
├── chat_bubble.dart
├── voice_visualizer.dart
├── input_mode_toggle.dart
└── feature_card.dart
PolicePortal/
└── lib/
├── main.dart
└── screens/
└── police_portal.dart # Statement list, view, URN assignment, PDF download
# StatementAssist Flutter tests
cd StatementAssist
flutter test
# PolicePortal Flutter tests
cd PolicePortal
flutter test
# Backend tests
cd backend
source venv/bin/activate
pytest