Bodhi Platform
Overview

Event Logging & the Data Platform

How every ASR and TTS request is recorded in one analytical store (ClickHouse), and how that same store grows into the LC-review and finetuning platform. The idea and the overview logic — not an implementation spec.

ScopeThe ASR event path is the go-live requirement. TTS is included because it currently records nothing and needs the same treatment — but its rollout is planned once the ASR pipeline is finalised.

1Why this exists

Today the picture is broken:

So we have no reliable, queryable record of what the models actually produced — which is exactly what LC and the AI team need to find issues and finetune.

The fix: every ASR and TTS request emits one immutable event into ClickHouse — a columnar store built for huge append-only volume, cheap compression, and fast slice-and-dice. Billing stays with dwarpal; LC labels come later in their own store. ClickHouse is only the event lake.

2The one mental model

   ASR / TTS request                                          later
 ┌──────────────────┐    emit event   ┌───────────────┐   ┌──────────────────────┐
 │ bodhi-go-server  ├────────────────►│  ClickHouse   │──►│ LC dashboard         │
 │ bodhi-tts-server │  (via a queue)  │  event lake   │   │ (find issues)        │
 └──────────────────┘                 │ asr_events    │   ├──────────────────────┤
        │                             │ tts_events    │   │ Finetune dataset     │
        │ report usage                └───────────────┘   │ builder (AI team)    │
        ▼                                    ▲             └──────────────────────┘
   dwarpal (billing) ── platform DB          │  join on transaction_id
                                    ┌─────────┴─────────┐
                                    │ bodhi-lc Postgres │  ← LC scores + human
                                    │ (mutable labels)  │     corrections (later)
                                    └───────────────────┘

3Core principles (what makes it reliable)

  1. Emit through a durable queue, not a bare goroutine. Producer → Azure Event Hubs (Kafka API) → ClickHouse Kafka-engine table → materialized view → MergeTree. No custom consumer, batched inserts, replayable. (Lighter alt: ClickHouse async_insert with an app-side disk buffer.)
  2. Logging is independent of everything else. The event is emitted even if audio save fails (audio_ref = null) and regardless of billing.
  3. Never swallow errors. A failed emit increments a metric and alerts.
  4. Idempotent on transaction_id. Retries/replays don't double-count.
  5. Immutable only. Mutable things (LC scores, money) live elsewhere.
Go-live acceptance testDaily dwarpal usage reports == event count in ClickHouse. If they match, we know it's actually saving — the exact check the current code fails.

4What we store

asr_events — one row per transcription request

FieldNote
transaction_idthe join key (call id)
customer_id, api_key_idplatform ids
model_id, model_versionattribution — which model produced this (from the catalog)
language, usage_typestreaming / batch
call_durationseconds (denormalized; billing truth is dwarpal's)
audio_refpointer to audio in Blob (null if save failed)
transcriptmodel output text (or Blob pointer if very large)
quality signalsconfidence, endpoint_lag, silence — what lets LC rank "bad" calls
request_ts, response_ts, statustiming + success/error

tts_events — one row per synthesis request

FieldNote
transaction_idjoin key
customer_id, api_key_id
model_id, model_versionwhich TTS model/replica
language, voicevoice = speaker within a language
input_textwhat was asked (or Blob pointer)
char_count, audio_duration, sample_ratesize / output dims
audio_refsynthesized audio in Blob
quality flagsflagged mispronunciations, later MOS-style ratings
request_ts, response_ts, status

Engine sketch (both): MergeTree, PARTITION BY toYYYYMM(request_ts), ORDER BY (customer_id, model_id, request_ts), TTL for retention. Big payloads (audio, and optionally very long transcripts/text) live in Blob, referenced by *_ref.

What does NOT go in ClickHouse: LC scores / human corrections → mutable → bodhi-lc Postgres. Billing / ledger → dwarpal → platform DB.

5Later: the LC layer

Rough idea — to confirm with SaloniThis section is a first sketch of how the LC layer might work. The actual review workflow, fields, and tables should be worked out with Saloni and the LC team before anything is built.

The LC team reviews calls to find where ASR/TTS went wrong. That produces mutable, human-owned data — so it gets its own Postgres DB (bodhi-lc), keyed by transaction_id, joined to the ClickHouse event at read time:

The dashboard = "browse events from ClickHouse, filter to suspicious ones (low confidence, long lag, a given model_version/language/customer), open one, score/correct it → write to bodhi-lc."

6Later: the finetune platform

Building a training set becomes two queries joined on transaction_id:

  1. ClickHouse — find the cohort: "all hi-banking-v3 calls last month with confidence < X, for customer Y".
  2. bodhi-lc — pull the human corrections for those transactions.
  3. Export (audio_ref, model output, corrected reference, tags) → the AI team finetunes → ships a new model version via the model catalog.

That closes the loop with the model catalog:

  model catalog deploys a version
        → ASR/TTS emit events (ClickHouse)
        → LC finds its issues (bodhi-lc)
        → AI team finetunes
        → new version uploaded to the catalog → deploy   ↺

The catalog gives every event a precise model_id/model_version, so the platform can say "v3 regressed on Hindi numbers vs v2" — and target the finetune.

7Build order (rough)

StepWhatResult
1ClickHouse + Event Hubs + asr_events/tts_events (Kafka-engine + MV)A place to put events
2Durable producer in bodhi-go-server and bodhi-tts-server; dual-run + reconcileEvery request reliably recorded; go-live safe
3Drop the old streaming_logs write; backfill history if neededOff the old DB
4bodhi-lc Postgres + LC dashboardLC can find issues
5Dataset builder / exportAI team finetunes from real failures

Steps 1–3 are the go-live requirement. 4–5 are the platform that grows on top of the same events.

8Open decisions

  1. ClickHouse: managed (ClickHouse Cloud on Azure) vs self-hosted. Managed is faster to go-live.
  2. Queue: Event Hubs + Kafka-engine table vs app-side async_insert.
  3. Transcript / input_text: inline column vs Blob pointer. Can start inline, move later.
  4. Retention / TTL per table before volume grows.
One-line summary
Every ASR and TTS request emits one immutable event into ClickHouse (billing stays with dwarpal); that event lake is the queryable record we lack today, and it grows — joined by transaction_id — into the LC-review store and the finetuning dataset builder.