Files
HyperNode-AI/app/services/portal.py
T

110 lines
4.7 KiB
Python

"""控制台要看的实时快照。今日积分按产出公式预览,不写入流水。"""
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