diff --git a/scripts/fix-wpm.mjs b/scripts/fix-wpm.mjs new file mode 100644 index 0000000..3b4d273 --- /dev/null +++ b/scripts/fix-wpm.mjs @@ -0,0 +1,31 @@ +#!/usr/bin/env node +// One-time migration: zero out WPM for sessions that are clearly paste artifacts. +// A session is flagged when activeMs < 5000 (sub-5-second burst) and the resulting +// WPM exceeds 300 — no human types that fast. + +import { readFileSync, writeFileSync, cpSync } from 'fs' + +const TELEMETRY = `${process.env.HOME}/Library/Application Support/Borges/.borges/telemetry.json` +const BACKUP = TELEMETRY + '.bak' + +const sessions = JSON.parse(readFileSync(TELEMETRY, 'utf-8')) + +let fixed = 0 +const patched = sessions.map(s => { + if (s.wpm > 300 && s.activeMs < 5000) { + fixed++ + console.log(`Fixing session ${s.id} (${s.storyId}): wpm ${s.wpm} → 0 [activeMs=${s.activeMs}, words ${s.wordsStart}→${s.wordsEnd}]`) + return { ...s, wpm: 0 } + } + return s +}) + +if (fixed === 0) { + console.log('No sessions to fix.') + process.exit(0) +} + +cpSync(TELEMETRY, BACKUP) +console.log(`Backed up to ${BACKUP}`) +writeFileSync(TELEMETRY, JSON.stringify(patched, null, 2), 'utf-8') +console.log(`Done. Fixed ${fixed} session(s).`) diff --git a/scripts/merge-sessions.mjs b/scripts/merge-sessions.mjs new file mode 100644 index 0000000..ac0e248 --- /dev/null +++ b/scripts/merge-sessions.mjs @@ -0,0 +1,58 @@ +#!/usr/bin/env node +// One-time migration: merge consecutive same-story sessions that were split by +// the blur-flush bug (every tab-out ended the session prematurely). +// Sessions of the same story on the same date with a gap < GAP_MS are merged. + +import { readFileSync, writeFileSync, copyFileSync } from 'fs' + +const GAP_MS = 10 * 60 * 1000 // 10-minute gap = new session +const TELEMETRY = `${process.env.HOME}/Library/Application Support/Borges/.borges/telemetry.json` +const BACKUP = TELEMETRY + '.merge-bak' + +const sessions = JSON.parse(readFileSync(TELEMETRY, 'utf-8')) + +// Sort chronologically so we can walk them in order +const sorted = [...sessions].sort((a, b) => a.startedAt - b.startedAt) + +const merged = [] +let mergeCount = 0 + +for (const s of sorted) { + const prev = merged[merged.length - 1] + + const sameStory = prev && prev.storyId === s.storyId + const sameDate = prev && prev.date === s.date + const gapMs = prev ? s.startedAt - prev.endedAt : Infinity + + if (sameStory && sameDate && gapMs < GAP_MS) { + // Merge s into prev + const combinedActiveMs = prev.activeMs + s.activeMs + const typedWords = s.wordsEnd - prev.wordsStart + const wpm = combinedActiveMs > 0 + ? Math.round(Math.max(0, typedWords) / (combinedActiveMs / 60_000)) + : 0 + + console.log( + `Merging ${s.id} into ${prev.id} (${prev.storyId}) ` + + `gap=${(gapMs / 60000).toFixed(1)}m words ${prev.wordsStart}→${s.wordsEnd} wpm ${prev.wpm}→${wpm}` + ) + + prev.endedAt = s.endedAt + prev.wordsEnd = s.wordsEnd + prev.activeMs = combinedActiveMs + prev.wpm = wpm + mergeCount++ + } else { + merged.push({ ...s }) + } +} + +if (mergeCount === 0) { + console.log('No sessions to merge.') + process.exit(0) +} + +copyFileSync(TELEMETRY, BACKUP) +console.log(`\nBacked up to ${BACKUP}`) +writeFileSync(TELEMETRY, JSON.stringify(merged, null, 2), 'utf-8') +console.log(`Done. Merged ${mergeCount} session(s) → ${merged.length} total (was ${sessions.length}).`)