Add holiday seasonal routing and calendar report seam

This commit is contained in:
Jacob Dubin
2026-05-20 20:03:14 -05:00
parent 39b21d1326
commit c76af83d7e
9 changed files with 387 additions and 39 deletions

View File

@@ -0,0 +1,14 @@
using Jibo.Runtime.Abstractions;
namespace Jibo.Cloud.Application.Abstractions;
public interface ICalendarReportProvider
{
Task<CalendarReportSnapshot?> GetReportAsync(TurnContext turn, CancellationToken cancellationToken = default);
}
public sealed record CalendarReportSnapshot(
IReadOnlyList<string> EventSummaries,
IReadOnlyList<string> EventTimesOnAt,
IReadOnlyList<string> TomorrowEventSummaries,
bool HasServiceError = false);

View File

@@ -12,6 +12,7 @@ public sealed class JiboInteractionService(
IJiboRandomizer randomizer, IJiboRandomizer randomizer,
IPersonalMemoryStore personalMemoryStore, IPersonalMemoryStore personalMemoryStore,
IWeatherReportProvider? weatherReportProvider = null, IWeatherReportProvider? weatherReportProvider = null,
ICalendarReportProvider? calendarReportProvider = null,
ICommuteReportProvider? commuteReportProvider = null, ICommuteReportProvider? commuteReportProvider = null,
INewsBriefingProvider? newsBriefingProvider = null, INewsBriefingProvider? newsBriefingProvider = null,
ICloudStateStore? cloudStateStore = null) ICloudStateStore? cloudStateStore = null)
@@ -485,6 +486,7 @@ public sealed class JiboInteractionService(
randomizer, randomizer,
personalMemoryStore, personalMemoryStore,
BuildWeatherReportDecisionAsync, BuildWeatherReportDecisionAsync,
BuildCalendarReportDecisionAsync,
BuildCommuteReportDecisionAsync, BuildCommuteReportDecisionAsync,
turnContext => ResolveTenantScope(turnContext), turnContext => ResolveTenantScope(turnContext),
cancellationToken); cancellationToken);
@@ -655,6 +657,54 @@ public sealed class JiboInteractionService(
"holiday season", "holiday season",
"festive", "festive",
"celebrate"), "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( "seasonal_new_years_resolution" => BuildScriptedPersonalityDecision(
catalog, catalog,
"seasonal_new_years_resolution", "seasonal_new_years_resolution",
@@ -708,8 +758,8 @@ public sealed class JiboInteractionService(
"really like times of giving and receiving", "really like times of giving and receiving",
"long way away", "long way away",
"looking forward to christmas"), "looking forward to christmas"),
"seasonal_plans_for_christmas" => BuildScriptedPersonalityDecision( "seasonal_plans_for_christmas" => BuildScriptedHolidayDecision(
catalog, catalog.HolidaySeasonReplies,
"seasonal_plans_for_christmas", "seasonal_plans_for_christmas",
"christmas sweaters", "christmas sweaters",
"wear one of my", "wear one of my",
@@ -1581,6 +1631,37 @@ public sealed class JiboInteractionService(
BuildCommuteSpokenReply(snapshot, catalog)); 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,
@@ -2074,6 +2155,91 @@ public sealed class JiboInteractionService(
return template.Trim(); 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<string> 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) private static string EscapeForEsml(string value)
{ {
return value return value
@@ -3052,13 +3218,6 @@ public sealed class JiboInteractionService(
"are you excited for christmas")) "are you excited for christmas"))
return "seasonal_looks_forward_to_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( if (MatchesAny(
loweredTranscript, loweredTranscript,
"what are you thankful for", "what are you thankful for",
@@ -3076,6 +3235,13 @@ public sealed class JiboInteractionService(
"what's your favourite thing to do")) "what's your favourite thing to do"))
return "robot_what_do_you_like_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( if (MatchesAny(
loweredTranscript, loweredTranscript,
"what is your favorite flower", "what is your favorite flower",
@@ -3241,6 +3407,71 @@ public sealed class JiboInteractionService(
"what is holiday season like")) "what is holiday season like"))
return "seasonal_holiday_season"; 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( if (MatchesAny(
loweredTranscript, loweredTranscript,
"what is your new years resolution", "what is your new years resolution",

View File

@@ -70,6 +70,7 @@ internal static class PersonalReportOrchestrator
IJiboRandomizer randomizer, IJiboRandomizer randomizer,
IPersonalMemoryStore personalMemoryStore, IPersonalMemoryStore personalMemoryStore,
Func<TurnContext, string, CancellationToken, Task<JiboInteractionDecision>> buildWeatherDecisionAsync, Func<TurnContext, string, CancellationToken, Task<JiboInteractionDecision>> buildWeatherDecisionAsync,
Func<TurnContext, CancellationToken, Task<JiboInteractionDecision>> buildCalendarDecisionAsync,
Func<TurnContext, CancellationToken, Task<JiboInteractionDecision>> buildCommuteDecisionAsync, Func<TurnContext, CancellationToken, Task<JiboInteractionDecision>> buildCommuteDecisionAsync,
Func<TurnContext, PersonalMemoryTenantScope> tenantScopeResolver, Func<TurnContext, PersonalMemoryTenantScope> tenantScopeResolver,
CancellationToken cancellationToken) CancellationToken cancellationToken)
@@ -192,6 +193,7 @@ internal static class PersonalReportOrchestrator
toggles, toggles,
currentName, currentName,
buildWeatherDecisionAsync, buildWeatherDecisionAsync,
buildCalendarDecisionAsync,
buildCommuteDecisionAsync, buildCommuteDecisionAsync,
cancellationToken); cancellationToken);
@@ -237,6 +239,7 @@ internal static class PersonalReportOrchestrator
toggles, toggles,
parsedName, parsedName,
buildWeatherDecisionAsync, buildWeatherDecisionAsync,
buildCalendarDecisionAsync,
buildCommuteDecisionAsync, buildCommuteDecisionAsync,
cancellationToken); cancellationToken);
} }
@@ -253,6 +256,7 @@ internal static class PersonalReportOrchestrator
PersonalReportServiceToggles toggles, PersonalReportServiceToggles toggles,
string userName, string userName,
Func<TurnContext, string, CancellationToken, Task<JiboInteractionDecision>> buildWeatherDecisionAsync, Func<TurnContext, string, CancellationToken, Task<JiboInteractionDecision>> buildWeatherDecisionAsync,
Func<TurnContext, CancellationToken, Task<JiboInteractionDecision>> buildCalendarDecisionAsync,
Func<TurnContext, CancellationToken, Task<JiboInteractionDecision>> buildCommuteDecisionAsync, Func<TurnContext, CancellationToken, Task<JiboInteractionDecision>> buildCommuteDecisionAsync,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
@@ -275,26 +279,7 @@ internal static class PersonalReportOrchestrator
} }
if (toggles.CalendarEnabled) if (toggles.CalendarEnabled)
{ reportSections.Add((await buildCalendarDecisionAsync(turn, cancellationToken)).ReplyText);
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));
}
if (toggles.CommuteEnabled) if (toggles.CommuteEnabled)
reportSections.Add((await buildCommuteDecisionAsync(turn, cancellationToken)).ReplyText); reportSections.Add((await buildCommuteDecisionAsync(turn, cancellationToken)).ReplyText);
@@ -706,4 +691,4 @@ internal static class PersonalReportOrchestrator
bool CalendarEnabled, bool CalendarEnabled,
bool CommuteEnabled, bool CommuteEnabled,
bool NewsEnabled); bool NewsEnabled);
} }

View File

@@ -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<CalendarReportSnapshot?> GetReportAsync(
TurnContext turn,
CancellationToken cancellationToken = default)
{
return Task.FromResult<CalendarReportSnapshot?>(null);
}
}

View File

@@ -119,7 +119,8 @@ public static class LegacyMimCatalogImporter
if (fileName.StartsWith("JBO_WhatHolidaysDoYouCelebrate", StringComparison.OrdinalIgnoreCase)) if (fileName.StartsWith("JBO_WhatHolidaysDoYouCelebrate", StringComparison.OrdinalIgnoreCase))
return LegacyMimBucket.Holiday; return LegacyMimBucket.Holiday;
if (fileName.StartsWith("RI_JBO_HasFavoriteHoliday", StringComparison.OrdinalIgnoreCase)) if (fileName.StartsWith("RI_JBO_HasFavoriteHoliday", StringComparison.OrdinalIgnoreCase) ||
IsHolidaySeasonFile(fileName))
return LegacyMimBucket.HolidaySeason; return LegacyMimBucket.HolidaySeason;
if (fileName.StartsWith("RI_JBO_HasFavoriteAnimal", StringComparison.OrdinalIgnoreCase) || if (fileName.StartsWith("RI_JBO_HasFavoriteAnimal", StringComparison.OrdinalIgnoreCase) ||
@@ -375,6 +376,48 @@ public static class LegacyMimCatalogImporter
or LegacyMimBucket.HolidayTracker; 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 private enum LegacyMimBucket
{ {
GenericFallback, GenericFallback,

View File

@@ -1,6 +1,7 @@
using Jibo.Cloud.Application.Abstractions; using Jibo.Cloud.Application.Abstractions;
using Jibo.Cloud.Application.Services; using Jibo.Cloud.Application.Services;
using Jibo.Cloud.Infrastructure.Audio; using Jibo.Cloud.Infrastructure.Audio;
using Jibo.Cloud.Infrastructure.Calendar;
using Jibo.Cloud.Infrastructure.Commute; using Jibo.Cloud.Infrastructure.Commute;
using Jibo.Cloud.Infrastructure.Content; using Jibo.Cloud.Infrastructure.Content;
using Jibo.Cloud.Infrastructure.Holidays; using Jibo.Cloud.Infrastructure.Holidays;
@@ -53,6 +54,7 @@ public static class ServiceCollectionExtensions
services.AddHttpClient<INewsBriefingProvider, NewsApiBriefingProvider>(); services.AddHttpClient<INewsBriefingProvider, NewsApiBriefingProvider>();
services.AddSingleton<IHolidayCalendarProvider>(provider => services.AddSingleton<IHolidayCalendarProvider>(provider =>
new NagerDateHolidayCalendarProvider(provider.GetRequiredService<HolidayCalendarOptions>())); new NagerDateHolidayCalendarProvider(provider.GetRequiredService<HolidayCalendarOptions>()));
services.AddSingleton<ICalendarReportProvider, UnavailableCalendarReportProvider>();
services.AddSingleton<ICommuteReportProvider, UnavailableCommuteReportProvider>(); services.AddSingleton<ICommuteReportProvider, UnavailableCommuteReportProvider>();
var statePersistencePath = configuration?["OpenJibo:State:PersistencePath"] var statePersistencePath = configuration?["OpenJibo:State:PersistencePath"]
?? Path.Combine(AppContext.BaseDirectory, "App_Data", "cloud-state.json"); ?? Path.Combine(AppContext.BaseDirectory, "App_Data", "cloud-state.json");

View File

@@ -108,10 +108,6 @@ public sealed class LegacyMimCatalogImporterTests
reply.Contains("holiday music", StringComparison.OrdinalIgnoreCase)); reply.Contains("holiday music", StringComparison.OrdinalIgnoreCase));
Assert.Contains(catalog.PersonalityReplies, reply => Assert.Contains(catalog.PersonalityReplies, reply =>
reply.Contains("dance party", StringComparison.OrdinalIgnoreCase)); 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 => Assert.Contains(catalog.PersonalityReplies, reply =>
reply.Contains("thankful for the people I know", StringComparison.OrdinalIgnoreCase)); reply.Contains("thankful for the people I know", StringComparison.OrdinalIgnoreCase));
Assert.Contains(catalog.PersonalityReplies, reply => Assert.Contains(catalog.PersonalityReplies, reply =>
@@ -213,6 +209,14 @@ public sealed class LegacyMimCatalogImporterTests
reply.Contains("fun time of year", StringComparison.OrdinalIgnoreCase)); reply.Contains("fun time of year", StringComparison.OrdinalIgnoreCase));
Assert.Contains(catalog.HolidayGiftReplies, reply => Assert.Contains(catalog.HolidayGiftReplies, reply =>
reply.Contains("pet elephant", StringComparison.OrdinalIgnoreCase)); 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 => Assert.Contains(catalog.BirthdayCelebrationReplies, reply =>
reply.Contains("first powered up", StringComparison.OrdinalIgnoreCase) || reply.Contains("first powered up", StringComparison.OrdinalIgnoreCase) ||
reply.Contains("another year older", StringComparison.OrdinalIgnoreCase)); reply.Contains("another year older", StringComparison.OrdinalIgnoreCase));
@@ -348,6 +352,7 @@ public sealed class LegacyMimCatalogImporterTests
catalog.GenericFallbackReplies); catalog.GenericFallbackReplies);
Assert.Contains("For your weather.", catalog.WeatherIntroReplies); Assert.Contains("For your weather.", catalog.WeatherIntroReplies);
Assert.Contains("Today's high is {high}, and the low is {low}.", catalog.WeatherTodayHighLowReplies); 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.", Assert.Contains("Looking at your calendar, I don't see anything scheduled today.",
catalog.CalendarNothingTodayReplies); catalog.CalendarNothingTodayReplies);
Assert.Contains("Looks like I can't access calendars right now. Sorry.", catalog.CalendarServiceDownReplies); Assert.Contains("Looks like I can't access calendars right now. Sorry.", catalog.CalendarServiceDownReplies);

