Skip to content

feat: add transcription billing via SQS events#141

Open
AnthonyRonning wants to merge 2 commits intomasterfrom
feat/transcription-billing
Open

feat: add transcription billing via SQS events#141
AnthonyRonning wants to merge 2 commits intomasterfrom
feat/transcription-billing

Conversation

@AnthonyRonning
Copy link
Contributor

@AnthonyRonning AnthonyRonning commented Feb 5, 2026

Adds audio transcription billing by publishing SQS events with:

  • event_type: "transcription"
  • audio_seconds: estimated duration based on file size and content type

Duration estimation lookup table:

  • MP3: ~20KB/s (160kbps average)
  • WAV: ~176KB/s (44.1kHz stereo 16-bit)
  • OGG/Opus/WebM: ~14KB/s
  • FLAC: ~88KB/s
  • AAC/M4A: ~20KB/s
  • Unknown: defaults to MP3 rate

The billing server will handle the actual cost calculation based on audio_seconds.

Includes unit tests for the duration estimation function.


Open with Devin

Summary by CodeRabbit

  • New Features
    • Transcription usage tracking: automatic audio-duration estimation, provider attribution, and richer event details for billing/analytics.
  • Tests
    • Unit tests added for audio-duration estimation across formats and edge cases.
  • Chores
    • Appended new PCR snapshot entries to development and production history.

@coderabbitai
Copy link

coderabbitai bot commented Feb 5, 2026

Walkthrough

Appends PCR snapshot entries to dev and prod history JSON files, adds optional event_type and audio_seconds to UsageEvent, and implements audio-duration estimation plus publishing of transcription usage events with provider propagation and tests in the transcription flow.

Changes

Cohort / File(s) Summary
PCR History Files
pcrDevHistory.json, pcrProdHistory.json
Appended multiple PCR snapshot objects (each with PCR0/PCR1/PCR2, timestamp, signature).
Event Struct
src/sqs.rs
Added optional fields to public UsageEvent: event_type: Option<String> and audio_seconds: Option<i32> with serde defaults and conditional serialization.
Transcription Flow & Billing
src/web/openai.rs
Added estimate_audio_duration_seconds(file_size, content_type) and publish_transcription_usage_event(...); changed send_transcription_with_retries to return provider info; propagate provider through chunked/streaming flows; publish SQS usage events after transcription; added unit tests for duration estimator.

Sequence Diagram

sequenceDiagram
    participant Client
    participant TranscriptionHandler as Transcription<br/>Handler
    participant Estimator as Duration<br/>Estimator
    participant Provider as Transcription<br/>Provider
    participant SQS as SQS<br/>Publisher

    Client->>TranscriptionHandler: Upload/submit audio
    TranscriptionHandler->>Provider: Send audio for transcription (stream/chunk)
    Provider-->>TranscriptionHandler: Transcription result + provider_name
    TranscriptionHandler->>Estimator: estimate_audio_duration_seconds(file_size, content_type)
    Estimator-->>TranscriptionHandler: duration_seconds
    TranscriptionHandler->>SQS: publish_transcription_usage_event(UsageEvent{audio_seconds,event_type,provider})
    SQS-->>TranscriptionHandler: publish acknowledgment
    TranscriptionHandler-->>Client: return transcription result (includes provider)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I nibbled timestamps, counted each byte,

Measured seconds by audio light,
Pushed a UsageEvent on fluffy feet,
SQS hummed softly — billing complete,
Hop, hop — provider and logs all right!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add transcription billing via SQS events' accurately and concisely summarizes the main change: implementing transcription billing through SQS event publishing. It directly aligns with the core objective and changes across all modified files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/transcription-billing

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 4 additional findings in Devin Review.

Open in Devin Review

@greptile-apps
Copy link

greptile-apps bot commented Feb 5, 2026

Greptile Overview

Greptile Summary

This PR implements transcription billing by publishing SQS events with audio duration estimates. The implementation:

  • Adds event_type and audio_seconds fields to UsageEvent struct in src/sqs.rs:43-46
  • Implements estimate_audio_duration_seconds() function in src/web/openai.rs:43-61 with format-specific bitrate assumptions
  • Updates send_transcription_with_retries() in src/web/openai.rs:1038 to return both response and provider name
  • Publishes transcription usage events via publish_transcription_usage_event() in src/web/openai.rs:943-986
  • Includes unit tests for the duration estimation function in src/web/openai.rs:1912-1953
  • Updates PCR hashes for dev and prod environments

The duration estimation uses reasonable bitrate averages for common audio formats (MP3: 20KB/s, WAV: 176KB/s, OGG: 14KB/s, FLAC: 88KB/s, AAC: 20KB/s). The billing server will handle cost calculation based on the estimated audio_seconds.

Key observation: The implementation tracks only the first provider used when multiple chunks are processed via different providers (lines 1292-1300). This limitation was already noted in previous review threads.

Confidence Score: 4/5

  • safe to merge with minor billing accuracy limitations in multi-chunk scenarios
  • implementation is sound with good test coverage for duration estimation, proper error handling, and follows existing patterns. provider tracking limitation in multi-chunk scenarios (already flagged in previous threads) affects billing accuracy but doesn't break functionality
  • no files require special attention - PCR updates are routine, core logic in src/web/openai.rs has good test coverage

Important Files Changed

Filename Overview
src/sqs.rs added optional event_type and audio_seconds fields to UsageEvent struct for transcription billing support
src/web/openai.rs implemented transcription billing with duration estimation function, updated provider tracking in retry logic, and published usage events

Sequence Diagram

