Decimal is a local-first audio understanding stack. It ingests long lectures, meetings, or ad-hoc recordings, diarizes the speakers, transcribes them with Whisper, runs llama.cpp summaries, and exposes everything through a FastAPI backend plus a lightweight web client.
- Overview
- Tech Stack
- Repository Layout
- Prerequisites
- Setup
- Running the Stack
- Web & API Entry Points
- AI Pipeline
- Backend Data Model & Workflow
- Local Directories
- Useful Commands
- Troubleshooting
- Upload up to 10 audio files (mp3, wav, flac, opus, webm, etc.) per summary job with per-file limits of 10 GB.
- Organize recordings through Workspaces -> Subjects -> Summary Jobs, and keep every run auditable via SQLAlchemy models.
- Execute a six-stage AI pipeline (normalize, diarize, STT, merge, categorize, refine) backed by Whisper, pyannote, and llama.cpp with CPU/GPU fallbacks.
- Serve the resulting transcripts, summaries, artifacts, and downloadable files over FastAPI endpoints and an embedded web UI mounted at
/web. - Automate environment checks (Python >=3.10), dependency hashes,
.envcreation, PostgreSQL role provisioning, and uvicorn startup throughrun.py.
- Backend: FastAPI, Pydantic v2, SQLAlchemy 2.x, Alembic-ready schema, background tasks.
- Database: PostgreSQL (psycopg2) with UTF-8 enforcement; SQLite (
apps/api/test.db) can be used for quick local smoke tests. - AI: openai-whisper, pyannote.audio, torch/torchaudio, llama.cpp GGUF models, custom pipeline stages under
apps/ai. - Frontend: Static HTML/CSS/ES6 modules in
apps/web, consuming the REST API viafetch. - Tooling:
run.pylauncher, Hugging Face model bootstrap (apps/ai/bootstrap), ffmpeg/ffprobe for audio prep,docs/api/openapi.yamlfor schema documentation.
.
|-- run.py # Cross-platform launcher for the FastAPI app
|-- requirements.txt
|-- apps
| |-- api # FastAPI service, SQLAlchemy models, schemas, routes
| |-- ai # Audio + LLM pipeline, bootstrapper, resources
| |-- projects # Reserved for exported project bundles
| `-- web # Front-end assets served at /web
|-- docs
| |-- api/openapi.yaml # OpenAPI contract
| `-- architecture # Draw.io + PNG diagrams explaining the stack
|-- logs # Rotated uvicorn logs when using run.py --prod
|-- summary # User-curated summary exports
`-- tmp # PID files, requirement hashes, bootstrap markers
- Python 3.10 or later.
- PostgreSQL 14+ with a superuser (default
postgres) reachable viapsql. ffmpeg/ffprobeonPATHfor audio normalization and segmentation.- Git LFS (optional) if you plan to pull large pretrained models into the repo.
- Hugging Face access token with permission to download pyannote models; stored in
HUGGINGFACE_TOKEN. - (Optional) CUDA-capable GPU for faster Whisper/llama.cpp inference.
-
Create and activate a virtual environment
python -m venv .venv # Windows .\.venv\Scripts\activate # macOS/Linux source .venv/bin/activate
-
Install dependencies
python -m pip install --upgrade pip pip install -r requirements.txt
-
Configure environment variables (
.envin the repo root)
The launcher will auto-create this file on the first run, but you can edit it upfront. Key settings:Key Purpose ENVdevelopmentorproductiontoggle consumed by the app.PORTFastAPI port. run.pywill move to a free port if needed.PGHOST,PGPORTPostgreSQL host/port. PGUSER,PGPASSWORDApplication database role. PGDATABASETarget database name. DB_URLFull SQLAlchemy URL postgresql://USER:PASS@HOST:PORT/DB.POSTGRES_PASSWORDSuperuser password so run.pycan create roles/dbs. Leave empty if you manage it yourself.HUGGINGFACE_TOKENRequired by pyannote + HF downloads in the AI bootstrap. API_KEYReserved for future authenticated endpoints / web client. Grant Hugging Face / pyannote access
- Sign in to https://huggingface.co/ and request access to the
pyannote/speaker-diarization-3.1model family (you will find an "Access request" button on the model card). - Once approved, open Settings > Access Tokens, create a new token with the
readscope, and copy it. - Either run
huggingface-cli login --token <token>(preferred if multiple repos share the cache) or paste the token into theHUGGINGFACE_TOKENentry in.env. - Re-run the bootstrap step (below) any time you rotate the token so the pyannote pipeline can refresh its credentials.
- Sign in to https://huggingface.co/ and request access to the
-
Bootstrap the AI configuration and cache the models
This inspects your hardware, chooses reasonable Whisper/pyannote/llama.cpp targets, downloads weights, and writesapps/ai/ai.config.json.python -m apps.ai.bootstrap.manager
-
Prepare PostgreSQL (only needed once)
WithPOSTGRES_PASSWORDpopulated,python run.pywill create thePGUSERrole, grant privileges, and createPGDATABASE.
To do it manually:psql -h 127.0.0.1 -U postgres -c "CREATE USER app_user WITH PASSWORD 'app_password';" psql -h 127.0.0.1 -U postgres -c "CREATE DATABASE app_db OWNER app_user;"
python run.py # development mode (foreground, streaming uvicorn logs)
python run.py --prod # background mode, logs under logs/<app>_YYYYMMDD-HHMMSS.logrun.py handles:
- Python version verification.
requirements.txthash + auto-install when the file changes..envcreation and automatic PORT reassignment to avoid conflicts.- PostgreSQL connectivity probes and optional role/database bootstrap.
- PID management to prevent double-launches.
Customize it with --app-module apps.api.main:app, --default-port 9000, --keep-logs 10, etc.
uvicorn apps.api.main:app --host 0.0.0.0 --port 8000 --reloadUse this when iterating purely on the API and you do not need the safeguards bundled in run.py.
- Web client: http://localhost:8000/web
Upload recordings, monitor summary jobs, browse local folders, and read generated summaries. - Interactive docs: http://localhost:8000/docs (FastAPI Swagger UI) or read
docs/api/openapi.yaml. - Health check: http://localhost:8000/ (returns
{"message": "Hello Decimal"}once you expose such a route, or use the docs endpoint.)
All logic lives under apps/ai and can be executed independently via python -m apps.ai.main <audio-file>.
- NormalizeStage - Converts input audio to mono 16 kHz WAV with ffmpeg, splits long sessions into <=30 min chunks.
- DiarizeStage - Runs pyannote speaker diarization when models are available; otherwise produces deterministic placeholders so the rest of the pipeline still succeeds.
- STTStage - Uses Whisper (auto GPU/CPU + fp16 fallback) to create time-aligned transcripts per chunk.
- MergeStage - Aligns diarization turns with STT segments, builds speaker-attributed transcripts, and indexes dominant speakers.
- CategorizeLLMStage - Classifies the document type (conversation / lecture / meeting) using llama.cpp GGUF models or heuristics if the model is absent.
- RefineLLMStage - Generates formatted Markdown summaries using prompt templates tuned per document type; falls back to deterministic transcript merges when llama.cpp is unavailable.
Artifacts (chunks, diarization JSON, stt.json, speaker-attributed text, summary.txt) are written under apps/ai/output/<job_id> by apps/ai/io/storage.py.
- Workspace -> root folder grouping Subjects.
- Subject -> a logical course/meeting thread; stores
is_korean_onlyso the pipeline can pick different prompts/models. - SummaryJob -> one run initiated by the user; tracks status (
PENDING,PROCESSING,COMPLETED,FAILED) and itsSourceMaterials. - SourceMaterial -> each uploaded file, its storage path under
apps/api/uploads, and AI output pointers (output_artifacts). - SpeakerAttributedSegment -> diarized sentences persisted for later review.
- JobStageLog -> fine-grained pipeline telemetry ready for UIs or audits.
Typical flow:
- User creates a Workspace and optional Subjects from the sidebar in the web app.
- POST
/summary-jobswith files + optionalsubject_id. - FastAPI immediately stores uploads in
apps/api/uploads, creates DB rows, and schedulesrun_ai_processingas a background task. - The background worker calls
run_ai_pipeline, waits for files inapps/ai/output/<job_id>and backfills transcripts + summaries into the database. - UI polls
/summary-jobs/{id}until the job isCOMPLETED, then enables downloads (summary markdown, transcripts, artifacts directories).
Refer to docs/api/openapi.yaml for full request/response schemas.
apps/api/uploads/- raw user uploads, named with UUIDs per job.apps/ai/output/- AI artifacts grouped by sanitizedjob_id(summary.txt, speaker-attributed.txt, diarization.json, chunk audio, etc.).apps/projects/- placeholder for exported bundles or future collaboration features.logs/- uvicorn/stdout logs when running in--prod.tmp/- PID files, requirement hashes, PostgreSQL permission markers.summary/- manually curated summaries that the team wants to version-control.
| Purpose | Command |
|---|---|
| Bootstrap AI config/models | python -m apps.ai.bootstrap.manager |
| Run the FastAPI stack | python run.py |
| Background server with logging | python run.py --prod --keep-logs 10 |
| Direct pipeline dry-run | python -m apps.ai.main path/to/audio.wav |
| Open API docs locally | uvicorn apps.api.main:app --reload then visit /docs |
| Inspect queued jobs | sqlite3 apps/api/test.db (for local-only smoke tests) or connect to PostgreSQL with psql |
ffmpegnot found: install it (brew install ffmpeg,choco install ffmpeg, or download from ffmpeg.org) and ensure it is onPATH.- Pyannote authorization errors: set
HUGGINGFACE_TOKENto a token that can accesspyannote/speaker-diarization-3.1. Restart the app so the pipeline reloads. - GPU OOM in Whisper/llama.cpp: the pipeline automatically retries on CPU, but you can lower model sizes in
apps/ai/ai.config.jsonand rerun the bootstrapper. - PostgreSQL permission denied: either provide
POSTGRES_PASSWORDsorun.pycan grant privileges, or manually run theGRANT/ALTER ROLEstatements. - Stale artifacts after deleting jobs: use the
/summary-jobs/{id}DELETE endpoint which also cleansapps/ai/outputand uploaded files; manual deletes may orphan files.
For deeper architectural context, check the diagrams under docs/architecture/*.png.