"""控制台要看的实时快照。今日积分按产出公式预览,不写入流水。""" from __future__ import annotations from decimal import Decimal from sqlalchemy import func, select from sqlalchemy.orm import Session, selectinload from app.clock import utcnow from app.models import ComputeNode, NodeStatus, NodeYield, User, WithdrawalLog, WithdrawalStatus, YieldStatus from app.money import money, to_decimal from app.services.hardware import display_node_id, displayed_population, is_genesis from app.services.withdrawals import available_balance from app.services.yield_engine import network_yields def settled_points(session: Session, user_id: int | None = None) -> Decimal: query = select(NodeYield.points_delta).where(NodeYield.status == YieldStatus.SETTLED) if user_id is not None: query = query.where(NodeYield.user_id == user_id) rows = session.scalars(query).all() return money(sum((to_decimal(item) for item in rows), Decimal("0"))) def pending_withdrawal_amount(session: Session) -> Decimal: rows = session.scalars( select(WithdrawalLog.amount).where(WithdrawalLog.status == WithdrawalStatus.PENDING) ).all() return money(sum((to_decimal(item) for item in rows), Decimal("0"))) def authorized_amount(session: Session) -> Decimal: rows = session.scalars(select(ComputeNode.purchase_amount)).all() return money(sum((to_decimal(item) for item in rows), Decimal("0"))) def node_counts(session: Session) -> tuple[int, int]: nodes = session.scalar(select(func.count()).select_from(ComputeNode)) or 0 online = session.scalar( select(func.count()).select_from(ComputeNode).where(ComputeNode.status == NodeStatus.ONLINE) ) or 0 return nodes, online def live_nodes(session: Session, user_id: int | None = None) -> list[dict]: query = select(ComputeNode).options(selectinload(ComputeNode.user)).order_by(ComputeNode.id.desc()) if user_id is not None: query = query.where(ComputeNode.user_id == user_id) today = utcnow().date() scored = {node.id: computed for node, computed in network_yields(session, today)} snapshots = [] for node in session.scalars(query).all(): computed = scored.get(node.id) snapshots.append( { "id": node.id, "sn": node.sn, "node_index": node.node_index, "display_node_id": display_node_id(node.node_index), "genesis": is_genesis(node.node_index), "user_id": node.user_id, "wallet_address": node.user.wallet_address, "purchase_amount": node.purchase_amount, "license_months": node.license_months, "licensed_at": node.licensed_at, "license_expires_at": node.license_expires_at, "status": node.status, "last_heartbeat_at": node.last_heartbeat_at, "callback_url": node.callback_url, "node_token": node.node_token if user_id is not None else None, "activated": node.node_token is not None, "online_rate": computed.online_rate if computed is not None else Decimal("0"), "today_points": computed.points_delta if computed is not None and computed.eligible else Decimal("0"), "gpu_model": node.gpu_model, "vram_gb": node.vram_gb, "cpu_count": node.cpu_count, "memory_gb": node.memory_gb, "hardware_weight": node.hardware_weight if node.hardware_weight is not None else 0.05, "gpu_generation": node.gpu_generation or "10", "recovery_days": node.recovery_days or 540, "generation_locked": bool(node.generation_locked), "recoverable_stake_amount": node.recoverable_stake_amount, "is_whitelist": bool(node.is_whitelist), "client_ip": node.client_ip, "hardware_fingerprint": node.hardware_fingerprint, } ) return snapshots def user_rows(session: Session) -> list[dict]: counts = dict( session.execute(select(ComputeNode.user_id, func.count()).group_by(ComputeNode.user_id)).all() ) rows = [] for user in session.scalars(select(User).order_by(User.id.desc())).all(): rows.append( { "id": user.id, "wallet_address": user.wallet_address, "username": user.username, "role": user.role, "created_at": user.created_at, "node_count": int(counts.get(user.id, 0)), "available_points": available_balance(session, user.id), } ) return rows