6.2 KiB
Immersion Tracking
SubMiner can log your watching and mining activity to a local SQLite database, then surface it in the built-in stats dashboard. Tracking is enabled by default and can be turned off if you do not want local analytics.
When enabled, SubMiner records per-session statistics (watch time, subtitle lines seen, words encountered, cards mined) and maintains daily and monthly rollups. You can view that data in SubMiner's stats UI or query the database directly with any SQLite tool.
Enabling
{
"immersionTracking": {
"enabled": true,
"dbPath": ""
}
}
- Leave
dbPathempty to use the default location (immersion.sqlitein SubMiner's app-data directory). - Set an explicit path to move the database (useful for backups, cloud syncing, or external tools).
Stats Dashboard
The same immersion data powers the stats dashboard.
- In-app overlay: focus the visible overlay, then press the key from
stats.toggleKey(default:`/Backquote). - Launcher command: run
subminer statsto start the local stats server on demand and open the dashboard in your browser. - Maintenance command: run
subminer stats cleanuporsubminer stats cleanup -vto backfill/repair vocabulary metadata (headword,reading, POS) and purge stale or excluded rows fromimm_wordson demand. - Browser page: open
http://127.0.0.1:5175directly if the local stats server is already running.
Dashboard tabs:
- Overview: recent sessions, quick totals, short-term watch-time chart
- Trends: watch time, cards mined, cards/hour, lookup hit rate over time
- Sessions: per-session breakdown with timeline and event drill-down
- Vocabulary: top words, kanji frequency, recent vocabulary growth, and click-through occurrence drilldown in a right-side drawer
Stats server config lives under stats:
{
"stats": {
"toggleKey": "Backquote",
"serverPort": 5175,
"autoStartServer": true
}
}
toggleKeyis overlay-local, not a system-wide shortcut.serverPortcontrols the localhost dashboard URL.autoStartServerstarts the local stats HTTP server on launch once immersion tracking is active.subminer statsforces the dashboard server to start even whenautoStartServerisfalse.subminer statsfails with an error whenimmersionTracking.enabledisfalse.subminer stats cleanupdefaults to vocabulary cleanup, repairs staleheadword,reading, andpart_of_speechvalues, attempts best-effort MeCab backfill for legacy rows, and removes rows that still fail vocab filtering.
Retention Defaults
Data is kept for the following durations before automatic cleanup:
| Data type | Retention |
|---|---|
| Raw events | 7 days |
| Telemetry | 30 days |
| Daily rollups | 1 year |
| Monthly rollups | 5 years |
Maintenance runs on startup and every 24 hours. Vacuum runs weekly.
Configurable Knobs
All policy options live under immersionTracking in your config:
| Option | Description |
|---|---|
batchSize |
Writes per flush batch |
flushIntervalMs |
Max delay between flushes (default: 500ms) |
queueCap |
Max queued writes before oldest are dropped |
payloadCapBytes |
Max payload size per write |
maintenanceIntervalMs |
How often maintenance runs |
retention.eventsDays |
Raw event retention |
retention.telemetryDays |
Telemetry retention |
retention.dailyRollupsDays |
Daily rollup retention |
retention.monthlyRollupsDays |
Monthly rollup retention |
retention.vacuumIntervalDays |
Minimum spacing between vacuums |
Query Templates
Session timeline
SELECT
sample_ms,
total_watched_ms,
active_watched_ms,
lines_seen,
words_seen,
tokens_seen,
cards_mined
FROM imm_session_telemetry
WHERE session_id = ?
ORDER BY sample_ms DESC, telemetry_id DESC
LIMIT ?;
Session throughput summary
SELECT
s.session_id,
s.video_id,
s.started_at_ms,
s.ended_at_ms,
COALESCE(SUM(t.active_watched_ms), 0) AS active_watched_ms,
COALESCE(SUM(t.words_seen), 0) AS words_seen,
COALESCE(SUM(t.cards_mined), 0) AS cards_mined,
CASE
WHEN COALESCE(SUM(t.active_watched_ms), 0) > 0
THEN COALESCE(SUM(t.words_seen), 0) / (COALESCE(SUM(t.active_watched_ms), 0) / 60000.0)
ELSE NULL
END AS words_per_min,
CASE
WHEN COALESCE(SUM(t.active_watched_ms), 0) > 0
THEN (COALESCE(SUM(t.cards_mined), 0) * 60.0) / (COALESCE(SUM(t.active_watched_ms), 0) / 60000.0)
ELSE NULL
END AS cards_per_hour
FROM imm_sessions s
LEFT JOIN imm_session_telemetry t ON t.session_id = s.session_id
GROUP BY s.session_id
ORDER BY s.started_at_ms DESC
LIMIT ?;
Daily rollups
SELECT
rollup_day,
video_id,
total_sessions,
total_active_min,
total_lines_seen,
total_words_seen,
total_tokens_seen,
total_cards,
cards_per_hour,
words_per_min,
lookup_hit_rate
FROM imm_daily_rollups
ORDER BY rollup_day DESC, video_id DESC
LIMIT ?;
Monthly rollups
SELECT
rollup_month,
video_id,
total_sessions,
total_active_min,
total_lines_seen,
total_words_seen,
total_tokens_seen,
total_cards
FROM imm_monthly_rollups
ORDER BY rollup_month DESC, video_id DESC
LIMIT ?;
Technical Details
- Write path is asynchronous and queue-backed. Hot paths (subtitle parsing, render, token flows) enqueue telemetry and never await SQLite writes.
- Queue overflow policy: drop oldest queued writes, keep newest.
- SQLite pragmas:
journal_mode=WAL,synchronous=NORMAL,foreign_keys=ON,busy_timeout=2500. - Rollups run incrementally from the last processed telemetry sample; startup performs a one-time bootstrap pass.
- If retention pruning removes telemetry/session rows, maintenance triggers a full rollup rebuild to resync historical aggregates.
Schema (v3)
Core tables:
imm_videos— video key/title/source metadataimm_sessions— session UUID, video reference, timing/statusimm_session_telemetry— high-frequency session aggregates over timeimm_session_events— event stream with compact numeric event types
Rollup tables:
imm_daily_rollupsimm_monthly_rollups
Vocabulary tables:
imm_words(id, headword, word, reading, first_seen, last_seen, frequency)imm_kanji(id, kanji, first_seen, last_seen, frequency)