artifacts-dashboard/backend/app/api/logs.py
Paweł Orzech 2484a40dbd
Fix Grand Exchange: use public browse endpoint, fix price capture, add proper tabs
- Market tab now calls GET /grandexchange/orders (all public orders) instead of
  /my/grandexchange/orders (own orders only), fixing the empty exchange issue
- Fix capture_prices reading "type" field instead of wrong "order" field
- Add proper pagination to all GE queries via _get_paginated
- Separate My Orders (active own orders) from Trade History (transaction log)
- Add GEHistoryEntry type matching GeOrderHistorySchema (order_id, seller, buyer, sold_at)
- Add /api/exchange/my-orders and /api/exchange/sell-history endpoints
- Exchange page now has 4 tabs: Market, My Orders, Trade History, Price History
2026-03-01 20:38:19 +01:00

119 lines
4 KiB
Python

"""Character logs and analytics API router."""
import logging
from typing import Any
from fastapi import APIRouter, Query, Request
from sqlalchemy import select
from app.database import async_session_factory
from app.models.automation import AutomationConfig, AutomationLog, AutomationRun
from app.services.analytics_service import AnalyticsService
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/logs", tags=["logs"])
@router.get("/")
async def get_logs(
character: str = Query(default="", description="Character name to filter logs"),
limit: int = Query(default=50, ge=1, le=200, description="Max entries to return"),
) -> dict[str, Any]:
"""Get automation action logs from the database.
Joins automation_logs -> automation_runs -> automation_configs
to include character_name with each log entry.
"""
async with async_session_factory() as db:
stmt = (
select(
AutomationLog.id,
AutomationLog.action_type,
AutomationLog.details,
AutomationLog.success,
AutomationLog.created_at,
AutomationConfig.character_name,
)
.join(AutomationRun, AutomationLog.run_id == AutomationRun.id)
.join(AutomationConfig, AutomationRun.config_id == AutomationConfig.id)
.order_by(AutomationLog.created_at.desc())
.limit(limit)
)
if character:
stmt = stmt.where(AutomationConfig.character_name == character)
result = await db.execute(stmt)
rows = result.all()
return {
"logs": [
{
"id": row.id,
"character_name": row.character_name,
"action_type": row.action_type,
"details": row.details,
"success": row.success,
"created_at": row.created_at.isoformat(),
}
for row in rows
],
}
@router.get("/analytics")
async def get_analytics(
request: Request,
character: str = Query(default="", description="Character name (empty for all)"),
hours: int = Query(default=24, ge=1, le=168, description="Hours of history"),
) -> dict[str, Any]:
"""Get analytics aggregations for a character.
Returns XP history, gold history, and estimated actions per hour.
If no character is specified, aggregates across all characters with snapshots.
"""
analytics = AnalyticsService()
async with async_session_factory() as db:
if character:
characters = [character]
else:
characters = await analytics.get_tracked_characters(db)
all_xp: list[dict[str, Any]] = []
all_gold: list[dict[str, Any]] = []
total_actions_per_hour = 0.0
for char_name in characters:
xp_history = await analytics.get_xp_history(db, char_name, hours)
gold_history = await analytics.get_gold_history(db, char_name, hours)
actions_rate = await analytics.get_actions_per_hour(db, char_name)
# Transform xp_history to TimeSeriesPoint format
for point in xp_history:
all_xp.append({
"timestamp": point["timestamp"],
"value": point["xp"],
"label": f"{char_name} XP" if not character else "XP",
})
# Transform gold_history to TimeSeriesPoint format
for point in gold_history:
all_gold.append({
"timestamp": point["timestamp"],
"value": point["gold"],
"label": char_name if not character else None,
})
total_actions_per_hour += actions_rate.get("estimated_actions_per_hour", 0)
# Sort by timestamp
all_xp.sort(key=lambda p: p["timestamp"] or "")
all_gold.sort(key=lambda p: p["timestamp"] or "")
return {
"xp_history": all_xp,
"gold_history": all_gold,
"actions_per_hour": round(total_actions_per_hour, 1),
}