diff --git a/src/database/repositories.ts b/src/database/repositories.ts index 3818f5d..15d4cb5 100644 --- a/src/database/repositories.ts +++ b/src/database/repositories.ts @@ -664,6 +664,7 @@ export class MessagesRepository { export class SuccessfulMessagesRepository { private insertStmt: Database.Statement; private getRecentStmt: Database.Statement; + private getAttributedRecentStmt: Database.Statement; private getBySubredditStmt: Database.Statement; private countStmt: Database.Statement; private existsByMessageStmt: Database.Statement; @@ -678,6 +679,17 @@ export class SuccessfulMessagesRepository { SELECT * FROM successful_messages ORDER BY created_at DESC, id DESC LIMIT ? `); + this.getAttributedRecentStmt = db.prepare(` + SELECT successful_messages.* + FROM successful_messages + LEFT JOIN sent_messages sm ON sm.id = successful_messages.sent_message_id + WHERE successful_messages.subreddit IS NOT NULL + OR sm.post_id IS NOT NULL + OR sm.strategy_id IS NOT NULL + ORDER BY successful_messages.created_at DESC, successful_messages.id DESC + LIMIT ? + `); + // Get examples prioritizing same subreddit, then falling back to others this.getBySubredditStmt = db.prepare(` SELECT *, @@ -722,6 +734,11 @@ export class SuccessfulMessagesRepository { return rows.map((row) => this.mapRow(row)); } + getAttributedRecent(limit: number = 5): SuccessfulMessageRecord[] { + const rows = this.getAttributedRecentStmt.all(limit) as Record[]; + return rows.map((row) => this.mapRow(row)); + } + getWeightedExamples(subreddit: string | null, limit: number = 3): SuccessfulMessageRecord[] { if (subreddit) { const rows = this.getBySubredditStmt.all(subreddit, limit) as Record[]; diff --git a/src/web/routes/stats.ts b/src/web/routes/stats.ts index b9a4546..c3e9aa2 100644 --- a/src/web/routes/stats.ts +++ b/src/web/routes/stats.ts @@ -50,7 +50,7 @@ export function createStatsRouter(ctx: WebContext): Router { }); router.get('/successful', (_req, res) => { - res.json(ctx.repos.successful.getRecent(10)); + res.json(ctx.repos.successful.getAttributedRecent(10)); }); return router; diff --git a/tests/repositories.test.ts b/tests/repositories.test.ts index e3b9791..6f71748 100644 --- a/tests/repositories.test.ts +++ b/tests/repositories.test.ts @@ -431,11 +431,15 @@ describe('MessagesRepository', () => { describe('SuccessfulMessagesRepository', () => { let successfulMessagesRepo: SuccessfulMessagesRepository; + let postsRepo: PostsRepository; + let messagesRepo: MessagesRepository; let db: Database; beforeEach(() => { const repos = createTestRepositories(); successfulMessagesRepo = repos.successfulMessagesRepo; + postsRepo = repos.postsRepo; + messagesRepo = repos.messagesRepo; db = repos.db; }); @@ -520,6 +524,46 @@ describe('SuccessfulMessagesRepository', () => { expect(recent[0].subreddit).toBeNull(); }); + it('returns only examples attributable to a post, strategy, or subreddit', () => { + postsRepo.insert({ + id: 'source-post', + subreddit: 'example', + author: 'alice', + title: 'Source post', + imageUrl: null, + processedAt: Date.now(), + }); + const sentMessageId = messagesRepo.insert({ + postId: 'source-post', + recipient: 'alice', + subject: 'alice', + message: 'Attributed opener', + sentAt: Date.now(), + strategyId: 'direct', + }); + successfulMessagesRepo.insert({ + sentMessageId, + originalMessage: 'Attributed opener', + imageAnalysis: null, + postTitle: 'Source post', + responseSnippet: 'Hello', + subreddit: null, + }); + successfulMessagesRepo.insert({ + sentMessageId: null, + originalMessage: 'Legacy unknown opener', + imageAnalysis: null, + postTitle: 'Chat with t2_unknown', + responseSnippet: 'Hello', + subreddit: null, + }); + + expect(successfulMessagesRepo.getAttributedRecent(10)).toEqual([ + expect.objectContaining({ originalMessage: 'Attributed opener' }), + ]); + expect(successfulMessagesRepo.getRecent(10)).toHaveLength(2); + }); + it('returns most recent examples first', () => { for (let i = 0; i < 5; i++) { successfulMessagesRepo.insert({