( db: DatabaseAdapter, segmentIds: number[], memberNames: Map<number, string>, perseveranceThreshold: number )
| 208 | } |
| 209 | |
| 210 | function queryMessageLevelStats( |
| 211 | db: DatabaseAdapter, |
| 212 | segmentIds: number[], |
| 213 | memberNames: Map<number, string>, |
| 214 | perseveranceThreshold: number |
| 215 | ): { |
| 216 | responseLatency: ResponseLatencyMember[] |
| 217 | perseverance: PerseveranceMember[] |
| 218 | totalDoubleTexts: number |
| 219 | monthlyResponseLatency: MonthlyResponseLatency[] |
| 220 | monthlyPerseverance: MonthlyPerseverance[] |
| 221 | } { |
| 222 | const empty = { |
| 223 | responseLatency: [], |
| 224 | perseverance: [], |
| 225 | totalDoubleTexts: 0, |
| 226 | monthlyResponseLatency: [], |
| 227 | monthlyPerseverance: [], |
| 228 | } |
| 229 | if (segmentIds.length === 0) return empty |
| 230 | |
| 231 | const BATCH_SIZE = 500 |
| 232 | const responseTotals = new Map<number, { sum: number; count: number }>() |
| 233 | const dtTotals = new Map<number, number>() |
| 234 | const monthlyRespMap = new Map<string, Map<number, { sum: number; count: number }>>() |
| 235 | const monthlyDtMap = new Map<string, Map<number, number>>() |
| 236 | |
| 237 | for (let i = 0; i < segmentIds.length; i += BATCH_SIZE) { |
| 238 | const batch = segmentIds.slice(i, i + BATCH_SIZE) |
| 239 | const placeholders = batch.map(() => '?').join(',') |
| 240 | |
| 241 | const latencyRows = db |
| 242 | .prepare( |
| 243 | `WITH msg_lag AS ( |
| 244 | SELECT m.sender_id, m.ts, |
| 245 | LAG(m.sender_id) OVER (PARTITION BY mc.segment_id ORDER BY m.ts, m.id) AS prev_sender_id, |
| 246 | LAG(m.ts) OVER (PARTITION BY mc.segment_id ORDER BY m.ts, m.id) AS prev_ts |
| 247 | FROM message_context mc JOIN message m ON m.id = mc.message_id |
| 248 | WHERE mc.segment_id IN (${placeholders}) AND m.type = 0 |
| 249 | ) |
| 250 | SELECT strftime('%Y-%m', datetime(ts, 'unixepoch', 'localtime')) AS month, |
| 251 | sender_id AS responder_id, SUM(ts - prev_ts) AS total_time, COUNT(*) AS response_count |
| 252 | FROM msg_lag WHERE prev_sender_id IS NOT NULL AND sender_id != prev_sender_id |
| 253 | GROUP BY month, responder_id` |
| 254 | ) |
| 255 | .all(...batch) as Array<{ month: string; responder_id: number; total_time: number; response_count: number }> |
| 256 | |
| 257 | for (const row of latencyRows) { |
| 258 | const existing = responseTotals.get(row.responder_id) |
| 259 | if (existing) { |
| 260 | existing.sum += row.total_time |
| 261 | existing.count += row.response_count |
| 262 | } else responseTotals.set(row.responder_id, { sum: row.total_time, count: row.response_count }) |
| 263 | |
| 264 | if (!monthlyRespMap.has(row.month)) monthlyRespMap.set(row.month, new Map()) |
| 265 | const mMap = monthlyRespMap.get(row.month)! |
| 266 | const mExisting = mMap.get(row.responder_id) |
| 267 | if (mExisting) { |
no test coverage detected