Self-hosted, Docker-based agentic troubleshooting platform: FastAPI backend + LangGraph agent, Next.js UI, tiered LLM routing (local Ollama -> Gemini -> DeepSeek -> OpenRouter), MCP server manager, encrypted device credentials, RBAC, audit log, project-memory + Obsidian integrations, and editable troubleshooting decision rules tuned for the GeneseasX vessel stack. Co-authored-by: Cursor <cursoragent@cursor.com>
86 lines
3.2 KiB
Python
86 lines
3.2 KiB
Python
"""Serialize tasks with vessel names for API responses."""
|
|
from __future__ import annotations
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.models.inventory import Vessel
|
|
from app.models.task import Task
|
|
from app.schemas import TaskDetail, TaskOut, TaskOverviewOut
|
|
|
|
|
|
async def vessel_name_map(db: AsyncSession, vessel_ids: set[int]) -> dict[int, str]:
|
|
if not vessel_ids:
|
|
return {}
|
|
res = await db.execute(select(Vessel).where(Vessel.id.in_(vessel_ids)))
|
|
return {v.id: v.name for v in res.scalars().all()}
|
|
|
|
|
|
def task_to_out(task: Task, *, vessel_name: str | None = None) -> TaskOut:
|
|
base = TaskOut.model_validate(task)
|
|
name = vessel_name
|
|
if name is None and task.report:
|
|
name = task.report.get("vessel_name")
|
|
return base.model_copy(update={"vessel_name": name})
|
|
|
|
|
|
def task_to_detail(task: Task, *, vessel_name: str | None = None) -> TaskDetail:
|
|
base = TaskDetail.model_validate(task)
|
|
name = vessel_name
|
|
if name is None and task.report:
|
|
name = task.report.get("vessel_name")
|
|
return base.model_copy(update={"vessel_name": name})
|
|
|
|
|
|
def _report_summary(report: dict | None) -> tuple[str | None, str | None]:
|
|
if not report:
|
|
return None, None
|
|
summary = report.get("executive_summary") or report.get("summary")
|
|
findings = report.get("findings") or []
|
|
key_finding = None
|
|
if isinstance(findings, list) and findings:
|
|
first = findings[0]
|
|
if isinstance(first, dict):
|
|
key_finding = first.get("summary") or first.get("description")
|
|
else:
|
|
key_finding = str(first)
|
|
if not key_finding:
|
|
key_finding = report.get("root_cause") or report.get("resolution")
|
|
return summary, key_finding
|
|
|
|
|
|
def task_to_overview(task: Task, *, vessel_name: str | None = None) -> TaskOverviewOut:
|
|
report = task.report or {}
|
|
name = vessel_name or report.get("vessel_name")
|
|
summary, key_finding = _report_summary(report)
|
|
return TaskOverviewOut(
|
|
id=task.id,
|
|
title=task.title,
|
|
issue=task.issue,
|
|
status=task.status,
|
|
vessel_id=task.vessel_id,
|
|
vessel_name=name,
|
|
summary=summary,
|
|
key_finding=key_finding,
|
|
devices_checked=list(report.get("devices_checked") or []),
|
|
resolved=report.get("resolved"),
|
|
memory_id=task.memory_id or report.get("memory_id"),
|
|
obsidian_path=task.obsidian_path or report.get("obsidian_path"),
|
|
obsidian_push_error=report.get("obsidian_push_error"),
|
|
run_cost_usd=report.get("run_cost_usd"),
|
|
created_at=task.created_at,
|
|
updated_at=task.updated_at,
|
|
)
|
|
|
|
|
|
async def tasks_to_out_list(db: AsyncSession, tasks: list[Task]) -> list[TaskOut]:
|
|
ids = {t.vessel_id for t in tasks if t.vessel_id}
|
|
vmap = await vessel_name_map(db, ids)
|
|
return [task_to_out(t, vessel_name=vmap.get(t.vessel_id) if t.vessel_id else None) for t in tasks]
|
|
|
|
|
|
async def tasks_to_overview_list(db: AsyncSession, tasks: list[Task]) -> list[TaskOverviewOut]:
|
|
ids = {t.vessel_id for t in tasks if t.vessel_id}
|
|
vmap = await vessel_name_map(db, ids)
|
|
return [task_to_overview(t, vessel_name=vmap.get(t.vessel_id) if t.vessel_id else None) for t in tasks]
|