Extract report decision helpers into partial service

This commit is contained in:
Jacob Dubin
2026-05-21 07:39:52 -05:00
parent 2bf686f791
commit a0d6102399
2 changed files with 297 additions and 286 deletions

View File

@@ -0,0 +1,295 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Jibo.Cloud.Application.Abstractions;
using Jibo.Cloud.Domain.Models;
using Jibo.Runtime.Abstractions;
namespace Jibo.Cloud.Application.Services;
public sealed partial class JiboInteractionService
{
private async Task<JiboInteractionDecision> BuildWeatherReportDecisionAsync(
TurnContext turn,
string transcript,
CancellationToken cancellationToken)
{
var referenceLocalTime = TryResolveReferenceLocalTime(turn);
var catalog = await contentCache.GetCatalogAsync(cancellationToken);
var normalizedTranscript = NormalizeCommandPhrase(transcript);
var locationQuery = TryResolveWeatherLocationQuery(transcript);
var weatherDate = ResolveWeatherDateEntity(turn, transcript, normalizedTranscript, referenceLocalTime);
var isRangeForecastRequest = IsRangeForecastRequest(normalizedTranscript);
var isOpenEndedForecastRequest = IsOpenEndedForecastRequest(
normalizedTranscript,
weatherDate,
isRangeForecastRequest,
locationQuery);
if (ShouldDefaultForecastToTomorrow(
normalizedTranscript,
weatherDate,
isRangeForecastRequest,
isOpenEndedForecastRequest))
weatherDate = new WeatherDateEntity("tomorrow", 1, "Tomorrow");
if (weatherReportProvider is null)
return new JiboInteractionDecision(
"weather",
ChooseWeatherServiceDownReply(catalog));
var weatherCoordinates = string.IsNullOrWhiteSpace(locationQuery)
? TryResolveWeatherCoordinates(turn)
: null;
var useCelsius = ShouldUseCelsius(turn, transcript);
var isNextWeekForecast = IsNextWeekForecastRequest(normalizedTranscript, isRangeForecastRequest);
var isThisWeekForecast = IsThisWeekForecastRequest(normalizedTranscript, isRangeForecastRequest);
if (isNextWeekForecast || isThisWeekForecast || isOpenEndedForecastRequest)
{
const int rangeStartOffset = 1;
var rangeEndOffset = isThisWeekForecast
? ResolveThisWeekForecastEndOffset(referenceLocalTime)
: MaxWeatherForecastDayOffset;
var weeklySnapshots = new List<(int DayOffset, WeatherReportSnapshot Snapshot)>();
for (var offset = rangeStartOffset; offset <= rangeEndOffset; offset += 1)
{
WeatherReportSnapshot? weeklySnapshot;
try
{
weeklySnapshot = await weatherReportProvider.GetReportAsync(
new WeatherReportRequest(
locationQuery,
weatherCoordinates?.Latitude,
weatherCoordinates?.Longitude,
offset == 1,
useCelsius,
offset),
cancellationToken);
}
catch (Exception) when (!cancellationToken.IsCancellationRequested)
{
weeklySnapshot = null;
}
if (weeklySnapshot is not null) weeklySnapshots.Add((offset, weeklySnapshot));
}
if (weeklySnapshots.Count == 0)
return new JiboInteractionDecision(
"weather",
"I couldn't fetch the weather right now. Please try again.");
var weeklySegments = BuildWeeklyForecastCardSegments(weeklySnapshots, referenceLocalTime);
var weeklySpokenReply = BuildWeeklyForecastSpokenReply(
weeklySegments,
weeklySnapshots[0].Snapshot.LocationName,
weeklySnapshots[0].Snapshot.UseCelsius,
isThisWeekForecast);
var weeklyWeatherPayload = BuildWeeklyWeatherSkillPayload(
weeklySpokenReply,
weeklySnapshots[0].Snapshot,
weeklySegments,
referenceLocalTime);
AddWeatherRequestDiagnostics(
weeklyWeatherPayload,
transcript,
normalizedTranscript,
locationQuery,
weatherDate,
isRangeForecastRequest,
isThisWeekForecast,
isNextWeekForecast);
return new JiboInteractionDecision(
"weather",
weeklySpokenReply,
"chitchat-skill",
weeklyWeatherPayload);
}
if (weatherDate.ForecastDayOffset > MaxWeatherForecastDayOffset)
return new JiboInteractionDecision(
"weather",
$"I can forecast up to {MaxWeatherForecastDayOffset} days ahead. Try tomorrow or another day this week.");
WeatherReportSnapshot? snapshot;
try
{
snapshot = await weatherReportProvider.GetReportAsync(
new WeatherReportRequest(
locationQuery,
weatherCoordinates?.Latitude,
weatherCoordinates?.Longitude,
string.Equals(weatherDate.DateEntity, "tomorrow", StringComparison.OrdinalIgnoreCase),
useCelsius,
weatherDate.ForecastDayOffset),
cancellationToken);
}
catch (Exception) when (!cancellationToken.IsCancellationRequested)
{
snapshot = null;
}
if (snapshot is null)
return new JiboInteractionDecision(
"weather",
ChooseWeatherServiceDownReply(catalog));
var spokenReply = BuildWeatherSpokenReply(snapshot, weatherDate, catalog);
var weatherPayload = BuildWeatherSkillPayload(spokenReply, snapshot, referenceLocalTime);
AddWeatherRequestDiagnostics(
weatherPayload,
transcript,
normalizedTranscript,
locationQuery,
weatherDate,
isRangeForecastRequest,
isThisWeekForecast,
isNextWeekForecast);
return new JiboInteractionDecision(
"weather",
spokenReply,
"chitchat-skill",
weatherPayload);
}
private async Task<JiboInteractionDecision> BuildCommuteReportDecisionAsync(
TurnContext turn,
CancellationToken cancellationToken)
{
var catalog = await contentCache.GetCatalogAsync(cancellationToken);
if (commuteReportProvider is null)
return new JiboInteractionDecision(
"commute",
ChooseCommuteServiceDownReply(catalog));
CommuteReportSnapshot? snapshot;
try
{
snapshot = await commuteReportProvider.GetReportAsync(turn, cancellationToken);
}
catch (Exception) when (!cancellationToken.IsCancellationRequested)
{
snapshot = null;
}
if (snapshot is null)
return new JiboInteractionDecision(
"commute",
ChooseCommuteServiceDownReply(catalog));
if (snapshot.RequiresSetup)
return new JiboInteractionDecision(
"commute_setup",
ChooseCommuteAppSetupReply(catalog));
return new JiboInteractionDecision(
"commute",
BuildCommuteSpokenReply(snapshot, catalog));
}
private async Task<JiboInteractionDecision> BuildCalendarReportDecisionAsync(
TurnContext turn,
CancellationToken cancellationToken)
{
var catalog = await contentCache.GetCatalogAsync(cancellationToken);
if (calendarReportProvider is null)
return new JiboInteractionDecision(
"calendar",
ChooseCalendarServiceDownReply(catalog));
CalendarReportSnapshot? snapshot;
try
{
snapshot = await calendarReportProvider.GetReportAsync(turn, cancellationToken);
}
catch (Exception) when (!cancellationToken.IsCancellationRequested)
{
snapshot = null;
}
if (snapshot is null)
return new JiboInteractionDecision(
"calendar",
ChooseCalendarServiceDownReply(catalog));
return new JiboInteractionDecision(
"calendar",
BuildCalendarSpokenReply(snapshot, catalog));
}
private async Task<JiboInteractionDecision> BuildNewsDecisionAsync(
TurnContext turn,
string transcript,
JiboExperienceCatalog catalog,
CancellationToken cancellationToken)
{
var preferredCategories = ResolvePreferredNewsCategories(turn, transcript);
var requestedHeadlineCount = MaxNewsHeadlines;
if (newsBriefingProvider is not null)
try
{
var snapshot = await newsBriefingProvider.GetBriefingAsync(
new NewsBriefingRequest(preferredCategories, requestedHeadlineCount),
cancellationToken);
if (snapshot?.Headlines.Count > 0)
return BuildProviderNewsDecision(
snapshot,
catalog,
preferredCategories,
requestedHeadlineCount);
var providerStatus = ResolveNewsProviderStatus(snapshot);
var providerMessage = snapshot?.ProviderMessage;
var providerEndpoint = snapshot?.ProviderEndpoint;
var providerHttpStatusCode = snapshot?.ProviderHttpStatusCode;
var providerErrorCode = snapshot?.ProviderErrorCode;
var fallbackBriefingWhenEmpty = randomizer.Choose(catalog.NewsBriefings);
return BuildNewsDecision(
fallbackBriefingWhenEmpty,
null,
preferredCategories.Count > 0 ? preferredCategories : null,
null,
BuildNewsProviderDiagnostics(
providerStatus,
preferredCategories,
requestedHeadlineCount,
snapshot?.Headlines.Count ?? 0,
providerMessage,
providerHttpStatusCode,
providerEndpoint,
providerErrorCode));
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch
{
// Provider failures should never block baseline news behavior.
var fallbackBriefingOnError = randomizer.Choose(catalog.NewsBriefings);
return BuildNewsDecision(
fallbackBriefingOnError,
null,
preferredCategories.Count > 0 ? preferredCategories : null,
null,
BuildNewsProviderDiagnostics(
"provider_exception",
preferredCategories,
requestedHeadlineCount));
}
var fallbackBriefing = randomizer.Choose(catalog.NewsBriefings);
return BuildNewsDecision(
fallbackBriefing,
null,
preferredCategories.Count > 0 ? preferredCategories : null,
null,
BuildNewsProviderDiagnostics(
"provider_unavailable",
preferredCategories,
requestedHeadlineCount));
}
}

View File

@@ -7,7 +7,7 @@ using Jibo.Runtime.Abstractions;
namespace Jibo.Cloud.Application.Services; namespace Jibo.Cloud.Application.Services;
public sealed class JiboInteractionService( public sealed partial class JiboInteractionService(
JiboExperienceContentCache contentCache, JiboExperienceContentCache contentCache,
IJiboRandomizer randomizer, IJiboRandomizer randomizer,
IPersonalMemoryStore personalMemoryStore, IPersonalMemoryStore personalMemoryStore,
@@ -1353,215 +1353,6 @@ public sealed class JiboInteractionService(
ContextUpdates: ScriptedResponseDecisionBuilder.BuildScriptedResponseContextUpdates()); ContextUpdates: ScriptedResponseDecisionBuilder.BuildScriptedResponseContextUpdates());
} }
private async Task<JiboInteractionDecision> BuildWeatherReportDecisionAsync(
TurnContext turn,
string transcript,
CancellationToken cancellationToken)
{
var referenceLocalTime = TryResolveReferenceLocalTime(turn);
var catalog = await contentCache.GetCatalogAsync(cancellationToken);
var normalizedTranscript = NormalizeCommandPhrase(transcript);
var locationQuery = TryResolveWeatherLocationQuery(transcript);
var weatherDate = ResolveWeatherDateEntity(turn, transcript, normalizedTranscript, referenceLocalTime);
var isRangeForecastRequest = IsRangeForecastRequest(normalizedTranscript);
var isOpenEndedForecastRequest = IsOpenEndedForecastRequest(
normalizedTranscript,
weatherDate,
isRangeForecastRequest,
locationQuery);
if (ShouldDefaultForecastToTomorrow(
normalizedTranscript,
weatherDate,
isRangeForecastRequest,
isOpenEndedForecastRequest))
weatherDate = new WeatherDateEntity("tomorrow", 1, "Tomorrow");
if (weatherReportProvider is null)
return new JiboInteractionDecision(
"weather",
ChooseWeatherServiceDownReply(catalog));
var weatherCoordinates = string.IsNullOrWhiteSpace(locationQuery)
? TryResolveWeatherCoordinates(turn)
: null;
var useCelsius = ShouldUseCelsius(turn, transcript);
var isNextWeekForecast = IsNextWeekForecastRequest(normalizedTranscript, isRangeForecastRequest);
var isThisWeekForecast = IsThisWeekForecastRequest(normalizedTranscript, isRangeForecastRequest);
if (isNextWeekForecast || isThisWeekForecast || isOpenEndedForecastRequest)
{
const int rangeStartOffset = 1;
var rangeEndOffset = isThisWeekForecast
? ResolveThisWeekForecastEndOffset(referenceLocalTime)
: MaxWeatherForecastDayOffset;
var weeklySnapshots = new List<(int DayOffset, WeatherReportSnapshot Snapshot)>();
for (var offset = rangeStartOffset; offset <= rangeEndOffset; offset += 1)
{
WeatherReportSnapshot? weeklySnapshot;
try
{
weeklySnapshot = await weatherReportProvider.GetReportAsync(
new WeatherReportRequest(
locationQuery,
weatherCoordinates?.Latitude,
weatherCoordinates?.Longitude,
offset == 1,
useCelsius,
offset),
cancellationToken);
}
catch (Exception) when (!cancellationToken.IsCancellationRequested)
{
weeklySnapshot = null;
}
if (weeklySnapshot is not null) weeklySnapshots.Add((offset, weeklySnapshot));
}
if (weeklySnapshots.Count == 0)
return new JiboInteractionDecision(
"weather",
"I couldn't fetch the weather right now. Please try again.");
var weeklySegments = BuildWeeklyForecastCardSegments(weeklySnapshots, referenceLocalTime);
var weeklySpokenReply = BuildWeeklyForecastSpokenReply(
weeklySegments,
weeklySnapshots[0].Snapshot.LocationName,
weeklySnapshots[0].Snapshot.UseCelsius,
isThisWeekForecast);
var weeklyWeatherPayload = BuildWeeklyWeatherSkillPayload(
weeklySpokenReply,
weeklySnapshots[0].Snapshot,
weeklySegments,
referenceLocalTime);
AddWeatherRequestDiagnostics(
weeklyWeatherPayload,
transcript,
normalizedTranscript,
locationQuery,
weatherDate,
isRangeForecastRequest,
isThisWeekForecast,
isNextWeekForecast);
return new JiboInteractionDecision(
"weather",
weeklySpokenReply,
"chitchat-skill",
weeklyWeatherPayload);
}
if (weatherDate.ForecastDayOffset > MaxWeatherForecastDayOffset)
return new JiboInteractionDecision(
"weather",
$"I can forecast up to {MaxWeatherForecastDayOffset} days ahead. Try tomorrow or another day this week.");
WeatherReportSnapshot? snapshot;
try
{
snapshot = await weatherReportProvider.GetReportAsync(
new WeatherReportRequest(
locationQuery,
weatherCoordinates?.Latitude,
weatherCoordinates?.Longitude,
string.Equals(weatherDate.DateEntity, "tomorrow", StringComparison.OrdinalIgnoreCase),
useCelsius,
weatherDate.ForecastDayOffset),
cancellationToken);
}
catch (Exception) when (!cancellationToken.IsCancellationRequested)
{
snapshot = null;
}
if (snapshot is null)
return new JiboInteractionDecision(
"weather",
ChooseWeatherServiceDownReply(catalog));
var spokenReply = BuildWeatherSpokenReply(snapshot, weatherDate, catalog);
var weatherPayload = BuildWeatherSkillPayload(spokenReply, snapshot, referenceLocalTime);
AddWeatherRequestDiagnostics(
weatherPayload,
transcript,
normalizedTranscript,
locationQuery,
weatherDate,
isRangeForecastRequest,
isThisWeekForecast,
isNextWeekForecast);
return new JiboInteractionDecision(
"weather",
spokenReply,
"chitchat-skill",
weatherPayload);
}
private async Task<JiboInteractionDecision> BuildCommuteReportDecisionAsync(
TurnContext turn,
CancellationToken cancellationToken)
{
var catalog = await contentCache.GetCatalogAsync(cancellationToken);
if (commuteReportProvider is null)
return new JiboInteractionDecision(
"commute",
ChooseCommuteServiceDownReply(catalog));
CommuteReportSnapshot? snapshot;
try
{
snapshot = await commuteReportProvider.GetReportAsync(turn, cancellationToken);
}
catch (Exception) when (!cancellationToken.IsCancellationRequested)
{
snapshot = null;
}
if (snapshot is null)
return new JiboInteractionDecision(
"commute",
ChooseCommuteServiceDownReply(catalog));
if (snapshot.RequiresSetup)
return new JiboInteractionDecision(
"commute_setup",
ChooseCommuteAppSetupReply(catalog));
return new JiboInteractionDecision(
"commute",
BuildCommuteSpokenReply(snapshot, catalog));
}
private async Task<JiboInteractionDecision> BuildCalendarReportDecisionAsync(
TurnContext turn,
CancellationToken cancellationToken)
{
var catalog = await contentCache.GetCatalogAsync(cancellationToken);
if (calendarReportProvider is null)
return new JiboInteractionDecision(
"calendar",
ChooseCalendarServiceDownReply(catalog));
CalendarReportSnapshot? snapshot;
try
{
snapshot = await calendarReportProvider.GetReportAsync(turn, cancellationToken);
}
catch (Exception) when (!cancellationToken.IsCancellationRequested)
{
snapshot = null;
}
if (snapshot is null)
return new JiboInteractionDecision(
"calendar",
ChooseCalendarServiceDownReply(catalog));
return new JiboInteractionDecision(
"calendar",
BuildCalendarSpokenReply(snapshot, catalog));
}
private static string BuildWeatherSpokenReply( private static string BuildWeatherSpokenReply(
WeatherReportSnapshot snapshot, WeatherReportSnapshot snapshot,
WeatherDateEntity weatherDate, WeatherDateEntity weatherDate,
@@ -2279,81 +2070,6 @@ public sealed class JiboInteractionService(
}); });
} }
private async Task<JiboInteractionDecision> BuildNewsDecisionAsync(
TurnContext turn,
string transcript,
JiboExperienceCatalog catalog,
CancellationToken cancellationToken)
{
var preferredCategories = ResolvePreferredNewsCategories(turn, transcript);
var requestedHeadlineCount = MaxNewsHeadlines;
if (newsBriefingProvider is not null)
try
{
var snapshot = await newsBriefingProvider.GetBriefingAsync(
new NewsBriefingRequest(preferredCategories, requestedHeadlineCount),
cancellationToken);
if (snapshot?.Headlines.Count > 0)
return BuildProviderNewsDecision(
snapshot,
catalog,
preferredCategories,
requestedHeadlineCount);
var providerStatus = ResolveNewsProviderStatus(snapshot);
var providerMessage = snapshot?.ProviderMessage;
var providerEndpoint = snapshot?.ProviderEndpoint;
var providerHttpStatusCode = snapshot?.ProviderHttpStatusCode;
var providerErrorCode = snapshot?.ProviderErrorCode;
var fallbackBriefingWhenEmpty = randomizer.Choose(catalog.NewsBriefings);
return BuildNewsDecision(
fallbackBriefingWhenEmpty,
null,
preferredCategories.Count > 0 ? preferredCategories : null,
null,
BuildNewsProviderDiagnostics(
providerStatus,
preferredCategories,
requestedHeadlineCount,
snapshot?.Headlines.Count ?? 0,
providerMessage,
providerHttpStatusCode,
providerEndpoint,
providerErrorCode));
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch
{
// Provider failures should never block baseline news behavior.
var fallbackBriefingOnError = randomizer.Choose(catalog.NewsBriefings);
return BuildNewsDecision(
fallbackBriefingOnError,
null,
preferredCategories.Count > 0 ? preferredCategories : null,
null,
BuildNewsProviderDiagnostics(
"provider_exception",
preferredCategories,
requestedHeadlineCount));
}
var fallbackBriefing = randomizer.Choose(catalog.NewsBriefings);
return BuildNewsDecision(
fallbackBriefing,
null,
preferredCategories.Count > 0 ? preferredCategories : null,
null,
BuildNewsProviderDiagnostics(
"provider_unavailable",
preferredCategories,
requestedHeadlineCount));
}
private static JiboInteractionDecision BuildNewsDecision( private static JiboInteractionDecision BuildNewsDecision(
string spokenBriefing, string spokenBriefing,
string? sourceName, string? sourceName,
@@ -5790,4 +5506,4 @@ public sealed record JiboInteractionDecision(
string ReplyText, string ReplyText,
string? SkillName = null, string? SkillName = null,
IDictionary<string, object?>? SkillPayload = null, IDictionary<string, object?>? SkillPayload = null,
IDictionary<string, object?>? ContextUpdates = null); IDictionary<string, object?>? ContextUpdates = null);