( db: DatabaseAdapter, filter?: TimeFilter, options?: RelationshipOptions )
| 65 | const DEFAULT_PERSEVERANCE_THRESHOLD = 300 |
| 66 | |
| 67 | export function getRelationshipStats( |
| 68 | db: DatabaseAdapter, |
| 69 | filter?: TimeFilter, |
| 70 | options?: RelationshipOptions |
| 71 | ): RelationshipStats { |
| 72 | const perseveranceThreshold = options?.perseveranceThreshold ?? DEFAULT_PERSEVERANCE_THRESHOLD |
| 73 | const emptyResult: RelationshipStats = { |
| 74 | months: [], |
| 75 | members: [], |
| 76 | totalSessions: 0, |
| 77 | hasSessionIndex: false, |
| 78 | iceBreakers: [], |
| 79 | totalIceBreaks: 0, |
| 80 | responseLatency: [], |
| 81 | perseverance: [], |
| 82 | totalDoubleTexts: 0, |
| 83 | monthlyResponseLatency: [], |
| 84 | monthlyPerseverance: [], |
| 85 | perseveranceThreshold, |
| 86 | } |
| 87 | |
| 88 | const sessionCount = db.prepare('SELECT COUNT(*) as count FROM segment').get() as { count: number } | undefined |
| 89 | if (!sessionCount || sessionCount.count === 0) return emptyResult |
| 90 | |
| 91 | const timeConditions: string[] = [] |
| 92 | const params: (number | string)[] = [] |
| 93 | |
| 94 | if (filter?.startTs !== undefined) { |
| 95 | timeConditions.push('cs.start_ts >= ?') |
| 96 | params.push(filter.startTs) |
| 97 | } |
| 98 | if (filter?.endTs !== undefined) { |
| 99 | timeConditions.push('cs.start_ts <= ?') |
| 100 | params.push(filter.endTs) |
| 101 | } |
| 102 | |
| 103 | const whereClause = timeConditions.length > 0 ? `WHERE ${timeConditions.join(' AND ')}` : '' |
| 104 | |
| 105 | const segmentRows = db |
| 106 | .prepare( |
| 107 | `SELECT cs.id AS segment_id, cs.start_ts, cs.end_ts, |
| 108 | (SELECT m.sender_id FROM message_context mc JOIN message m ON m.id = mc.message_id |
| 109 | WHERE mc.segment_id = cs.id ORDER BY m.ts ASC, m.id ASC LIMIT 1) AS initiator_id, |
| 110 | (SELECT m.sender_id FROM message_context mc JOIN message m ON m.id = mc.message_id |
| 111 | WHERE mc.segment_id = cs.id ORDER BY m.ts DESC, m.id DESC LIMIT 1) AS closer_id |
| 112 | FROM segment cs ${whereClause} ORDER BY cs.start_ts ASC` |
| 113 | ) |
| 114 | .all(...params) as Array<{ |
| 115 | segment_id: number |
| 116 | start_ts: number |
| 117 | end_ts: number |
| 118 | initiator_id: number | null |
| 119 | closer_id: number | null |
| 120 | }> |
| 121 | |
| 122 | const memberNames = new Map<number, string>() |
| 123 | const memberRows = db |
| 124 | .prepare('SELECT id, COALESCE(group_nickname, account_name, platform_id) as name FROM member') |
no test coverage detected