diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/JiboInteractionService.ReportDecisions.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/JiboInteractionService.ReportDecisions.cs new file mode 100644 index 0000000..9d8e254 --- /dev/null +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/JiboInteractionService.ReportDecisions.cs @@ -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 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 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 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 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)); + } +} diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/JiboInteractionService.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/JiboInteractionService.cs index 7423fcc..a5406b3 100644 --- a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/JiboInteractionService.cs +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/JiboInteractionService.cs @@ -7,7 +7,7 @@ using Jibo.Runtime.Abstractions; namespace Jibo.Cloud.Application.Services; -public sealed class JiboInteractionService( +public sealed partial class JiboInteractionService( JiboExperienceContentCache contentCache, IJiboRandomizer randomizer, IPersonalMemoryStore personalMemoryStore, @@ -1353,215 +1353,6 @@ public sealed class JiboInteractionService( ContextUpdates: ScriptedResponseDecisionBuilder.BuildScriptedResponseContextUpdates()); } - private async Task 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 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 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( WeatherReportSnapshot snapshot, WeatherDateEntity weatherDate, @@ -2279,81 +2070,6 @@ public sealed class JiboInteractionService( }); } - private async Task 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( string spokenBriefing, string? sourceName, @@ -5790,4 +5506,4 @@ public sealed record JiboInteractionDecision( string ReplyText, string? SkillName = null, IDictionary? SkillPayload = null, - IDictionary? ContextUpdates = null); \ No newline at end of file + IDictionary? ContextUpdates = null);