View File

@@ -665,8 +665,8 @@ public sealed class JiboInteractionServiceTests
[InlineData("merry christmas", "seasonal_holiday_greeting", "It's a fun time of year")] [InlineData("merry christmas", "seasonal_holiday_greeting", "It's a fun time of year")]
[InlineData("what holidays do you celebrate", "seasonal_holidays", [InlineData("what holidays do you celebrate", "seasonal_holidays",
"official owner can tell me which ones we'll celebrate together")] "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("how is holiday season", "seasonal_holiday_season", "holiday season is going very nicely")]
[InlineData("do you like holiday season", "seasonal_holiday_season", "I do like festive times.")] [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", [InlineData("what is your new year's resolution", "seasonal_new_years_resolution",
"always trying to learn new skills")] "always trying to learn new skills")]
[InlineData("how are your new year's resolutions going", "seasonal_new_years_update", "not eat bacon")] [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 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 music", "seasonal_likes_holiday_music", "holiday music")]
[InlineData("do you like holiday parties", "seasonal_likes_holiday_parties", "holiday fun can be extra fun")] [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("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("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")] [InlineData("happy birthday", "birthday_celebration", "another year older")]
public async Task BuildDecisionAsync_SeasonalCharm_UsesImportedReplies( public async Task BuildDecisionAsync_SeasonalCharm_UsesImportedReplies(
string transcript, string transcript,
@@ -1876,6 +1884,35 @@ public sealed class JiboInteractionServiceTests
Assert.Equal(true, decision.ContextUpdates[PersonalReportUserVerifiedKey]); 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<string, object?>
{
[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] [Fact]
public async Task BuildDecisionAsync_PersonalReport_NoMatchRetriesThenDeclines() public async Task BuildDecisionAsync_PersonalReport_NoMatchRetriesThenDeclines()
{ {
@@ -4035,6 +4072,7 @@ public sealed class JiboInteractionServiceTests
IPersonalMemoryStore? personalMemoryStore = null, IPersonalMemoryStore? personalMemoryStore = null,
ICloudStateStore? cloudStateStore = null, ICloudStateStore? cloudStateStore = null,
IWeatherReportProvider? weatherReportProvider = null, IWeatherReportProvider? weatherReportProvider = null,
ICalendarReportProvider? calendarReportProvider = null,
ICommuteReportProvider? commuteReportProvider = null, ICommuteReportProvider? commuteReportProvider = null,
INewsBriefingProvider? newsBriefingProvider = null, INewsBriefingProvider? newsBriefingProvider = null,
IJiboExperienceContentRepository? contentRepository = null, IJiboExperienceContentRepository? contentRepository = null,
@@ -4045,6 +4083,7 @@ public sealed class JiboInteractionServiceTests
randomizer ?? new FirstItemRandomizer(), randomizer ?? new FirstItemRandomizer(),
personalMemoryStore ?? new InMemoryPersonalMemoryStore(), personalMemoryStore ?? new InMemoryPersonalMemoryStore(),
weatherReportProvider, weatherReportProvider,
calendarReportProvider,
commuteReportProvider, commuteReportProvider,
newsBriefingProvider, newsBriefingProvider,
cloudStateStore); cloudStateStore);
@@ -4152,4 +4191,16 @@ public sealed class JiboInteractionServiceTests
return Task.FromResult(Snapshot); return Task.FromResult(Snapshot);
} }
} }
private sealed class CapturingCalendarReportProvider : ICalendarReportProvider
{
public CalendarReportSnapshot? Snapshot { get; init; }
public Task<CalendarReportSnapshot?> GetReportAsync(
TurnContext turn,
CancellationToken cancellationToken = default)
{
return Task.FromResult(Snapshot);
}
}
} }