sequenceDiagram
    participant Client
    participant OpenAI Handler
    participant AudioSplitter
    participant Retry Logic
    participant Provider (Tinfoil/Continuum)
    participant SQS Publisher
    participant Billing Server

    Client->>OpenAI Handler: POST /v1/audio/transcriptions
    OpenAI Handler->>OpenAI Handler: decode base64 audio
    OpenAI Handler->>OpenAI Handler: validate file size
    OpenAI Handler->>AudioSplitter: split audio into chunks
    AudioSplitter-->>OpenAI Handler: return chunks

    par Process chunks in parallel
        OpenAI Handler->>Retry Logic: send_transcription_with_retries(chunk 0)
        Retry Logic->>Provider (Tinfoil/Continuum): try primary provider
        Provider (Tinfoil/Continuum)-->>Retry Logic: response
        Retry Logic-->>OpenAI Handler: (response, provider_name)
    and
        OpenAI Handler->>Retry Logic: send_transcription_with_retries(chunk 1)
        Retry Logic->>Provider (Tinfoil/Continuum): try primary/fallback
        Provider (Tinfoil/Continuum)-->>Retry Logic: response
        Retry Logic-->>OpenAI Handler: (response, provider_name)
    end

    OpenAI Handler->>OpenAI Handler: merge transcription results
    OpenAI Handler->>OpenAI Handler: estimate_audio_duration_seconds()
    
    OpenAI Handler->>SQS Publisher: publish_transcription_usage_event()
    Note over SQS Publisher: event_type: "transcription"<br/>audio_seconds: estimated duration<br/>provider_name: first successful provider
    
    SQS Publisher->>Billing Server: send UsageEvent to SQS queue
    
    OpenAI Handler->>Client: return encrypted transcription
Loading

@AnthonyRonning AnthonyRonning force-pushed the feat/transcription-billing branch from 97d55df to eaff978 Compare February 5, 2026 05:55
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

estimated_cost: BigDecimal::from(0),
chat_time: Utc::now(),
is_api_request,
provider_name: "continuum".to_string(),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

provider_name is hardcoded to "continuum" but transcriptions can be fulfilled by either Tinfoil or Continuum providers (see send_transcription_with_retries). Consider tracking which provider actually fulfilled the request, similar to how chat completions track successful_provider (line 633)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/web/openai.rs
Line: 968:968

Comment:
`provider_name` is hardcoded to "continuum" but transcriptions can be fulfilled by either Tinfoil or Continuum providers (see `send_transcription_with_retries`). Consider tracking which provider actually fulfilled the request, similar to how chat completions track `successful_provider` (line 633)

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Adds audio duration estimation based on file size and content type,
then publishes SQS events with event_type='transcription' and
audio_seconds for the billing server to process.

- Tracks actual provider (tinfoil/continuum) that fulfilled request
- Publishes asynchronously via tokio::spawn (non-blocking)
- Rounds up to nearest second

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
@AnthonyRonning AnthonyRonning force-pushed the feat/transcription-billing branch from eaff978 to ab17c23 Compare February 5, 2026 17:29
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/web/openai.rs (1)

1271-1285: ⚠️ Potential issue | 🟡 Minor

Fix formatting to pass CI.

The pipeline indicates cargo fmt check failed in this region. Run cargo fmt --all to fix the formatting issues.

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +1292 to +1300
let mut successful_provider = String::new();
for result in results {
match result {
Ok(r) => successful_results.push(r),
Ok((index, value, provider)) => {
successful_results.push((index, value));
// Track the provider (use the first one, or could use the last)
if successful_provider.is_empty() {
successful_provider = provider;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when multiple chunks succeed via different providers (e.g., chunk 0 via Tinfoil, chunk 1 via Continuum), only the first provider is tracked for billing. consider tracking all providers or using the most common one

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/web/openai.rs
Line: 1292:1300

Comment:
when multiple chunks succeed via different providers (e.g., chunk 0 via Tinfoil, chunk 1 via Continuum), only the first provider is tracked for billing. consider tracking all providers or using the most common one

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +43 to +61
fn estimate_audio_duration_seconds(file_size: usize, content_type: &str) -> f64 {
// Bytes per second for common audio formats (approximate averages)
let bytes_per_second: f64 = match content_type.to_lowercase().as_str() {
// MP3: typically 128-192kbps, use ~160kbps = 20KB/s
"audio/mpeg" | "audio/mp3" => 20_000.0,
// WAV: 44.1kHz, 16-bit stereo = 176.4KB/s
"audio/wav" | "audio/x-wav" | "audio/wave" => 176_400.0,
// OGG/Opus/WebM: typically ~96-128kbps = ~12-16KB/s, use 14KB/s
"audio/ogg" | "audio/opus" | "audio/webm" => 14_000.0,
// FLAC: lossless, roughly half of WAV = ~88KB/s
"audio/flac" | "audio/x-flac" => 88_000.0,
// AAC/M4A: similar to MP3, ~160kbps = 20KB/s
"audio/aac" | "audio/m4a" | "audio/mp4" | "audio/x-m4a" => 20_000.0,
// Default: assume MP3-like compression
_ => 20_000.0,
};

file_size as f64 / bytes_per_second
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider adding test case for zero or very small file sizes to ensure duration estimation handles edge cases gracefully

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/web/openai.rs
Line: 43:61

Comment:
consider adding test case for zero or very small file sizes to ensure duration estimation handles edge cases gracefully

How can I resolve this? If you propose a fix, please make it concise.

@AnthonyRonning AnthonyRonning force-pushed the feat/transcription-billing branch from ab17c23 to 61bd304 Compare February 5, 2026 17:34
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant