Skip to content

RE1-T115 Updating TTS and Twilio outbound voice flow#357

Merged
ucswift merged 2 commits intomasterfrom
develop
May 2, 2026
Merged

RE1-T115 Updating TTS and Twilio outbound voice flow#357
ucswift merged 2 commits intomasterfrom
develop

Conversation

@ucswift
Copy link
Copy Markdown
Member

@ucswift ucswift commented May 2, 2026

Summary by CodeRabbit

  • New Features

    • Added multi-digit menu selection support for outbound voice responses.
    • Introduced dispatch content playback before voice response options.
  • Improvements

    • Updated default text-to-speech voice for enhanced audio quality.
    • Refined voice prompt wording and punctuation for improved clarity.
    • Enhanced voice request handling with better variant voice support.

@request-info
Copy link
Copy Markdown

request-info Bot commented May 2, 2026

Thanks for opening this, but we'd appreciate a little more information. Could you update it with more details?

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 2, 2026

Warning

Rate limit exceeded

@ucswift has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 11 minutes and 48 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5e0b78c5-541c-4084-a54c-b3aed56d5cba

📥 Commits

Reviewing files that changed from the base of the PR and between f62b091 and d193f87.

📒 Files selected for processing (17)
  • Core/Resgrid.Model/Resgrid.Model.csproj
  • Core/Resgrid.Model/TwilioVoicePromptCatalog.cs
  • Core/Resgrid.Services/Resgrid.Services.csproj
  • Providers/Resgrid.Providers.Email/Resgrid.Providers.Email.csproj
  • Providers/Resgrid.Providers.Workflow/Resgrid.Providers.Workflow.csproj
  • Tests/Resgrid.Tests/Resgrid.Tests.csproj
  • Tests/Resgrid.Tests/Web/Services/TwilioControllerVoiceVerificationTests.cs
  • Tests/Resgrid.Tests/Web/Tts/S3StorageServiceTests.cs
  • Web/Resgrid.Web.Eventing/Resgrid.Web.Eventing.csproj
  • Web/Resgrid.Web.Services/Controllers/TwilioController.cs
  • Web/Resgrid.Web.Services/Controllers/TwilioProviderController.cs
  • Web/Resgrid.Web.Services/Resgrid.Web.Services.csproj
  • Web/Resgrid.Web.Services/Resgrid.Web.Services.xml
  • Web/Resgrid.Web.Tts/Services/S3StorageService.cs
  • Web/Resgrid.Web/Resgrid.Web.csproj
  • Workers/Resgrid.Workers.Framework/BatchProcessingQueueHandler.cs
  • Workers/Resgrid.Workers.Framework/QueueHandler.cs
📝 Walkthrough

Walkthrough

This PR migrates the default TTS voice from legacy F3 to Klatt6 variant across configuration and runtime code, refactors Twilio call flow to support dispatch-based outbound calling with new menu endpoints and prompts, and optimizes S3 storage uploads to buffer payloads with configurable SSL protocol selection.

Changes

Voice Configuration Migration

Layer / File(s) Summary
Data Shape & Defaults
Core/Resgrid.Model/EspeakVoiceCatalog.cs
"en-us+f3" voice removed from VoicesInternal; DefaultIdentifier changed from "en-us+f3" to "en-us". New GetBaseIdentifier() and updated TryGetOption() normalize voice identifiers by stripping +-suffixes before lookup.
Configuration
Core/Resgrid.Config/TtsConfig.cs, Web/Resgrid.Web.Tts/Configuration/TtsOptions.cs
DefaultVoice updated from "en-us+f3" to "en-us+klatt6" in both config/options classes.
Voice Normalization
Web/Resgrid.Web.Tts/Services/TtsService.cs
New NormalizeVoice() helper remaps legacy "en-us+f3" to configured default, applies configured variant suffix to base languages, and preserves explicit variants.
Infrastructure
Web/Resgrid.Web.Tts/Dockerfile, Web/Resgrid.Web.Tts/k8s/deployment.yaml
Docker image installs klatt6 voice data variant; k8s ConfigMap updates RESGRID__TtsConfig__DefaultVoice to en-us+klatt6.
Tests
Tests/Resgrid.Tests/Services/DepartmentSettingsServiceTtsLanguageTests.cs, Tests/Resgrid.Tests/Web/Tts/TtsAdminControllerTests.cs, Tests/Resgrid.Tests/Web/Tts/TtsServiceTests.cs
Test setup and expectations updated to use en-us+klatt6; new tests verify variant application to base languages and legacy F3 replacement behavior.

Twilio Dispatch Voice Call Flow

Layer / File(s) Summary
Prompt Content
Core/Resgrid.Model/TwilioVoicePromptCatalog.cs
Added new outbound dispatch prompts (OutboundDispatchMenu, OutboundResponseSelectionIntro, RepeatDispatchWithPound, GoBackToMainMenuWithPound). Updated existing prompt text for punctuation/wording consistency and "press pound" flow instructions. Updated prompt-generating method outputs to reflect revised phrasing.
Controller Endpoints
Web/Resgrid.Web.Services/Controllers/TwilioController.cs
Refactored VoiceCall to play dispatch via AppendDispatchPlaybackAsync() before 2-pass Gather menu. Simplified VoiceCallAction to route menu digits (1, 2) and invalid input. Added new VoiceCallRespond endpoint to handle digit responses (0=back, 1=scene, numeric=station). VoiceCallResponseOptions now routes to new VoiceCallRespond via finishOnKey="#". Extracted dispatch/station/prompt builders.
Provider Controller
Web/Resgrid.Web.Services/Controllers/TwilioProviderController.cs
Parallel refactoring of VoiceCall, VoiceCallAction, VoiceCallResponseOptions, and added VoiceCallRespond endpoint with equivalent dispatch playback and multi-digit station selection logic. Added AppendVoicePromptAsync(Gather ...) overload.
Tests
Tests/Resgrid.Tests/Web/Services/TwilioControllerVoiceVerificationTests.cs
Updated mock setup to support Gather elements and AppendPromptsAsync(). Stubbed TTS language lookup to return null. Added 7 new test cases exercising dispatch playback, outbound menu routing, multi-digit station/status selection with logging verification, and prompt punctuation validation.

S3 Storage Payload Buffering

Layer / File(s) Summary
Service Refactoring
Web/Resgrid.Web.Tts/Services/S3StorageService.cs
UploadAsync now reads input stream into byte[] payload once and passes buffered payload through all upload paths (SDK and presigned-URL fallback). Updated UploadWithSdkAsync and UploadWithPresignedUrlAsync signatures to accept byte[]. Added GetPresignedUrlProtocol() helper to set explicit HTTP/HTTPS protocol based on _options.UseSsl.
Tests
Tests/Resgrid.Tests/Web/Tts/S3StorageServiceTests.cs
Updated presigned-URL tests to expect HTTP (not HTTPS) and assert Protocol.HTTP via useSsl: false. Enhanced CreateService helper with useSsl parameter. Added new test verifying payload reuse when SDK disposes input stream during upload failure and fallback occurs.

Sequence Diagram(s)

sequenceDiagram
    participant Caller as Caller/PSTN
    participant Twilio as Twilio Gateway
    participant TwilioCtrl as TwilioController
    participant DispatchSvc as Dispatch Service
    participant S3 as S3 Storage
    participant TTS as TTS Service

    Caller->>Twilio: Incoming call
    Twilio->>TwilioCtrl: POST /api/Twilio/VoiceCall
    TwilioCtrl->>DispatchSvc: Get call & dispatch details
    alt Dispatch Audio Available
        DispatchSvc->>S3: Fetch dispatch audio
        S3-->>DispatchSvc: Audio URL
    else Geo-based Dispatch
        TwilioCtrl->>TTS: Synthesize dispatch prompt
        TTS-->>TwilioCtrl: Audio URL
    end
    TwilioCtrl-->>Twilio: TwiML (dispatch audio + Gather menu)
    Twilio-->>Caller: Play dispatch, collect digits
    Caller->>Twilio: Enter digit (1 or 2)
    Twilio->>TwilioCtrl: POST /api/Twilio/VoiceCallAction
    alt Digit = 1
        TwilioCtrl-->>Twilio: Redirect to VoiceCall
    else Digit = 2
        TwilioCtrl-->>Twilio: Redirect to VoiceCallResponseOptions
    end
    Twilio->>TwilioCtrl: POST /api/Twilio/VoiceCallResponseOptions
    TwilioCtrl-->>Twilio: TwiML (response options + Gather, finishOnKey=#)
    Twilio-->>Caller: Play options, collect digits with #
    Caller->>Twilio: Enter digit sequence + #
    Twilio->>TwilioCtrl: POST /api/Twilio/VoiceCallRespond
    alt Digit = 0
        TwilioCtrl-->>Twilio: Redirect to VoiceCall
    else Digit = 1
        TwilioCtrl->>DispatchSvc: Log RespondingToScene
        TwilioCtrl-->>Twilio: TwiML (scene response, hangup)
    else Digit > 1
        TwilioCtrl->>DispatchSvc: Log RespondingToStation(index)
        TwilioCtrl-->>Twilio: TwiML (station response, hangup)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

This PR spans three independent subsystems with significant structural changes: the voice configuration migration requires careful validation of normalization logic and config propagation across layers; the Twilio controller refactoring introduces new endpoints and multi-layered call routing that demands careful sequence validation; and the S3 storage buffering refactor involves stream lifecycle and retry semantics. The changes are heterogeneous across configuration, service logic, controller endpoints, and tests, requiring separate reasoning for each area.

Possibly related PRs

  • RE1-T115 Changing default TTS voice #356: Updates the same TTS defaults (TtsConfig.DefaultVoice, TtsOptions.DefaultVoice) and voice catalog entries that are migrated in this PR.
  • Develop #332: Modifies Twilio voice call flow endpoints and outbound call handling, directly overlapping with the controller refactoring in this PR.
  • RE1-T115 S3 api fix #352: Updates S3 storage service buffering and presigned-URL fallback behavior, matching the payload buffering and SSL protocol changes in this PR.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: updating TTS voice configuration from f3 to klatt6 and refactoring Twilio outbound voice response flow.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 11 minutes and 48 seconds.

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

Copy link
Copy Markdown
Contributor

@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: 4

🧹 Nitpick comments (1)
Tests/Resgrid.Tests/Web/Tts/TtsServiceTests.cs (1)

108-154: 💤 Low value

New normalization tests: consider asserting result.Speed for full result coverage.

Both new tests verify result.Voice but skip result.Speed.Should().Be(175). The first cache-hit test (lines 58–63) asserts both. Since these tests go through the same result-construction code path, adding the speed assertion would close a small gap and keep the pattern consistent across all cache-hit tests.

✏️ Suggested additions
// generate_async_should_apply_configured_klatt_variant_to_requested_language
  result.Cached.Should().BeTrue();
  result.Voice.Should().Be("fr+klatt6");
+ result.Speed.Should().Be(175);
  result.Url.Should().Be(cachedUri.ToString());
// generate_async_should_replace_legacy_f3_voice_with_configured_klatt_variant
  result.Cached.Should().BeTrue();
  result.Voice.Should().Be("en-us+klatt6");
+ result.Speed.Should().Be(175);
  result.Url.Should().Be(cachedUri.ToString());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Tests/Resgrid.Tests/Web/Tts/TtsServiceTests.cs` around lines 108 - 154, Add
an assertion to both tests to check the returned speech speed: after calling
_service.GenerateAsync in
generate_async_should_apply_configured_klatt_variant_to_requested_language and
generate_async_should_replace_legacy_f3_voice_with_configured_klatt_variant,
assert result.Speed.Should().Be(175); this mirrors the existing cache-hit test
pattern and ensures the TtsRequest/GenerateAsync result includes the expected
Speed value alongside Cached, Voice, and Url validations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Core/Resgrid.Model/TwilioVoicePromptCatalog.cs`:
- Around line 30-31: The prompts InvalidStatusSelection and NoStatusSelection
currently say "Goodbye." but the handler InboundVoiceActionStatus still
redirects callers back to the main menu; update to make behavior & wording
consistent by either (A) editing the constants InvalidStatusSelection and
NoStatusSelection to say they are "returning to the main menu" (matching the
redirect flow) or (B) change InboundVoiceActionStatus to hang up after playing
those prompts instead of redirecting; pick one approach and update the
corresponding symbol(s) (the two constants or the InboundVoiceActionStatus
handler logic) so wording and flow match.

In `@Web/Resgrid.Web.Services/Controllers/TwilioController.cs`:
- Around line 1100-1107: BuildDispatchPrompt currently returns a dispatch prompt
string without terminal punctuation, causing TTS to run together; update the
method (BuildDispatchPrompt) to append a period (or appropriate sentence-ending
punctuation) to both return branches so the prompt ends cleanly, and apply the
same change to the duplicate helper in TwilioProviderController (the
corresponding BuildDispatchPrompt there) so both code paths produce a
sentence-terminated prompt for TTS.
- Around line 510-512: The three newly added public Twilio callback actions
(including VoiceCallAction and the two other methods added at the ranges
referenced) are missing the [ValidateRequest] attribute; add [ValidateRequest]
above each of these controller action declarations (e.g., above VoiceCallAction
and the other two new Twilio callback methods) so Twilio request signature
validation is enforced before executing logic like SetUserActionAsync(). Ensure
the attribute sits on the same method declarations where [HttpGet(...)] and
[Produces(...)] are declared.

In `@Web/Resgrid.Web.Tts/Services/S3StorageService.cs`:
- Around line 431-434: GetPresignedUrlProtocol currently forces HTTPS/HTTP based
only on _options.UseSsl; instead detect if _options.Endpoint is an absolute URI
and, if so, use its scheme (https => Protocol.HTTPS, http => Protocol.HTTP);
otherwise fall back to _options.UseSsl. Update the GetPresignedUrlProtocol
method to parse _options.Endpoint (e.g., with Uri.TryCreate) and return the
protocol based on that parsed Uri.Scheme when absolute, referencing
GetPresignedUrlProtocol and _options.Endpoint/_options.UseSsl so presigned URLs
honor an explicit endpoint URI scheme.

---

Nitpick comments:
In `@Tests/Resgrid.Tests/Web/Tts/TtsServiceTests.cs`:
- Around line 108-154: Add an assertion to both tests to check the returned
speech speed: after calling _service.GenerateAsync in
generate_async_should_apply_configured_klatt_variant_to_requested_language and
generate_async_should_replace_legacy_f3_voice_with_configured_klatt_variant,
assert result.Speed.Should().Be(175); this mirrors the existing cache-hit test
pattern and ensures the TtsRequest/GenerateAsync result includes the expected
Speed value alongside Cached, Voice, and Url validations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: faf2e734-cad8-4e02-aeef-ffebd0682129

📥 Commits

Reviewing files that changed from the base of the PR and between a8601ab and f62b091.

📒 Files selected for processing (15)
  • Core/Resgrid.Config/TtsConfig.cs
  • Core/Resgrid.Model/EspeakVoiceCatalog.cs
  • Core/Resgrid.Model/TwilioVoicePromptCatalog.cs
  • Tests/Resgrid.Tests/Services/DepartmentSettingsServiceTtsLanguageTests.cs
  • Tests/Resgrid.Tests/Web/Services/TwilioControllerVoiceVerificationTests.cs
  • Tests/Resgrid.Tests/Web/Tts/S3StorageServiceTests.cs
  • Tests/Resgrid.Tests/Web/Tts/TtsAdminControllerTests.cs
  • Tests/Resgrid.Tests/Web/Tts/TtsServiceTests.cs
  • Web/Resgrid.Web.Services/Controllers/TwilioController.cs
  • Web/Resgrid.Web.Services/Controllers/TwilioProviderController.cs
  • Web/Resgrid.Web.Tts/Configuration/TtsOptions.cs
  • Web/Resgrid.Web.Tts/Dockerfile
  • Web/Resgrid.Web.Tts/Services/S3StorageService.cs
  • Web/Resgrid.Web.Tts/Services/TtsService.cs
  • Web/Resgrid.Web.Tts/k8s/deployment.yaml

Comment thread Core/Resgrid.Model/TwilioVoicePromptCatalog.cs Outdated
Comment thread Web/Resgrid.Web.Services/Controllers/TwilioController.cs
Comment thread Web/Resgrid.Web.Services/Controllers/TwilioController.cs Outdated
Comment thread Web/Resgrid.Web.Tts/Services/S3StorageService.cs
@ucswift
Copy link
Copy Markdown
Member Author

ucswift commented May 2, 2026

Approve

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

This PR is approved.

@ucswift ucswift merged commit 68625b6 into master May 2, 2026
18 of 19 checks passed
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