cyb/src/pages/Mining/buildMiningReport.ts

import type { WindowStatusResponse, ConfigResponse } from 'src/generated/lithium/LitiumMine.types';

type ProofLogEntry = {
  hash: string;
  nonce: number;
  txHash?: string;
  error?: string;
  status?: 'submitted' | 'pending' | 'success' | 'failed' | 'retrying';
  timestamp: number;
  reward?: number;
};

export type ReportParams = {
  isNative: boolean;
  address: string | undefined;
  referrer: string | null;
  liBalance: number;
  autoMining: boolean;
  userDifficulty: number;
  minDifficulty: number;
  threadCount: number;
  backend: string;
  availableBackends: string[];
  hashrate: number;
  totalHashes: number;
  elapsed: number;
  pendingProofs: number;
  sessionLiMined: number;
  tauriStatus: Record<string, unknown> | null;
  tauriParams: Record<string, unknown> | null;
  latestBlock: { height: number; blockHash: string; timestamp: number } | null;
  wsConnected: boolean;
  samples: number[];
  config: ConfigResponse | undefined;
  windowStatus: WindowStatusResponse | undefined;
  uniqueMiners: number;
  totalProofs: number;
  avgDifficulty: number;
  dRate: number;
  similarDevices: number;
  windowEntries: number;
  baseRate: number;
  rewardPerProof: number;
  grossRewardPerProof: number;
  estimatedLiPerHour: number;
  rpcOnline: boolean;
  retryQueueSize: number;
  consecutiveFails: number;
  proofLog: ProofLogEntry[];
  sessionStart: number;
  actionLog?: { action: string; detail?: string; timestamp: number; result?: string; error?: string }[];
};

export function buildMiningReport(params: ReportParams): object {
  const sessionLog = params.proofLog.filter(
    (p) => p.timestamp >= params.sessionStart
  );
  const accepted = sessionLog.filter((p) => p.status === 'success').length;
  const failed = sessionLog.filter(
    (p) => p.status === 'failed' || (p.error && !p.status)
  ).length;
  const pending = sessionLog.filter(
    (p) => p.status === 'submitted' || p.status === 'pending'
  ).length;
  const total = accepted + failed;

  const nav = navigator as Navigator & { deviceMemory?: number };
  const device = {
    cpu_cores: navigator.hardwareConcurrency || null,
    device_memory_gb: nav.deviceMemory || null,
    platform: navigator.platform || null,
    max_touch_points: navigator.maxTouchPoints,
  };

  return {
    exported_at: new Date().toISOString(),
    user_agent: navigator.userAgent,
    platform: params.isNative ? 'tauri' : 'web',
    address: params.address || null,
    referrer: params.referrer || null,
    li_balance: params.liBalance,
    device,
    mining: {
      active: params.autoMining,
      difficulty: params.userDifficulty,
      min_difficulty: params.minDifficulty,
      threads: params.threadCount,
      backend: params.backend,
      available_backends: params.availableBackends,
      hashrate: params.hashrate,
      total_hashes: params.totalHashes,
      elapsed_secs: params.elapsed,
      pending_proofs: params.pendingProofs,
      session_li_mined: params.sessionLiMined,
      batch_count: (params.tauriStatus?.batch_count as number) ?? null,
      avg_batch_ms: (params.tauriStatus?.avg_batch_ms as number) ?? null,
      proofs_submitted:
        (params.tauriStatus?.proofs_submitted as number) ?? null,
      proofs_failed: (params.tauriStatus?.proofs_failed as number) ?? null,
    },
    uhash_params: params.tauriParams
      ? {
          chains: params.tauriParams.chains,
          scratchpad_kb: params.tauriParams.scratchpad_kb,
          total_mb: params.tauriParams.total_mb,
          rounds: params.tauriParams.rounds,
          block_size: params.tauriParams.block_size,
        }
      : null,
    block: params.latestBlock
      ? {
          height: params.latestBlock.height,
          hash: params.latestBlock.blockHash,
          timestamp: params.latestBlock.timestamp,
        }
      : null,
    ws_connected: params.wsConnected,
    hashrate_samples: params.samples,
    contract_config: params.config || null,
    window_status: params.windowStatus || null,
    network: {
      unique_miners: params.uniqueMiners,
      total_proofs: params.totalProofs,
      avg_difficulty: params.avgDifficulty,
      d_rate: params.dRate,
      similar_devices: params.similarDevices,
      window_proof_count: params.windowEntries,
      base_rate: params.baseRate,
    },
    reward_estimate: {
      per_proof: params.rewardPerProof,
      gross_per_proof: params.grossRewardPerProof,
      li_per_hour: params.estimatedLiPerHour,
    },
    connection: {
      rpc_online: params.rpcOnline,
      retry_queue_size: params.retryQueueSize,
      consecutive_fails: params.consecutiveFails,
    },
    proof_summary: {
      total: sessionLog.length,
      accepted,
      failed,
      pending,
      retrying: sessionLog.filter((p) => p.status === 'retrying').length,
      success_rate:
        total > 0 ? `${((accepted / total) * 100).toFixed(1)}%` : null,
    },
    proof_log: sessionLog,
    action_log: (params.actionLog || []).filter(a => a.timestamp >= params.sessionStart),
  };
}

Neighbours