Add recent outreach rejections stream

This commit is contained in:
jjbrunton committed 2026-08-14 15:45:57 +01:00
1 parent 0c21202253
commit 94049ed9c4
9 files changed
+170 -16

No files matched your search

@@ -480,6 +480,21 @@ export class CandidateQueueRepository {
return rows.map((row) => this.mapRow(row));
}
/** Most recently completed screening rejections for the operator audit stream. */
listRecentRejected(limit = 50): CandidateQueueRecord[] {
if (!Number.isInteger(limit) || limit < 1 || limit > 50) {
throw new Error('Rejected candidate limit must be an integer between 1 and 50');
}
const rows = this.db.prepare(`
SELECT * FROM candidate_queue
WHERE status = 'rejected'
AND COALESCE(last_error, '') NOT IN ('Cleared manually by operator', 'Removed manually by operator')
ORDER BY updated_at DESC, post_created_at DESC
LIMIT ?
`).all(limit) as Array<Record<string, unknown>>;
return rows.map((row) => this.mapRow(row));
}
private claim(
from: 'discovered' | 'ready',
to: 'screening' | 'sending',
+25
View File
@@ -45,6 +45,18 @@ interface OutreachCandidateView {
owner: { accountId: string; redditUsername: string } | null;
}
interface OutreachRejectedCandidateView {
postId: string;
subreddit: string;
author: string;
title: string;
postUrl: string;
sourceType: 'image' | 'chat';
sourceContent: string | null;
rejectedAt: number;
reason: string;
}
function redditPostUrl(permalink: string): string {
try {
return new URL(permalink, 'https://www.reddit.com').toString();
@@ -65,6 +77,7 @@ export function createOutreachRouter(ctx: WebContext): Router {
const stats = queue.getStats();
const records = queue.listActive();
const rejectedRecords = queue.listRecentRejected();
const accountById = new Map((ctx.services.redditAccounts?.list() ?? [])
.map((account) => [account.accountId, account.redditUsername] as const));
const candidates: OutreachCandidateView[] = records.map((candidate) => ({
@@ -116,6 +129,17 @@ export function createOutreachRouter(ctx: WebContext): Router {
}
: null,
}));
const recentRejections: OutreachRejectedCandidateView[] = rejectedRecords.map((candidate) => ({
postId: candidate.postId,
subreddit: candidate.subreddit,
author: candidate.author,
title: candidate.post.title,
postUrl: redditPostUrl(candidate.post.permalink),
sourceType: candidate.sourceType,
sourceContent: candidate.post.targetComment?.body ?? (candidate.post.selftext?.trim() || null),
rejectedAt: candidate.updatedAt,
reason: candidate.lastError ?? 'Unknown',
}));
const stages = {
discovered: stats.byStatus.discovered,
screening: stats.byStatus.screening,
@@ -129,6 +153,7 @@ export function createOutreachRouter(ctx: WebContext): Router {
stages,
outcomes: { sent: stats.byStatus.sent, rejected: stats.byStatus.rejected },
rejectionReasons: stats.rejectionReasons,
recentRejections,
oldestActiveAt: queue.getOldestActiveAt(),
candidates,
};