diff --git a/src/services/monitor.ts b/src/services/monitor.ts index 49cd362..160daf2 100644 --- a/src/services/monitor.ts +++ b/src/services/monitor.ts @@ -14,6 +14,7 @@ interface ListingScreeningSummary { eligible: number; outsideRecentWindow: number; deferred: number; + deferredReason: 'selected-recent-candidate' | 'older-than-fallback' | null; } export class MonitorService { @@ -57,6 +58,7 @@ export class MonitorService { eligible: 0, outsideRecentWindow: 0, deferred: 0, + deferredReason: null, }; let selected: RedditPost | null = null; @@ -126,6 +128,7 @@ export class MonitorService { if (!prioritizeRecent || post.created_utc >= recentCutoffSec) { selected = post; summary.deferred = newestFirst.length - index - 1; + summary.deferredReason = 'selected-recent-candidate'; break; } @@ -135,6 +138,7 @@ export class MonitorService { summary.outsideRecentWindow += 1; if (!fallback || post.created_utc > fallback.created_utc) fallback = post; summary.deferred = newestFirst.length - index - 1; + summary.deferredReason = 'older-than-fallback'; break; } @@ -232,7 +236,12 @@ export class MonitorService { `${summary.eligible} eligible${selected ? ' (selected)' : ''}`, `${summary.outsideRecentWindow} outside recent window`, ]; - if (summary.deferred > 0) counts.push(`${summary.deferred} not evaluated after outcome`); + if (summary.deferred > 0) { + const reason = summary.deferredReason === 'older-than-fallback' + ? 'older than eligible fallback' + : 'not evaluated after selected recent candidate'; + counts.push(`${summary.deferred} ${reason}`); + } this.logger.info(`r/${subreddit} listing: ${counts.join('; ')}`); } diff --git a/tests/monitor.test.ts b/tests/monitor.test.ts index 64e8b6e..36bd83d 100644 --- a/tests/monitor.test.ts +++ b/tests/monitor.test.ts @@ -168,7 +168,7 @@ describe('MonitorService', () => { await expect(monitor.getUnprocessedPosts()).resolves.toEqual([selected]); expect(logger.logs).toContainEqual(expect.objectContaining({ level: 'info', - message: 'r/sub1 listing: 4 fetched; 3 screened; 0 already processed; 1 already messaged; 1 seller signals; 0 missing gender tag; 0 gender not targeted; 1 eligible (selected); 0 outside recent window; 1 not evaluated after outcome', + message: 'r/sub1 listing: 4 fetched; 3 screened; 0 already processed; 1 already messaged; 1 seller signals; 0 missing gender tag; 0 gender not targeted; 1 eligible (selected); 0 outside recent window; 1 not evaluated after selected recent candidate', })); }); @@ -321,6 +321,32 @@ describe('MonitorService', () => { expect(result[0].id).toBe('t3_old'); }); + it('labels deferred listing posts as older than the eligible fallback', async () => { + const now = Math.floor(Date.now() / 1000); + const fallback: RedditPost = { + id: 't3_fallback', subreddit: 'sub1', author: 'u_fallback', title: 'Fallback', + url: 'http://example.com/fallback.jpg', permalink: '/r/sub1/fallback', thumbnail: '', + is_self: false, created_utc: now - 7200, over_18: false, spoiler: false, + }; + const older: RedditPost = { ...fallback, id: 't3_older', author: 'u_older', created_utc: now - 10_800 }; + const config = getTestConfig({ + reddit: { + subreddits: [{ name: 'sub1', type: 'image' }], + prioritizeRecentPosts: true, + recentWindowMinutes: 60, + }, + }); + monitor = new MonitorService( + createMockRedditApi([fallback, older]), createMockPostsRepo(), createMockMessagesRepo(), config, logger, 0, + ); + + await monitor.getUnprocessedPosts(); + + expect(logger.logs).toContainEqual(expect.objectContaining({ + message: expect.stringContaining('1 older than eligible fallback'), + })); + }); + it('returns empty array when no subreddits have valid posts', async () => { const redditApi = createMockRedditApi([]); const postsRepo = createMockPostsRepo();