The 489-line method ran ten independent sections in sequence (contacts, incoming packets, adverts, repeaters, caches, messages, commands, paths, network health, geography), each reading only the cursor, the table list, the stats dict and its window arguments. Each section is now a `_stats_*` method called in the same order inside the same connection and try; their statements are AST-identical to the originals, SQL strings included.
_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.