feat: track received image outcomes
This commit is contained in:
1 parent
50de1c180d
commit
d819a0f1e6
15 files changed
+145
-21
No files matched your search
@@ -187,6 +187,24 @@ export async function bootstrap(
|
||||
const mediaDownloader = new MatrixMediaDownloader(activeMatrixClient, logger);
|
||||
const mediaService = new MediaService(mediaDownloader, logger);
|
||||
|
||||
const indexedMedia = await mediaService.getMediaItems();
|
||||
const receivedImageRooms = new Set(
|
||||
indexedMedia
|
||||
.filter((item) => item.sender.toLowerCase() !== 'me' && item.chatRoomId)
|
||||
.map((item) => item.chatRoomId as string),
|
||||
);
|
||||
let backfilledImageOutcomes = 0;
|
||||
for (const roomId of receivedImageRooms) {
|
||||
const message = messagesRepo.getByChatRoomId(roomId);
|
||||
const hasPerformanceContext = Boolean(message?.postId || message?.strategyId || message?.subreddit);
|
||||
if (!message || !hasPerformanceContext || message.gotImage) continue;
|
||||
messagesRepo.markGotImage(message.id);
|
||||
backfilledImageOutcomes += 1;
|
||||
}
|
||||
if (backfilledImageOutcomes > 0) {
|
||||
logger.info(`Backfilled ${backfilledImageOutcomes} received-image performance outcome(s) from the media index`);
|
||||
}
|
||||
|
||||
// A 401 waits for a new extension dump instead of opening a browser login
|
||||
// or token-sniff flow.
|
||||
const tokenRefresher = new MatrixTokenRefresher();
|
||||
|
||||
@@ -192,6 +192,12 @@ export function runMigrations(db: Database.Database): void {
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
`),
|
||||
() => {
|
||||
addColumnIfNotExists(db, 'sent_messages', 'got_image', 'INTEGER NOT NULL DEFAULT 0');
|
||||
db.exec(`
|
||||
CREATE INDEX IF NOT EXISTS idx_sent_messages_image ON sent_messages(got_image);
|
||||
`);
|
||||
},
|
||||
];
|
||||
|
||||
let currentVersion = db.pragma('user_version', { simple: true }) as number;
|
||||
|
||||
@@ -28,6 +28,7 @@ export interface SentMessageRecord {
|
||||
message: string;
|
||||
sentAt: number;
|
||||
gotResponse: boolean;
|
||||
gotImage: boolean;
|
||||
responseCheckedAt: number | null;
|
||||
strategyId: string | null;
|
||||
chatRoomId: string | null;
|
||||
@@ -47,6 +48,7 @@ export interface SuccessfulMessageRecord {
|
||||
responseSnippet: string | null;
|
||||
subreddit: string | null;
|
||||
createdAt: number;
|
||||
gotImage?: boolean;
|
||||
}
|
||||
|
||||
export interface StrategyStats {
|
||||
@@ -54,6 +56,8 @@ export interface StrategyStats {
|
||||
totalSent: number;
|
||||
totalResponses: number;
|
||||
responseRate: number;
|
||||
totalImages: number;
|
||||
imageRate: number;
|
||||
}
|
||||
|
||||
export interface SubredditStats {
|
||||
@@ -61,6 +65,8 @@ export interface SubredditStats {
|
||||
totalSent: number;
|
||||
totalResponses: number;
|
||||
responseRate: number;
|
||||
totalImages: number;
|
||||
imageRate: number;
|
||||
}
|
||||
|
||||
export interface DailyTrend {
|
||||
@@ -68,6 +74,8 @@ export interface DailyTrend {
|
||||
totalSent: number;
|
||||
totalResponses: number;
|
||||
responseRate: number;
|
||||
totalImages: number;
|
||||
imageRate: number;
|
||||
}
|
||||
|
||||
export interface ActivityLogRecord {
|
||||
@@ -417,6 +425,7 @@ export class MessagesRepository {
|
||||
private getRecentStmt: Database.Statement;
|
||||
private getUncheckedStmt: Database.Statement;
|
||||
private markResponseStmt: Database.Statement;
|
||||
private markImageStmt: Database.Statement;
|
||||
private markCheckedStmt: Database.Statement;
|
||||
private hasSentToStmt: Database.Statement;
|
||||
|
||||
@@ -475,6 +484,12 @@ export class MessagesRepository {
|
||||
UPDATE sent_messages SET got_response = 1, response_checked_at = ? WHERE id = ?
|
||||
`);
|
||||
|
||||
this.markImageStmt = db.prepare(`
|
||||
UPDATE sent_messages
|
||||
SET got_image = 1, got_response = 1, response_checked_at = ?
|
||||
WHERE id = ?
|
||||
`);
|
||||
|
||||
this.markCheckedStmt = db.prepare(`
|
||||
UPDATE sent_messages SET response_checked_at = ? WHERE id = ?
|
||||
`);
|
||||
@@ -565,6 +580,10 @@ export class MessagesRepository {
|
||||
this.markResponseStmt.run(Date.now(), messageId);
|
||||
}
|
||||
|
||||
markGotImage(messageId: number): void {
|
||||
this.markImageStmt.run(Date.now(), messageId);
|
||||
}
|
||||
|
||||
markChecked(messageId: number): void {
|
||||
this.markCheckedStmt.run(Date.now(), messageId);
|
||||
}
|
||||
@@ -578,7 +597,8 @@ export class MessagesRepository {
|
||||
SELECT
|
||||
strategy_id,
|
||||
COUNT(*) as total_sent,
|
||||
SUM(CASE WHEN got_response = 1 THEN 1 ELSE 0 END) as total_responses
|
||||
SUM(CASE WHEN got_response = 1 THEN 1 ELSE 0 END) as total_responses,
|
||||
SUM(CASE WHEN got_image = 1 THEN 1 ELSE 0 END) as total_images
|
||||
FROM sent_messages
|
||||
WHERE strategy_id IS NOT NULL
|
||||
GROUP BY strategy_id
|
||||
@@ -591,6 +611,10 @@ export class MessagesRepository {
|
||||
responseRate: (row.total_sent as number) > 0
|
||||
? Math.round(((row.total_responses as number) / (row.total_sent as number)) * 100)
|
||||
: 0,
|
||||
totalImages: row.total_images as number,
|
||||
imageRate: (row.total_sent as number) > 0
|
||||
? Math.round(((row.total_images as number) / (row.total_sent as number)) * 100)
|
||||
: 0,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -599,7 +623,8 @@ export class MessagesRepository {
|
||||
SELECT
|
||||
pp.subreddit,
|
||||
COUNT(*) as total_sent,
|
||||
SUM(CASE WHEN sm.got_response = 1 THEN 1 ELSE 0 END) as total_responses
|
||||
SUM(CASE WHEN sm.got_response = 1 THEN 1 ELSE 0 END) as total_responses,
|
||||
SUM(CASE WHEN sm.got_image = 1 THEN 1 ELSE 0 END) as total_images
|
||||
FROM sent_messages sm
|
||||
JOIN processed_posts pp ON sm.post_id = pp.id
|
||||
GROUP BY pp.subreddit
|
||||
@@ -613,6 +638,10 @@ export class MessagesRepository {
|
||||
responseRate: (row.total_sent as number) > 0
|
||||
? Math.round(((row.total_responses as number) / (row.total_sent as number)) * 100)
|
||||
: 0,
|
||||
totalImages: row.total_images as number,
|
||||
imageRate: (row.total_sent as number) > 0
|
||||
? Math.round(((row.total_images as number) / (row.total_sent as number)) * 100)
|
||||
: 0,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -622,7 +651,8 @@ export class MessagesRepository {
|
||||
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
|
||||
SUM(CASE WHEN got_response = 1 THEN 1 ELSE 0 END) as total_responses,
|
||||
SUM(CASE WHEN got_image = 1 THEN 1 ELSE 0 END) as total_images
|
||||
FROM sent_messages
|
||||
WHERE sent_at > ?
|
||||
AND (post_id IS NOT NULL OR strategy_id IS NOT NULL)
|
||||
@@ -637,6 +667,10 @@ export class MessagesRepository {
|
||||
responseRate: (row.total_sent as number) > 0
|
||||
? Math.round(((row.total_responses as number) / (row.total_sent as number)) * 100)
|
||||
: 0,
|
||||
totalImages: row.total_images as number,
|
||||
imageRate: (row.total_sent as number) > 0
|
||||
? Math.round(((row.total_images as number) / (row.total_sent as number)) * 100)
|
||||
: 0,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -649,6 +683,7 @@ export class MessagesRepository {
|
||||
message: row.message as string,
|
||||
sentAt: row.sent_at as number,
|
||||
gotResponse: Boolean(row.got_response),
|
||||
gotImage: Boolean(row.got_image),
|
||||
responseCheckedAt: row.response_checked_at as number | null,
|
||||
strategyId: (row.strategy_id as string) || null,
|
||||
chatRoomId: (row.chat_room_id as string) ?? null,
|
||||
@@ -680,7 +715,7 @@ export class SuccessfulMessagesRepository {
|
||||
`);
|
||||
|
||||
this.getAttributedRecentStmt = db.prepare(`
|
||||
SELECT successful_messages.*
|
||||
SELECT successful_messages.*, COALESCE(sm.got_image, 0) AS got_image
|
||||
FROM successful_messages
|
||||
LEFT JOIN sent_messages sm ON sm.id = successful_messages.sent_message_id
|
||||
WHERE successful_messages.subreddit IS NOT NULL
|
||||
@@ -762,6 +797,7 @@ export class SuccessfulMessagesRepository {
|
||||
responseSnippet: row.response_snippet as string | null,
|
||||
subreddit: row.subreddit as string | null,
|
||||
createdAt: row.created_at as number,
|
||||
...(row.got_image === undefined ? {} : { gotImage: Boolean(row.got_image) }),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -737,6 +737,9 @@ export class ChatService {
|
||||
try {
|
||||
const withMedia = await this.mediaService.attachMedia(chat, mediaMessages);
|
||||
this.mergeNewMessages(chat, withMedia);
|
||||
if (withMedia.some((message) => !message.isSelf && (message.media?.length ?? 0) > 0)) {
|
||||
this.recordReceivedImage(chat);
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.warn(
|
||||
`[chat] delta media attach failed for ${roomId}: ${(err as Error).message}`,
|
||||
@@ -848,6 +851,9 @@ export class ChatService {
|
||||
if (firstSelf) chat.originMessage = firstSelf.text;
|
||||
|
||||
const hasReply = chat.messages.some((m) => !m.isSelf);
|
||||
const hasReceivedImage = chat.messages.some(
|
||||
(message) => !message.isSelf && (message.media?.length ?? 0) > 0,
|
||||
);
|
||||
let performanceMessage: SentMessageRecord | undefined;
|
||||
|
||||
if (firstSelf && chat.roomId && this.messagesRepo) {
|
||||
@@ -861,6 +867,9 @@ export class ChatService {
|
||||
if (hasReply && !existing.gotResponse) {
|
||||
this.messagesRepo.markGotResponse(existing.id);
|
||||
}
|
||||
if (hasReceivedImage && !existing.gotImage) {
|
||||
this.messagesRepo.markGotImage(existing.id);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.warn('[chat] failed to associate reply with performance context:', err);
|
||||
@@ -897,6 +906,20 @@ export class ChatService {
|
||||
}
|
||||
}
|
||||
|
||||
private recordReceivedImage(chat: ActiveChat): void {
|
||||
if (!chat.roomId || !this.messagesRepo) return;
|
||||
try {
|
||||
const existing = this.messagesRepo.getByChatRoomId(chat.roomId);
|
||||
const hasPerformanceContext = Boolean(
|
||||
existing?.postId || existing?.strategyId || existing?.subreddit,
|
||||
);
|
||||
if (!existing || !hasPerformanceContext) return;
|
||||
if (!existing.gotImage) this.messagesRepo.markGotImage(existing.id);
|
||||
} catch (err) {
|
||||
this.logger.warn('[chat] failed to associate received image with performance context:', err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Project a Matrix room (from /sync) into an ActiveChat. If `prev` is
|
||||
* supplied, fields it carries (messages, pendingReply, originMessage)
|
||||
|
||||
@@ -83,6 +83,8 @@ export class StrategySelector {
|
||||
totalSent: s?.totalSent || 0,
|
||||
totalResponses: s?.totalResponses || 0,
|
||||
responseRate: s?.responseRate || 0,
|
||||
totalImages: s?.totalImages || 0,
|
||||
imageRate: s?.imageRate || 0,
|
||||
configured: true,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user