View File

@@ -4734,6 +4734,7 @@ public sealed class JiboWebSocketServiceTests
new StubWeatherReportProvider( new StubWeatherReportProvider(
new WeatherReportSnapshot("Lone Jack, US", "overcast clouds", 79, 82, 78, "clouds", false)), new WeatherReportSnapshot("Lone Jack, US", "overcast clouds", 79, 82, 78, "clouds", false)),
null, null,
null,
new StubNewsBriefingProvider( new StubNewsBriefingProvider(
new NewsBriefingSnapshot( new NewsBriefingSnapshot(
[ [
@@ -5126,6 +5127,7 @@ public sealed class JiboWebSocketServiceTests
private static JiboWebSocketService CreateService( private static JiboWebSocketService CreateService(
InMemoryCloudStateStore stateStore, InMemoryCloudStateStore stateStore,
IWeatherReportProvider? weatherReportProvider = null, IWeatherReportProvider? weatherReportProvider = null,
ICalendarReportProvider? calendarReportProvider = null,
ICommuteReportProvider? commuteReportProvider = null, ICommuteReportProvider? commuteReportProvider = null,
INewsBriefingProvider? newsBriefingProvider = null) INewsBriefingProvider? newsBriefingProvider = null)
{ {
@@ -5136,6 +5138,7 @@ public sealed class JiboWebSocketServiceTests
new DefaultJiboRandomizer(), new DefaultJiboRandomizer(),
new InMemoryPersonalMemoryStore(), new InMemoryPersonalMemoryStore(),
weatherReportProvider, weatherReportProvider,
calendarReportProvider,
commuteReportProvider, commuteReportProvider,
newsBriefingProvider); newsBriefingProvider);
var conversationBroker = new DemoConversationBroker(interactionService); var conversationBroker = new DemoConversationBroker(interactionService);
@@ -5209,4 +5212,4 @@ public sealed class JiboWebSocketServiceTests
return items[^1]; return items[^1];
} }
} }
} }