Update source code and remove runtime data from tracking

Add gitignore rules for browser session data, SQLite databases,
media downloads, and build artifacts. Remove previously tracked
runtime files from the repository.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
jjbruntonandClaude Opus 4.6 committed 2026-05-20 18:54:07 +01:00
1 parent e90213d1ff
commit dfa0580859
23 files changed
+5324 -347

No files matched your search

+157 -15
View File
@@ -22,7 +22,10 @@ export interface SentMessageRecord {
sentAt: number;
gotResponse: boolean;
responseCheckedAt: number | null;
strategyId: string | null;
subreddit?: string;
postTitle?: string;
postImageUrl?: string;
}
export interface SuccessfulMessageRecord {
@@ -32,9 +35,31 @@ export interface SuccessfulMessageRecord {
imageAnalysis: string | null;
postTitle: string | null;
responseSnippet: string | null;
subreddit: string | null;
createdAt: number;
}
export interface StrategyStats {
strategyId: string;
totalSent: number;
totalResponses: number;
responseRate: number;
}
export interface SubredditStats {
subreddit: string;
totalSent: number;
totalResponses: number;
responseRate: number;
}
export interface DailyTrend {
date: string;
totalSent: number;
totalResponses: number;
responseRate: number;
}
export class PostsRepository {
private insertStmt: Database.Statement;
private existsStmt: Database.Statement;
@@ -42,6 +67,7 @@ export class PostsRepository {
private updateAnalysisStmt: Database.Statement;
private setErrorStmt: Database.Statement;
private getByIdStmt: Database.Statement;
private getByAuthorStmt: Database.Statement;
constructor(private db: Database.Database) {
this.insertStmt = db.prepare(`
@@ -62,6 +88,10 @@ export class PostsRepository {
this.setErrorStmt = db.prepare(`UPDATE processed_posts SET error = ? WHERE id = ?`);
this.getByIdStmt = db.prepare(`SELECT * FROM processed_posts WHERE id = ?`);
this.getByAuthorStmt = db.prepare(`
SELECT * FROM processed_posts WHERE author = ? COLLATE NOCASE ORDER BY processed_at DESC LIMIT 10
`);
}
isProcessed(postId: string): boolean {
@@ -115,6 +145,22 @@ export class PostsRepository {
error: row.error as string | null,
};
}
getByAuthor(author: string): ProcessedPostRecord[] {
const rows = this.getByAuthorStmt.all(author) as Record<string, unknown>[];
return rows.map((row) => ({
id: row.id as string,
subreddit: row.subreddit as string,
author: row.author as string,
title: row.title as string,
imageUrl: row.image_url as string | null,
imageAnalysis: row.image_analysis as string | null,
generatedMessage: row.generated_message as string | null,
processedAt: row.processed_at as number,
messageSent: Boolean(row.message_sent),
error: row.error as string | null,
}));
}
}
export class MessagesRepository {
@@ -128,14 +174,14 @@ export class MessagesRepository {
constructor(private db: Database.Database) {
this.insertStmt = db.prepare(`
INSERT INTO sent_messages (post_id, recipient, subject, message, sent_at)
VALUES (?, ?, ?, ?, ?)
INSERT INTO sent_messages (post_id, recipient, subject, message, sent_at, strategy_id)
VALUES (?, ?, ?, ?, ?, ?)
`);
this.getByPostIdStmt = db.prepare(`SELECT * FROM sent_messages WHERE post_id = ?`);
this.getRecentStmt = db.prepare(`
SELECT sm.*, pp.subreddit
SELECT sm.*, pp.subreddit, pp.title AS post_title, pp.image_url AS post_image_url
FROM sent_messages sm
LEFT JOIN processed_posts pp ON sm.post_id = pp.id
ORDER BY sm.sent_at DESC
@@ -170,13 +216,15 @@ export class MessagesRepository {
subject: string;
message: string;
sentAt: number;
strategyId?: string | null;
}): number {
const result = this.insertStmt.run(
message.postId,
message.recipient,
message.subject,
message.message,
message.sentAt
message.sentAt,
message.strategyId || null
);
return result.lastInsertRowid as number;
}
@@ -212,6 +260,72 @@ export class MessagesRepository {
return this.hasSentToStmt.get(recipient) !== undefined;
}
getStrategyStats(): StrategyStats[] {
const rows = this.db.prepare(`
SELECT
strategy_id,
COUNT(*) as total_sent,
SUM(CASE WHEN got_response = 1 THEN 1 ELSE 0 END) as total_responses
FROM sent_messages
WHERE strategy_id IS NOT NULL
GROUP BY strategy_id
`).all() as Record<string, unknown>[];
return rows.map(row => ({
strategyId: row.strategy_id as string,
totalSent: row.total_sent as number,
totalResponses: row.total_responses as number,
responseRate: (row.total_sent as number) > 0
? Math.round(((row.total_responses as number) / (row.total_sent as number)) * 100)
: 0,
}));
}
getSubredditStats(): SubredditStats[] {
const rows = this.db.prepare(`
SELECT
pp.subreddit,
COUNT(*) as total_sent,
SUM(CASE WHEN sm.got_response = 1 THEN 1 ELSE 0 END) as total_responses
FROM sent_messages sm
JOIN processed_posts pp ON sm.post_id = pp.id
GROUP BY pp.subreddit
ORDER BY total_sent DESC
`).all() as Record<string, unknown>[];
return rows.map(row => ({
subreddit: row.subreddit as string,
totalSent: row.total_sent as number,
totalResponses: row.total_responses as number,
responseRate: (row.total_sent as number) > 0
? Math.round(((row.total_responses as number) / (row.total_sent as number)) * 100)
: 0,
}));
}
getResponseRateTrend(days: number = 30): DailyTrend[] {
const cutoff = Date.now() - (days * 24 * 60 * 60 * 1000);
const rows = this.db.prepare(`
SELECT
date(sent_at / 1000, 'unixepoch') as day,
COUNT(*) as total_sent,
SUM(CASE WHEN got_response = 1 THEN 1 ELSE 0 END) as total_responses
FROM sent_messages
WHERE sent_at > ?
GROUP BY day
ORDER BY day ASC
`).all(cutoff) as Record<string, unknown>[];
return rows.map(row => ({
date: row.day as string,
totalSent: row.total_sent as number,
totalResponses: row.total_responses as number,
responseRate: (row.total_sent as number) > 0
? Math.round(((row.total_responses as number) / (row.total_sent as number)) * 100)
: 0,
}));
}
private mapRow(row: Record<string, unknown>): SentMessageRecord {
return {
id: row.id as number,
@@ -222,7 +336,10 @@ export class MessagesRepository {
sentAt: row.sent_at as number,
gotResponse: Boolean(row.got_response),
responseCheckedAt: row.response_checked_at as number | null,
strategyId: (row.strategy_id as string) || null,
subreddit: (row.subreddit as string) || undefined,
postTitle: (row.post_title as string) || undefined,
postImageUrl: (row.post_image_url as string) || undefined,
};
}
}
@@ -230,19 +347,29 @@ export class MessagesRepository {
export class SuccessfulMessagesRepository {
private insertStmt: Database.Statement;
private getRecentStmt: Database.Statement;
private getBySubredditStmt: Database.Statement;
private countStmt: Database.Statement;
private existsByMessageStmt: Database.Statement;
constructor(private db: Database.Database) {
this.insertStmt = db.prepare(`
INSERT INTO successful_messages (sent_message_id, original_message, image_analysis, post_title, response_snippet, created_at)
VALUES (?, ?, ?, ?, ?, ?)
INSERT INTO successful_messages (sent_message_id, original_message, image_analysis, post_title, response_snippet, subreddit, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?)
`);
this.getRecentStmt = db.prepare(`
SELECT * FROM successful_messages ORDER BY created_at DESC LIMIT ?
`);
// Get examples prioritizing same subreddit, then falling back to others
this.getBySubredditStmt = db.prepare(`
SELECT *,
CASE WHEN subreddit = ? THEN 1 ELSE 0 END as subreddit_match
FROM successful_messages
ORDER BY subreddit_match DESC, created_at DESC
LIMIT ?
`);
this.countStmt = db.prepare(`SELECT COUNT(*) as count FROM successful_messages`);
this.existsByMessageStmt = db.prepare(`
@@ -256,6 +383,7 @@ export class SuccessfulMessagesRepository {
imageAnalysis: string | null;
postTitle: string | null;
responseSnippet: string | null;
subreddit: string | null;
}): void {
this.insertStmt.run(
data.sentMessageId,
@@ -263,6 +391,7 @@ export class SuccessfulMessagesRepository {
data.imageAnalysis,
data.postTitle,
data.responseSnippet,
data.subreddit,
Date.now()
);
}
@@ -273,19 +402,32 @@ export class SuccessfulMessagesRepository {
getRecent(limit: number = 5): SuccessfulMessageRecord[] {
const rows = this.getRecentStmt.all(limit) as Record<string, unknown>[];
return rows.map((row) => ({
id: row.id as number,
sentMessageId: row.sent_message_id as number,
originalMessage: row.original_message as string,
imageAnalysis: row.image_analysis as string | null,
postTitle: row.post_title as string | null,
responseSnippet: row.response_snippet as string | null,
createdAt: row.created_at as number,
}));
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<string, unknown>[];
return rows.map((row) => this.mapRow(row));
}
return this.getRecent(limit);
}
count(): number {
const result = this.countStmt.get() as { count: number };
return result.count;
}
private mapRow(row: Record<string, unknown>): SuccessfulMessageRecord {
return {
id: row.id as number,
sentMessageId: row.sent_message_id as number,
originalMessage: row.original_message as string,
imageAnalysis: row.image_analysis as string | null,
postTitle: row.post_title as string | null,
responseSnippet: row.response_snippet as string | null,
subreddit: row.subreddit as string | null,
createdAt: row.created_at as number,
};
}
}