"""Report active PostgreSQL/displayed storage backend and migration parity counts.""" from __future__ import annotations import json import sqlite3 from contextlib import closing from .config import get_database_url, get_storage_path, use_postgres_rcon_storage from .rcon_admin_log_materialization import summarize_rcon_materialization_status from .rcon_admin_log_storage import initialize_rcon_admin_log_storage from .sqlite_utils import connect_sqlite_readonly MIGRATED_RCON_TABLES = ( "rcon_admin_log_events", "rcon_player_profile_snapshots", "rcon_materialized_matches", "rcon_match_player_stats", "rcon_historical_targets", "rcon_historical_samples", "rcon_historical_competitive_windows", "rcon_scoreboard_match_candidates", ) def build_storage_diagnostics() -> dict[str, object]: """Return one JSON-safe diagnostic payload for the migrated domains.""" if use_postgres_rcon_storage(): from .postgres_rcon_storage import count_migrated_tables from .postgres_display_storage import table_counts rcon_counts = count_migrated_tables() displayed_counts = table_counts() backend = "postgresql" else: rcon_counts = _count_sqlite_tables() displayed_counts = {} backend = "sqlite-fallback" materialization = summarize_rcon_materialization_status() return { "active_storage_backend": backend, "database_url_configured": bool(get_database_url()), "sqlite_fallback_path": str(get_storage_path()), "migrated_domains": [ "rcon-admin-log-events", "rcon-player-profile-snapshots", "rcon-historical-capture-samples-and-windows", "rcon-materialized-matches", "rcon-materialized-player-stats", "rcon-safe-scoreboard-candidates", "public-scoreboard-historical-matches-and-player-stats", "weekly-rankings", "monthly-rankings", "displayed-historical-snapshots", "server-summary-and-live-server-cache", "player-event-ledger", ], "table_counts": { **rcon_counts, **displayed_counts, "admin_log_events": rcon_counts.get("rcon_admin_log_events", 0), "materialized_matches": rcon_counts.get("rcon_materialized_matches", 0), "player_stats": rcon_counts.get("rcon_match_player_stats", 0), "public_scoreboard_historical_matches": displayed_counts.get( "historical_matches", 0 ), "weekly_rankings_source_stats": displayed_counts.get( "historical_player_match_stats", 0 ), "monthly_rankings_source_stats": displayed_counts.get( "historical_player_match_stats", 0 ), "server_summary_cache": displayed_counts.get("displayed_historical_snapshots", 0), "player_event_ledger": displayed_counts.get("player_event_raw_ledger", 0), "scoreboard_candidates": rcon_counts.get("rcon_scoreboard_match_candidates", 0), }, "latest_materialized_matches": materialization["latest_materialized_matches"], "latest_admin_log_match_end_events": materialization[ "latest_admin_log_match_end_events" ], "match_end_status": materialization["match_end_status"], "remaining_sqlite_or_file_backed_domains": [ { "domain": "public-scoreboard ingestion run and backfill checkpoints", "displayed_in_frontend": False, "reason": "operational import bookkeeping is not read by visible pages", "planned_phase": "phase-3-or-when-scoreboard-import-runs-on-postgresql", }, { "domain": "Elo/MMR tables", "displayed_in_frontend": False, "reason": "Elo/MMR remains paused and hidden from visible pages", "planned_phase": "phase-3", }, ], "sqlite_remaining": [ "public-scoreboard ingestion run and backfill checkpoints", "paused Elo/MMR tables", ], "scoreboard_correlation": "PostgreSQL safe candidates and migrated trusted historical match URLs are used.", "external_player_ids": _postgres_external_player_id_diagnostics() if backend == "postgresql" else { "available_in_postgresql": False, "reason": "PostgreSQL storage is not active.", }, "migration_parity_summary": { "available": backend == "postgresql", "source_command": "python -m app.sqlite_to_postgres_migration", "displayed_historical_storage": ( "postgresql" if backend == "postgresql" else "sqlite-or-file-fallback" ), }, } def _count_sqlite_tables() -> dict[str, int]: resolved_path = initialize_rcon_admin_log_storage() counts: dict[str, int] = {} with closing(connect_sqlite_readonly(resolved_path)) as connection: for table_name in MIGRATED_RCON_TABLES: try: row = connection.execute( f"SELECT COUNT(*) AS count FROM {table_name}" ).fetchone() except sqlite3.Error: counts[table_name] = 0 else: counts[table_name] = int(row["count"] or 0) return counts def _postgres_external_player_id_diagnostics() -> dict[str, object]: from .postgres_rcon_storage import connect_postgres with connect_postgres() as connection: row = connection.execute( """ SELECT (SELECT COUNT(*) FROM rcon_match_player_stats WHERE player_id ~ '^[0-9]{17}$') AS rcon_match_steam_id64_rows, (SELECT COUNT(*) FROM rcon_player_profile_snapshots WHERE player_id ~ '^[0-9]{17}$') AS rcon_profile_steam_id64_rows, (SELECT COUNT(*) FROM historical_players WHERE steam_id ~ '^[0-9]{17}$') AS scoreboard_player_steam_id64_rows """ ).fetchone() return { "available_in_postgresql": True, "rcon_match_steam_id64_rows": int(row["rcon_match_steam_id64_rows"] or 0), "rcon_profile_steam_id64_rows": int(row["rcon_profile_steam_id64_rows"] or 0), "scoreboard_player_steam_id64_rows": int( row["scoreboard_player_steam_id64_rows"] or 0 ), } def main() -> None: print(json.dumps(build_storage_diagnostics(), ensure_ascii=False, indent=2, default=str)) if __name__ == "__main__": main()