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,
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<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(
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<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)
{
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",

View File

@@ -70,6 +70,7 @@ internal static class PersonalReportOrchestrator
IJiboRandomizer randomizer,
IPersonalMemoryStore personalMemoryStore,
Func<TurnContext, string, CancellationToken, Task<JiboInteractionDecision>> buildWeatherDecisionAsync,
Func<TurnContext, CancellationToken, Task<JiboInteractionDecision>> buildCalendarDecisionAsync,
Func<TurnContext, CancellationToken, Task<JiboInteractionDecision>> buildCommuteDecisionAsync,
Func<TurnContext, PersonalMemoryTenantScope> 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<TurnContext, string, CancellationToken, Task<JiboInteractionDecision>> buildWeatherDecisionAsync,
Func<TurnContext, CancellationToken, Task<JiboInteractionDecision>> buildCalendarDecisionAsync,
Func<TurnContext, CancellationToken, Task<JiboInteractionDecision>> 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);

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))
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,

View File

@@ -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<INewsBriefingProvider, NewsApiBriefingProvider>();
services.AddSingleton<IHolidayCalendarProvider>(provider =>
new NagerDateHolidayCalendarProvider(provider.GetRequiredService<HolidayCalendarOptions>()));
services.AddSingleton<ICalendarReportProvider, UnavailableCalendarReportProvider>();
services.AddSingleton<ICommuteReportProvider, UnavailableCommuteReportProvider>();
var statePersistencePath = configuration?["OpenJibo:State:PersistencePath"]
?? 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));
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);

View File

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

View File

@@ -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);