Add structured headlines to news payload

This commit is contained in:
Jacob Dubin
2026-05-21 16:50:43 -05:00
parent febceecab8
commit acdc6da286
2 changed files with 21 additions and 2 deletions

View File

@@ -14,7 +14,8 @@ public sealed partial class JiboInteractionService
string? sourceName, string? sourceName,
IReadOnlyList<string>? categories, IReadOnlyList<string>? categories,
int? headlineCount, int? headlineCount,
IReadOnlyDictionary<string, object?>? providerDiagnostics = null) IReadOnlyDictionary<string, object?>? providerDiagnostics = null,
IReadOnlyList<NewsHeadline>? headlines = null)
{ {
var speakableBriefing = NormalizeNewsSpeechText(spokenBriefing); var speakableBriefing = NormalizeNewsSpeechText(spokenBriefing);
var payload = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase) var payload = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
@@ -25,6 +26,9 @@ public sealed partial class JiboInteractionService
["mim_type"] = "announcement", ["mim_type"] = "announcement",
["prompt_id"] = "NewsHeadline_AN_01", ["prompt_id"] = "NewsHeadline_AN_01",
["prompt_sub_category"] = "AN", ["prompt_sub_category"] = "AN",
["news_view_enabled"] = true,
["news_view_kind"] = "newsBriefing",
["news_view_mode"] = "provider",
["esml"] = ["esml"] =
$"<speak><anim cat='news' meta='news-stinger' nonBlocking='true' /><break size='0.35'/><es cat='neutral' filter='!ssa-only, !sfx-only' endNeutral='true'>{EscapeForEsml(speakableBriefing)}</es></speak>" $"<speak><anim cat='news' meta='news-stinger' nonBlocking='true' /><break size='0.35'/><es cat='neutral' filter='!ssa-only, !sfx-only' endNeutral='true'>{EscapeForEsml(speakableBriefing)}</es></speak>"
}; };
@@ -35,6 +39,18 @@ public sealed partial class JiboInteractionService
if (categories is { Count: > 0 }) payload["news_categories"] = categories.ToArray(); if (categories is { Count: > 0 }) payload["news_categories"] = categories.ToArray();
if (headlines is { Count: > 0 })
payload["news_headlines"] = headlines.Select(static headline => new Dictionary<string, object?>(
StringComparer.OrdinalIgnoreCase)
{
["title"] = headline.Title,
["summary"] = headline.Summary,
["category"] = headline.Category,
["sourceName"] = headline.SourceName,
["url"] = headline.Url
})
.ToArray();
if (providerDiagnostics is not null) if (providerDiagnostics is not null)
foreach (var (key, value) in providerDiagnostics) foreach (var (key, value) in providerDiagnostics)
payload[key] = value; payload[key] = value;
@@ -77,7 +93,8 @@ public sealed partial class JiboInteractionService
"provider_success", "provider_success",
preferredCategories, preferredCategories,
requestedHeadlineCount, requestedHeadlineCount,
headlines.Length)); headlines.Length),
headlines);
} }
private static IReadOnlyDictionary<string, object?> BuildNewsProviderDiagnostics( private static IReadOnlyDictionary<string, object?> BuildNewsProviderDiagnostics(

View File

@@ -4192,6 +4192,8 @@ public sealed class JiboInteractionServiceTests
Assert.Equal("provider_success", decision.SkillPayload["news_provider_status"]); Assert.Equal("provider_success", decision.SkillPayload["news_provider_status"]);
Assert.Equal(3, decision.SkillPayload["news_provider_requested_headlines"]); Assert.Equal(3, decision.SkillPayload["news_provider_requested_headlines"]);
Assert.Equal(2, decision.SkillPayload["news_provider_resolved_headlines"]); Assert.Equal(2, decision.SkillPayload["news_provider_resolved_headlines"]);
Assert.NotNull(decision.SkillPayload["news_headlines"]);
Assert.IsType<Dictionary<string, object?>[]>(decision.SkillPayload["news_headlines"]);
Assert.Contains("Local robotics team unveils weather-ready helper", decision.ReplyText, Assert.Contains("Local robotics team unveils weather-ready helper", decision.ReplyText,
StringComparison.OrdinalIgnoreCase); StringComparison.OrdinalIgnoreCase);
Assert.NotNull(provider.LastRequest); Assert.NotNull(provider.LastRequest);