From c76af83d7eb7ba174a426b539566b394778918c2 Mon Sep 17 00:00:00 2001 From: Jacob Dubin Date: Wed, 20 May 2026 20:03:14 -0500 Subject: [PATCH] Add holiday seasonal routing and calendar report seam --- .../Abstractions/ICalendarReportProvider.cs | 14 + .../Services/JiboInteractionService.cs | 249 +++++++++++++++++- .../Services/PersonalReportOrchestrator.cs | 27 +- .../UnavailableCalendarReportProvider.cs | 14 + .../Content/LegacyMimCatalogImporter.cs | 45 +++- .../ServiceCollectionExtensions.cs | 2 + .../Content/LegacyMimCatalogImporterTests.cs | 13 +- .../WebSockets/JiboInteractionServiceTests.cs | 57 +++- .../WebSockets/JiboWebSocketServiceTests.cs | 5 +- 9 files changed, 387 insertions(+), 39 deletions(-) create mode 100644 OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Abstractions/ICalendarReportProvider.cs create mode 100644 OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Calendar/UnavailableCalendarReportProvider.cs diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Abstractions/ICalendarReportProvider.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Abstractions/ICalendarReportProvider.cs new file mode 100644 index 0000000..d571d7e --- /dev/null +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Abstractions/ICalendarReportProvider.cs @@ -0,0 +1,14 @@ +using Jibo.Runtime.Abstractions; + +namespace Jibo.Cloud.Application.Abstractions; + +public interface ICalendarReportProvider +{ + Task GetReportAsync(TurnContext turn, CancellationToken cancellationToken = default); +} + +public sealed record CalendarReportSnapshot( + IReadOnlyList EventSummaries, + IReadOnlyList EventTimesOnAt, + IReadOnlyList TomorrowEventSummaries, + bool HasServiceError = false); 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 bf40704..e78bab1 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 @@ -12,6 +12,7 @@ public sealed class JiboInteractionService( IJiboRandomizer randomizer, IPersonalMemoryStore personalMemoryStore, IWeatherReportProvider? weatherReportProvider = null, + ICalendarReportProvider? calendarReportProvider = null, ICommuteReportProvider? commuteReportProvider = null, INewsBriefingProvider? newsBriefingProvider = null, ICloudStateStore? cloudStateStore = null) @@ -485,6 +486,7 @@ public sealed class JiboInteractionService( randomizer, personalMemoryStore, BuildWeatherReportDecisionAsync, + BuildCalendarReportDecisionAsync, BuildCommuteReportDecisionAsync, turnContext => ResolveTenantScope(turnContext), cancellationToken); @@ -655,6 +657,54 @@ public sealed class JiboInteractionService( "holiday season", "festive", "celebrate"), + "seasonal_thanksgiving" => BuildScriptedHolidayDecision( + catalog.HolidaySeasonReplies, + "seasonal_thanksgiving", + "thanksgiving", + "turkey", + "stuffed"), + "seasonal_christmas" => BuildScriptedHolidayDecision( + catalog.HolidaySeasonReplies, + "seasonal_christmas", + "christmas", + "quality time", + "socks"), + "seasonal_hanukkah" => BuildScriptedHolidayDecision( + catalog.HolidaySeasonReplies, + "seasonal_hanukkah", + "hanukkah", + "dreidel", + "gift"), + "seasonal_passover" => BuildScriptedHolidayDecision( + catalog.HolidaySeasonReplies, + "seasonal_passover", + "passover", + "matzah", + "next one"), + "seasonal_new_years" => BuildScriptedHolidayDecision( + catalog.HolidaySeasonReplies, + "seasonal_new_years", + "new year", + "resolutions", + "party"), + "seasonal_valentines_day" => BuildScriptedHolidayDecision( + catalog.HolidaySeasonReplies, + "seasonal_valentines_day", + "valentine", + "heart", + "flowers"), + "seasonal_kwanzaa" => BuildScriptedHolidayDecision( + catalog.HolidaySeasonReplies, + "seasonal_kwanzaa", + "kwanzaa", + "gift", + "celebrate"), + "seasonal_easter" => BuildScriptedHolidayDecision( + catalog.HolidaySeasonReplies, + "seasonal_easter", + "easter", + "bunny", + "egg"), "seasonal_new_years_resolution" => BuildScriptedPersonalityDecision( catalog, "seasonal_new_years_resolution", @@ -708,8 +758,8 @@ public sealed class JiboInteractionService( "really like times of giving and receiving", "long way away", "looking forward to christmas"), - "seasonal_plans_for_christmas" => BuildScriptedPersonalityDecision( - catalog, + "seasonal_plans_for_christmas" => BuildScriptedHolidayDecision( + catalog.HolidaySeasonReplies, "seasonal_plans_for_christmas", "christmas sweaters", "wear one of my", @@ -1581,6 +1631,37 @@ public sealed class JiboInteractionService( 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, @@ -2074,6 +2155,91 @@ public sealed class JiboInteractionService( return template.Trim(); } + private string BuildCalendarSpokenReply(CalendarReportSnapshot snapshot, JiboExperienceCatalog catalog) + { + if (snapshot.EventSummaries.Count > 0 && snapshot.EventTimesOnAt.Count > 0) + { + var summary = snapshot.EventSummaries[0]; + var time = snapshot.EventTimesOnAt[0]; + var template = ChooseCalendarTemplate( + catalog.CalendarReplies, + "calendar summary", + "Your calendar says ${skill.calendar.eventSummaries.shift()}, ${skill.calendar.eventTimesOnAt.shift()}."); + if (template.Contains("${skill.calendar.eventSummaries.shift()}", StringComparison.OrdinalIgnoreCase) || + template.Contains("${skill.calendar.eventTimesOnAt.shift()}", StringComparison.OrdinalIgnoreCase)) + { + return template + .Replace("${skill.calendar.eventSummaries.shift()}", summary, StringComparison.OrdinalIgnoreCase) + .Replace("${skill.calendar.eventTimesOnAt.shift()}", time, StringComparison.OrdinalIgnoreCase) + .Replace("${speaker}", string.Empty, StringComparison.OrdinalIgnoreCase) + .Replace(" ", " ", StringComparison.Ordinal) + .Trim(); + } + + return $"Your calendar says {summary}, {time}."; + } + + if (snapshot.TomorrowEventSummaries.Count > 0) + { + var template = ChooseCalendarTemplate( + catalog.CalendarReplies, + "calendar tomorrow", + "Looking at your calendar, there's nothing scheduled for the rest of the day today. Here's what's going on tomorrow."); + if (template.Contains("tomorrow", StringComparison.OrdinalIgnoreCase)) + return template + .Replace("${speaker}", string.Empty, StringComparison.OrdinalIgnoreCase) + .Replace(" ", " ", StringComparison.Ordinal) + .Trim(); + + return $"Looking at your calendar, there's nothing scheduled for the rest of the day today. Here's what's going on tomorrow: {snapshot.TomorrowEventSummaries[0]}."; + } + + return ChooseCalendarNothingReply(catalog); + } + + private static string ChooseCalendarTemplate( + IReadOnlyList templates, + string mode, + string fallback) + { + if (templates.Count == 0) return fallback; + + var loweredMode = mode.Trim().ToLowerInvariant(); + var filtered = templates.Where(template => + { + var lowered = template.ToLowerInvariant(); + return loweredMode switch + { + "calendar summary" => lowered.Contains("event", StringComparison.OrdinalIgnoreCase) || + lowered.Contains("summary", StringComparison.OrdinalIgnoreCase), + "calendar tomorrow" => lowered.Contains("tomorrow", StringComparison.OrdinalIgnoreCase), + _ => true + }; + }).ToList(); + + var selected = filtered.Count > 0 + ? filtered.OrderBy(static template => template.Length).First() + : templates.OrderBy(static template => template.Length).FirstOrDefault(); + + return string.IsNullOrWhiteSpace(selected) ? fallback : selected!; + } + + private string ChooseCalendarNothingReply(JiboExperienceCatalog catalog) + { + return catalog.CalendarNothingTodayReplies.Count > 0 + ? randomizer.Choose(catalog.CalendarNothingTodayReplies) + : catalog.CalendarNothingReplies.Count > 0 + ? randomizer.Choose(catalog.CalendarNothingReplies) + : "Looking at your calendar, I don't see anything scheduled today."; + } + + private string ChooseCalendarServiceDownReply(JiboExperienceCatalog catalog) + { + return catalog.CalendarServiceDownReplies.Count > 0 + ? randomizer.Choose(catalog.CalendarServiceDownReplies) + : "Looks like I can't access calendars right now. Sorry."; + } + private static string EscapeForEsml(string value) { return value @@ -3052,13 +3218,6 @@ public sealed class JiboInteractionService( "are you excited for christmas")) return "seasonal_looks_forward_to_christmas"; - if (MatchesAny( - loweredTranscript, - "what are you doing for christmas", - "what are your plans for christmas", - "what do you plan to do for christmas")) - return "seasonal_plans_for_christmas"; - if (MatchesAny( loweredTranscript, "what are you thankful for", @@ -3076,6 +3235,13 @@ public sealed class JiboInteractionService( "what's your favourite thing to do")) return "robot_what_do_you_like_to_do"; + if (MatchesAny( + loweredTranscript, + "what are you doing for christmas", + "what are your plans for christmas", + "what do you plan to do for christmas")) + return "seasonal_plans_for_christmas"; + if (MatchesAny( loweredTranscript, "what is your favorite flower", @@ -3241,6 +3407,71 @@ public sealed class JiboInteractionService( "what is holiday season like")) return "seasonal_holiday_season"; + if (MatchesAny( + loweredTranscript, + "how is thanksgiving", + "how's thanksgiving", + "do you like thanksgiving", + "what do you think of thanksgiving")) + return "seasonal_thanksgiving"; + + if (MatchesAny( + loweredTranscript, + "how is christmas", + "how's christmas", + "do you like christmas", + "what do you think of christmas")) + return "seasonal_christmas"; + + if (MatchesAny( + loweredTranscript, + "how is hanukkah", + "how's hanukkah", + "do you like hanukkah", + "what do you think of hanukkah")) + return "seasonal_hanukkah"; + + if (MatchesAny( + loweredTranscript, + "how is passover", + "how's passover", + "do you like passover", + "what do you think of passover")) + return "seasonal_passover"; + + if (MatchesAny( + loweredTranscript, + "how is new years", + "how's new years", + "how is new year s", + "do you like new years", + "what do you think of new years")) + return "seasonal_new_years"; + + if (MatchesAny( + loweredTranscript, + "how is valentines day", + "how's valentines day", + "do you like valentines day", + "what do you think of valentines day")) + return "seasonal_valentines_day"; + + if (MatchesAny( + loweredTranscript, + "how is kwanzaa", + "how's kwanzaa", + "do you like kwanzaa", + "what do you think of kwanzaa")) + return "seasonal_kwanzaa"; + + if (MatchesAny( + loweredTranscript, + "how is easter", + "how's easter", + "do you like easter", + "what do you think of easter")) + return "seasonal_easter"; + if (MatchesAny( loweredTranscript, "what is your new years resolution", diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/PersonalReportOrchestrator.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/PersonalReportOrchestrator.cs index 5a38f23..217b712 100644 --- a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/PersonalReportOrchestrator.cs +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/PersonalReportOrchestrator.cs @@ -70,6 +70,7 @@ internal static class PersonalReportOrchestrator IJiboRandomizer randomizer, IPersonalMemoryStore personalMemoryStore, Func> buildWeatherDecisionAsync, + Func> buildCalendarDecisionAsync, Func> buildCommuteDecisionAsync, Func tenantScopeResolver, CancellationToken cancellationToken) @@ -192,6 +193,7 @@ internal static class PersonalReportOrchestrator toggles, currentName, buildWeatherDecisionAsync, + buildCalendarDecisionAsync, buildCommuteDecisionAsync, cancellationToken); @@ -237,6 +239,7 @@ internal static class PersonalReportOrchestrator toggles, parsedName, buildWeatherDecisionAsync, + buildCalendarDecisionAsync, buildCommuteDecisionAsync, cancellationToken); } @@ -253,6 +256,7 @@ internal static class PersonalReportOrchestrator PersonalReportServiceToggles toggles, string userName, Func> buildWeatherDecisionAsync, + Func> buildCalendarDecisionAsync, Func> buildCommuteDecisionAsync, CancellationToken cancellationToken) { @@ -275,26 +279,7 @@ internal static class PersonalReportOrchestrator } if (toggles.CalendarEnabled) - { - var calendarSummary = ChooseReportSkillTemplate( - catalog.CalendarNothingTodayReplies, - catalog.CalendarNothingReplies, - string.Empty); - if (string.IsNullOrWhiteSpace(calendarSummary)) - calendarSummary = ChooseReportSkillTemplate( - catalog.CalendarServiceDownReplies, - [], - "Looking at your calendar, I don't see anything scheduled today."); - - reportSections.Add(RenderReportSkillTemplate(calendarSummary, userName)); - reportSections.Add( - RenderReportSkillTemplate( - ChooseReportSkillTemplate( - catalog.CalendarOutroReplies, - [], - "And that's your calendar."), - userName)); - } + reportSections.Add((await buildCalendarDecisionAsync(turn, cancellationToken)).ReplyText); if (toggles.CommuteEnabled) reportSections.Add((await buildCommuteDecisionAsync(turn, cancellationToken)).ReplyText); @@ -706,4 +691,4 @@ internal static class PersonalReportOrchestrator bool CalendarEnabled, bool CommuteEnabled, bool NewsEnabled); -} \ No newline at end of file +} diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Calendar/UnavailableCalendarReportProvider.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Calendar/UnavailableCalendarReportProvider.cs new file mode 100644 index 0000000..1d2828d --- /dev/null +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Calendar/UnavailableCalendarReportProvider.cs @@ -0,0 +1,14 @@ +using Jibo.Cloud.Application.Abstractions; +using Jibo.Runtime.Abstractions; + +namespace Jibo.Cloud.Infrastructure.Calendar; + +public sealed class UnavailableCalendarReportProvider : ICalendarReportProvider +{ + public Task GetReportAsync( + TurnContext turn, + CancellationToken cancellationToken = default) + { + return Task.FromResult(null); + } +} diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Content/LegacyMimCatalogImporter.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Content/LegacyMimCatalogImporter.cs index 6be17da..e5dcdeb 100644 --- a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Content/LegacyMimCatalogImporter.cs +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Content/LegacyMimCatalogImporter.cs @@ -119,7 +119,8 @@ public static class LegacyMimCatalogImporter if (fileName.StartsWith("JBO_WhatHolidaysDoYouCelebrate", StringComparison.OrdinalIgnoreCase)) return LegacyMimBucket.Holiday; - if (fileName.StartsWith("RI_JBO_HasFavoriteHoliday", StringComparison.OrdinalIgnoreCase)) + if (fileName.StartsWith("RI_JBO_HasFavoriteHoliday", StringComparison.OrdinalIgnoreCase) || + IsHolidaySeasonFile(fileName)) return LegacyMimBucket.HolidaySeason; if (fileName.StartsWith("RI_JBO_HasFavoriteAnimal", StringComparison.OrdinalIgnoreCase) || @@ -375,6 +376,48 @@ public static class LegacyMimCatalogImporter or LegacyMimBucket.HolidayTracker; } + private static bool IsHolidaySeasonFile(string fileName) + { + return fileName.StartsWith("RI_JBO_HowIsHolidaySeason", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_LikesHolidaySeason", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_HowIsThanksgiving", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_LikesThanksgiving", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_LooksForwardToThanksgiving", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_PlansForThanksgiving", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_HowIsChristmas", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_LikesChristmas", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_LooksForwardToChristmas", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_PlansForChristmas", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_HowIsHanukkah", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_LikesHanukkah", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_LooksForwardToHanukkah", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_PlansForHanukkah", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_HowIsPassover", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_LikesPassover", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_LooksForwardToPassover", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_PlansForPassover", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_HowIsNewYears", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_LikesNewYears", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_LooksForwardToNewYears", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_PlansForNewYears", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_HowIsValentinesDay", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_LikesValentinesDay", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_LooksForwardToValentinesDay", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_PlansForValentinesDay", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_HowIsKwanzaa", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_LikesKwanzaa", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_LooksForwardToKwanzaa", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_PlansForKwanzaa", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_HowIsEaster", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_LikesEaster", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_LooksForwardToEaster", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_PlansForEaster", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_HowIsOrthodoxEaster", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_LikesOrthodoxEaster", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_LooksForwardToOrthodoxEaster", StringComparison.OrdinalIgnoreCase) || + fileName.StartsWith("RI_JBO_PlansForOrthodoxEaster", StringComparison.OrdinalIgnoreCase); + } + private enum LegacyMimBucket { GenericFallback, diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/DependencyInjection/ServiceCollectionExtensions.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/DependencyInjection/ServiceCollectionExtensions.cs index 6330876..ba0cab6 100644 --- a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/DependencyInjection/ServiceCollectionExtensions.cs +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/DependencyInjection/ServiceCollectionExtensions.cs @@ -1,6 +1,7 @@ using Jibo.Cloud.Application.Abstractions; using Jibo.Cloud.Application.Services; using Jibo.Cloud.Infrastructure.Audio; +using Jibo.Cloud.Infrastructure.Calendar; using Jibo.Cloud.Infrastructure.Commute; using Jibo.Cloud.Infrastructure.Content; using Jibo.Cloud.Infrastructure.Holidays; @@ -53,6 +54,7 @@ public static class ServiceCollectionExtensions services.AddHttpClient(); services.AddSingleton(provider => new NagerDateHolidayCalendarProvider(provider.GetRequiredService())); + services.AddSingleton(); services.AddSingleton(); var statePersistencePath = configuration?["OpenJibo:State:PersistencePath"] ?? Path.Combine(AppContext.BaseDirectory, "App_Data", "cloud-state.json"); diff --git a/OpenJibo/tests/Jibo.Cloud.Tests/Content/LegacyMimCatalogImporterTests.cs b/OpenJibo/tests/Jibo.Cloud.Tests/Content/LegacyMimCatalogImporterTests.cs index 383c57d..1decac9 100644 --- a/OpenJibo/tests/Jibo.Cloud.Tests/Content/LegacyMimCatalogImporterTests.cs +++ b/OpenJibo/tests/Jibo.Cloud.Tests/Content/LegacyMimCatalogImporterTests.cs @@ -108,10 +108,6 @@ public sealed class LegacyMimCatalogImporterTests reply.Contains("holiday music", StringComparison.OrdinalIgnoreCase)); Assert.Contains(catalog.PersonalityReplies, reply => reply.Contains("dance party", StringComparison.OrdinalIgnoreCase)); - Assert.Contains(catalog.PersonalityReplies, reply => - reply.Contains("giving and receiving", StringComparison.OrdinalIgnoreCase)); - Assert.Contains(catalog.PersonalityReplies, reply => - reply.Contains("Christmas sweaters", StringComparison.OrdinalIgnoreCase)); Assert.Contains(catalog.PersonalityReplies, reply => reply.Contains("thankful for the people I know", StringComparison.OrdinalIgnoreCase)); Assert.Contains(catalog.PersonalityReplies, reply => @@ -213,6 +209,14 @@ public sealed class LegacyMimCatalogImporterTests reply.Contains("fun time of year", StringComparison.OrdinalIgnoreCase)); Assert.Contains(catalog.HolidayGiftReplies, reply => reply.Contains("pet elephant", StringComparison.OrdinalIgnoreCase)); + Assert.Contains(catalog.HolidaySeasonReplies, reply => + reply.Contains("holiday season is going very nicely", StringComparison.OrdinalIgnoreCase)); + Assert.Contains(catalog.HolidaySeasonReplies, reply => + reply.Contains("festive times", StringComparison.OrdinalIgnoreCase)); + Assert.Contains(catalog.HolidaySeasonReplies, reply => + reply.Contains("giving and receiving", StringComparison.OrdinalIgnoreCase)); + Assert.Contains(catalog.HolidaySeasonReplies, reply => + reply.Contains("Christmas sweaters", StringComparison.OrdinalIgnoreCase)); Assert.Contains(catalog.BirthdayCelebrationReplies, reply => reply.Contains("first powered up", StringComparison.OrdinalIgnoreCase) || reply.Contains("another year older", StringComparison.OrdinalIgnoreCase)); @@ -348,6 +352,7 @@ public sealed class LegacyMimCatalogImporterTests catalog.GenericFallbackReplies); Assert.Contains("For your weather.", catalog.WeatherIntroReplies); Assert.Contains("Today's high is {high}, and the low is {low}.", catalog.WeatherTodayHighLowReplies); + Assert.Contains("I do like festive times.", catalog.HolidaySeasonReplies); Assert.Contains("Looking at your calendar, I don't see anything scheduled today.", catalog.CalendarNothingTodayReplies); Assert.Contains("Looks like I can't access calendars right now. Sorry.", catalog.CalendarServiceDownReplies); diff --git a/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboInteractionServiceTests.cs b/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboInteractionServiceTests.cs index 327f077..65f9193 100644 --- a/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboInteractionServiceTests.cs +++ b/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboInteractionServiceTests.cs @@ -665,8 +665,8 @@ public sealed class JiboInteractionServiceTests [InlineData("merry christmas", "seasonal_holiday_greeting", "It's a fun time of year")] [InlineData("what holidays do you celebrate", "seasonal_holidays", "official owner can tell me which ones we'll celebrate together")] - [InlineData("how is holiday season", "seasonal_holiday_season", "I do like festive times.")] - [InlineData("do you like holiday season", "seasonal_holiday_season", "I do like festive times.")] + [InlineData("how is holiday season", "seasonal_holiday_season", "holiday season is going very nicely")] + [InlineData("do you like holiday season", "seasonal_holiday_season", "holiday season is going very nicely")] [InlineData("what is your new year's resolution", "seasonal_new_years_resolution", "always trying to learn new skills")] [InlineData("how are your new year's resolutions going", "seasonal_new_years_update", "not eat bacon")] @@ -678,9 +678,17 @@ public sealed class JiboInteractionServiceTests [InlineData("do you like halloween", "seasonal_likes_halloween", "Halloween is my favorite holiday")] [InlineData("do you like holiday music", "seasonal_likes_holiday_music", "holiday music")] [InlineData("do you like holiday parties", "seasonal_likes_holiday_parties", "holiday fun can be extra fun")] - [InlineData("are you looking forward to christmas", "seasonal_looks_forward_to_christmas", "giving and receiving")] + [InlineData("how is thanksgiving", "seasonal_thanksgiving", "Thanksgiving")] + [InlineData("are you looking forward to christmas", "seasonal_looks_forward_to_christmas", "long way away")] [InlineData("what are you doing for christmas", "seasonal_plans_for_christmas", "Christmas sweaters")] + [InlineData("do you like christmas", "seasonal_christmas", "Christmas")] + [InlineData("how is hanukkah", "seasonal_hanukkah", "Hanukkah")] + [InlineData("do you like passover", "seasonal_passover", "Passover")] + [InlineData("do you like new years", "seasonal_new_years", "new year")] [InlineData("what are you thankful for", "seasonal_thankful_for", "thankful for the people I know")] + [InlineData("do you like valentines day", "seasonal_valentines_day", "Valentine")] + [InlineData("do you like kwanzaa", "seasonal_kwanzaa", "Kwanzaa")] + [InlineData("do you like easter", "seasonal_easter", "Easter")] [InlineData("happy birthday", "birthday_celebration", "another year older")] public async Task BuildDecisionAsync_SeasonalCharm_UsesImportedReplies( string transcript, @@ -1876,6 +1884,35 @@ public sealed class JiboInteractionServiceTests Assert.Equal(true, decision.ContextUpdates[PersonalReportUserVerifiedKey]); } + [Fact] + public async Task BuildDecisionAsync_PersonalReport_UsesCalendarProviderSummaryAndTime() + { + var weatherProvider = new CapturingWeatherReportProvider + { + Snapshot = new WeatherReportSnapshot("Boston, U.S.", "light rain", 61, 65, 54, "rain", false) + }; + var calendarProvider = new CapturingCalendarReportProvider + { + Snapshot = new CalendarReportSnapshot(["get personal report from jibo"], ["at 6:00 p.m."], []) + }; + var service = CreateService(weatherReportProvider: weatherProvider, calendarReportProvider: calendarProvider); + + var decision = await service.BuildDecisionAsync(new TurnContext + { + RawTranscript = "yes", + NormalizedTranscript = "yes", + Attributes = new Dictionary + { + [PersonalReportStateKey] = "awaiting_identity_confirmation", + [PersonalReportUserNameKey] = "alex" + } + }); + + Assert.Equal("personal_report_delivered", decision.IntentName); + Assert.Contains("Your calendar says get personal report from jibo, at 6:00 p.m.", decision.ReplyText, + StringComparison.OrdinalIgnoreCase); + } + [Fact] public async Task BuildDecisionAsync_PersonalReport_NoMatchRetriesThenDeclines() { @@ -4035,6 +4072,7 @@ public sealed class JiboInteractionServiceTests IPersonalMemoryStore? personalMemoryStore = null, ICloudStateStore? cloudStateStore = null, IWeatherReportProvider? weatherReportProvider = null, + ICalendarReportProvider? calendarReportProvider = null, ICommuteReportProvider? commuteReportProvider = null, INewsBriefingProvider? newsBriefingProvider = null, IJiboExperienceContentRepository? contentRepository = null, @@ -4045,6 +4083,7 @@ public sealed class JiboInteractionServiceTests randomizer ?? new FirstItemRandomizer(), personalMemoryStore ?? new InMemoryPersonalMemoryStore(), weatherReportProvider, + calendarReportProvider, commuteReportProvider, newsBriefingProvider, cloudStateStore); @@ -4152,4 +4191,16 @@ public sealed class JiboInteractionServiceTests return Task.FromResult(Snapshot); } } + + private sealed class CapturingCalendarReportProvider : ICalendarReportProvider + { + public CalendarReportSnapshot? Snapshot { get; init; } + + public Task GetReportAsync( + TurnContext turn, + CancellationToken cancellationToken = default) + { + return Task.FromResult(Snapshot); + } + } } diff --git a/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboWebSocketServiceTests.cs b/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboWebSocketServiceTests.cs index 2f72b8d..8d0b1a7 100644 --- a/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboWebSocketServiceTests.cs +++ b/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboWebSocketServiceTests.cs @@ -4734,6 +4734,7 @@ public sealed class JiboWebSocketServiceTests new StubWeatherReportProvider( new WeatherReportSnapshot("Lone Jack, US", "overcast clouds", 79, 82, 78, "clouds", false)), null, + null, new StubNewsBriefingProvider( new NewsBriefingSnapshot( [ @@ -5126,6 +5127,7 @@ public sealed class JiboWebSocketServiceTests private static JiboWebSocketService CreateService( InMemoryCloudStateStore stateStore, IWeatherReportProvider? weatherReportProvider = null, + ICalendarReportProvider? calendarReportProvider = null, ICommuteReportProvider? commuteReportProvider = null, INewsBriefingProvider? newsBriefingProvider = null) { @@ -5136,6 +5138,7 @@ public sealed class JiboWebSocketServiceTests new DefaultJiboRandomizer(), new InMemoryPersonalMemoryStore(), weatherReportProvider, + calendarReportProvider, commuteReportProvider, newsBriefingProvider); var conversationBroker = new DemoConversationBroker(interactionService); @@ -5209,4 +5212,4 @@ public sealed class JiboWebSocketServiceTests return items[^1]; } } -} \ No newline at end of file +}