Initial commit: Agentic OS troubleshooting platform

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>
This commit is contained in:
2026-06-14 22:11:07 +03:00
commit 6185b9b85a
126 changed files with 14565 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
"""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]