Three self-contained blocks of the 529-line method become `_tracking_where` (the WHERE clause and parameters for the contact filters, plus the path-bytes expression the sort reuses), `_tracking_entries` (one response entry per contact row) and `_tracking_server_stats` (advert, node and daily per-role counts). Their statements are AST-identical to the originals and run in the same order inside the same connection and try; no name they assign is read afterwards except the three the filter builder returns.
_get_tracking_data, _get_contact_detail and _calculate_distance move to modules/web_viewer/tracking.py (ContactTrackingMixin); the database statistics, table info and description, optimization and cache view move to modules/web_viewer/database_admin.py (DatabaseAdminMixin). All nine bodies are verbatim; BotDataViewer inherits both. tracking.py joins app.py on mypy's suppressed list, since the moved code carries the type errors mypy never checked in app.py. app.py keeps validate_sql_identifier and humanize_span imported so those module attributes still resolve. No test patched anything these methods use. app.py drops to about 5,800 lines, from 9,095 at the start of the refactor.