Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
"grammy": "^1.21.1",
"puppeteer": "^24.35.0"
},
"overrides": {
"basic-ftp": "^5.3.0"
},
"devDependencies": {
"@eslint/js": "^9.39.2",
"@types/bun": "^1.1.14",
Expand Down
28 changes: 14 additions & 14 deletions src/orchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const runBriefing = async (
),
);

const sections: BriefingSection[] = [];
const sections: Array<{ section: BriefingSection; priority: number }> = [];
const failures: SourceFailure[] = [];

results.forEach((result, i) => {
Expand All @@ -61,9 +61,11 @@ export const runBriefing = async (
console.log(`[orchestrator] ✓ ${source.name} succeeded`);
const value = result.value;
if (Array.isArray(value)) {
sections.push(...value);
sections.push(
...value.map((section) => ({ section, priority: source.priority })),
);
} else {
sections.push(value);
sections.push({ section: value, priority: source.priority });
}
} else {
const errorMessage =
Expand Down Expand Up @@ -96,7 +98,9 @@ export const runBriefing = async (
});

// Filter out sections with no items (e.g., conditional sources with nothing to report)
const nonEmptySections = sections.filter((s) => s.items.length > 0);
const nonEmptySections = sections.filter(
({ section }) => section.items.length > 0,
);
if (failures.length > 0) {
const failureSection: BriefingSection = {
title: "⚠️ Source Failures",
Expand All @@ -106,18 +110,14 @@ export const runBriefing = async (
sentiment: "negative" as const,
})),
};
nonEmptySections.push(failureSection);
nonEmptySections.push({ section: failureSection, priority: 99 });
}

// Sort sections by priority (lower = higher in briefing)
// Use startsWith to match titles that include additional info (e.g., "ETF Flows from Fri, Jan 30")
const sortedSections = [...nonEmptySections].sort((a, b) => {
const priorityA =
sources.find((s) => a.title.startsWith(s.name))?.priority ?? 99;
const priorityB =
sources.find((s) => b.title.startsWith(s.name))?.priority ?? 99;
return priorityA - priorityB;
});
// Sort sections by source priority (lower = higher in briefing),
// preserving multi-section source ordering regardless of section title.
const sortedSections = [...nonEmptySections]
.sort((a, b) => a.priority - b.priority)
.map(({ section }) => section);

return {
date,
Expand Down
37 changes: 37 additions & 0 deletions tests/orchestrator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,43 @@ describe("runBriefing", () => {
expect(briefing.sections[2]?.title).toBe("Low Priority");
});

it("should sort multi-section sources by source priority even when titles differ", async () => {
const sources = [
{
name: "Crypto News",
priority: 9,
fetch: async () => ({
title: "Crypto News",
icon: "📰",
items: [{ text: "News item" }],
}),
},
{
name: "App Store Rankings",
priority: 7,
fetch: async () => [
{
title: "App Store · Finance",
icon: "📱",
items: [{ text: "Finance rank" }],
},
{
title: "App Store · Total",
icon: "📱",
items: [{ text: "Total rank" }],
},
],
},
];

const briefing = await runBriefing(sources, new Date());

expect(briefing.sections).toHaveLength(3);
expect(briefing.sections[0]?.title).toBe("App Store · Finance");
expect(briefing.sections[1]?.title).toBe("App Store · Total");
expect(briefing.sections[2]?.title).toBe("Crypto News");
});

it("should exclude sections with empty items array", async () => {
const sources = [
createSuccessSource("Has Items", 1, [{ text: "Real data" }]),
Expand Down
Loading