文件预览

diagnostics.js

查看 FTTR Copilot 技能包中的文件内容。

文件内容

src/tools/diagnostics.js

import { listDeviceAlerts } from "./alerts.js";
import { getDeviceDetail, listMyDevices } from "./devices.js";
import { getNetworkExperience, getNetworkTopology, getStationStats } from "./network.js";

export async function diagnoseDeviceOffline(client, args = {}) {
  const deviceIdentifier = requiredString(args.device_identifier, "device_identifier");
  const detail = await getDeviceDetail(client, { device_identifier: deviceIdentifier });
  const alerts = await listDeviceAlerts(client, { event_type: "ALERT", limit: args.alert_limit || 10 });
  const relatedAlerts = filterAlertsForDevice(alerts.data.alerts, detail.data.detail);

  return {
    title: "设备离线诊断",
    summary: buildOfflineSummary(detail.data.detail, relatedAlerts),
    data: {
      device_identifier: deviceIdentifier,
      device_detail: compactResult(detail),
      recent_alerts: relatedAlerts,
      checked_scope: "customer_bound_devices",
    },
    suggestions: buildOfflineSuggestions(detail.data.detail, relatedAlerts),
    trace: [
      stepTrace("get_device_detail", detail),
      stepTrace("list_device_alerts", alerts),
    ],
  };
}

export async function diagnoseNetworkSlow(client, args = {}) {
  const deviceIdentifier = String(args.device_identifier || "").trim();
  if (!deviceIdentifier) {
    const devices = await listMyDevices(client);
    return {
      title: "网络慢诊断",
      summary: "缺少设备标识,已先列出当前账号绑定设备。",
      data: {
        devices: devices.data.devices,
      },
      suggestions: ["选择一个主网关后,带上 device_identifier 重新发起诊断。"],
      trace: [stepTrace("list_my_devices", devices)],
    };
  }

  const detail = await getDeviceDetail(client, { device_identifier: deviceIdentifier });
  const topology = await getNetworkTopology(client, { device_identifier: deviceIdentifier });
  const experience = await getNetworkExperience(client, { device_identifier: deviceIdentifier });
  const stationStats = await getStationStats(client, { device_identifier: deviceIdentifier });
  const symptom = String(args.symptom || "").trim();

  return {
    title: "网络慢诊断",
    summary: buildNetworkSlowSummary(detail, topology, experience, stationStats, symptom),
    data: {
      device_identifier: deviceIdentifier,
      symptom,
      device_detail: compactResult(detail),
      topology: topology.data.topology,
      experience: experience.data.experience,
      station_stats: stationStats.data.stats,
    },
    suggestions: buildNetworkSlowSuggestions(experience.data.experience, stationStats.data.stats),
    trace: [
      stepTrace("get_device_detail", detail),
      stepTrace("get_network_topology", topology),
      stepTrace("get_network_experience", experience),
      stepTrace("get_station_stats", stationStats),
    ],
  };
}

export async function explainFttraiCopilotUsage(_client, args = {}) {
  const goal = String(args.user_goal || "").trim();
  const summary = goal
    ? `针对“${goal}”,建议先提供设备 ID、MAC 或别名;我会优先查询 Customer 绑定设备范围内的状态、告警、拓扑和体验数据。`
    : "可以直接描述要查询或排查的问题;不知道设备标识时,先列出绑定设备。";

  return {
    title: "FTTR Copilot 使用说明",
    summary,
    data: {
      user_goal: goal,
      identity: "Customer",
      capabilities: [
        "设备:列出绑定设备、查看详情和在线状态、修改别名。",
        "告警:查询当前 Customer 绑定设备范围内的告警列表。",
        "网络:查询拓扑、下挂设备指标、网络体验和终端 RSSI 历史。",
        "统计:汇总当前 Customer 绑定设备的在线、故障码和基础负载信息。",
        "实时命令:查询主网关、从网关和 Agent 版本,命令结果可能异步返回 sequence_id。",
        "诊断:组合状态、告警、拓扑和体验数据输出离线或网慢诊断建议。",
      ],
      examples: [
        "列出我的设备。",
        "查询客厅主网关的在线状态。",
        "帮我诊断 AA:BB:CC:DD:EE:FF 为什么离线。",
        "这个网关网速慢,帮我检查拓扑和下挂设备指标。",
        "查询主网关 Agent 版本。",
      ],
      required_info: [
        "设备 ID、MAC 地址或设备别名。",
        "问题现象和影响范围,例如全屋慢、某房间弱覆盖、某台手机卡顿。",
        "如果涉及历史问题,补充大致发生时间。",
      ],
    },
    suggestions: ["不知道设备标识时,先调用 list_my_devices。"],
  };
}

function filterAlertsForDevice(alerts, detail) {
  const candidates = new Set([
    normalize(detail.id),
    normalize(detail.mac),
  ].filter(Boolean));

  return alerts.filter((alert) => {
    const values = [
      alert.device?.id,
      alert.device?.mac,
      alert.event_target?.device_id,
    ].map(normalize);
    return values.some((value) => candidates.has(value));
  });
}

function buildOfflineSummary(detail, alerts) {
  const name = detail.alias || detail.mac || detail.id;
  if (detail.online_status_label === "在线") {
    return `设备 ${name} 当前在线;如用户反馈离线,可能是短时离线已恢复或现场感知问题。`;
  }
  if (detail.online_status_label === "离线") {
    return alerts.length > 0
      ? `设备 ${name} 当前离线,并发现 ${alerts.length} 条相关告警。`
      : `设备 ${name} 当前离线,Customer 告警列表中未匹配到相关告警。`;
  }
  return `设备 ${name} 当前在线状态未知,需结合告警和现场信息继续确认。`;
}

function buildOfflineSuggestions(detail, alerts) {
  if (detail.online_status_label === "在线") {
    return ["核对用户反馈时间与最近告警时间,确认是否为短时离线后恢复。"];
  }
  const suggestions = [
    "优先检查设备供电、光纤链路、上联连接和指示灯状态。",
    "结合 last_offline_reason、fault_codes 和最近告警判断是否为链路中断或设备异常重启。",
  ];
  if (alerts.length === 0) {
    suggestions.push("未匹配到相关告警时,可继续查询主网关实时信息或联系平台侧确认采集是否正常。");
  }
  return suggestions;
}

function buildNetworkSlowSummary(detail, topology, experience, stationStats, symptom) {
  const score = experience.data.experience.network_score;
  const stations = stationStats.data.stats.length;
  const weakStations = stationStats.data.stats.filter((item) => item.stats.rssi && item.stats.rssi < -70).length;
  const symptomText = symptom ? `针对“${symptom}”,` : "";
  return `${symptomText}设备状态 ${detail.data.detail.online_status_label},网络评分 ${score},拓扑下挂 ${topology.data.topology.stations.length} 个终端,返回 ${stations} 个终端指标,其中弱信号 ${weakStations} 个。`;
}

function buildNetworkSlowSuggestions(experience, stationStats) {
  const suggestions = [];
  if (experience.network_score > 0 && experience.network_score < 70) {
    suggestions.push("网络体验评分偏低,优先检查带宽趋势、RSSI 趋势和异常终端。");
  }
  const weakStations = stationStats.filter((item) => item.stats.rssi && item.stats.rssi < -70);
  if (weakStations.length > 0) {
    suggestions.push("存在 RSSI 低于 -70 的终端,优先检查终端位置、漫游和从网关覆盖。");
  }
  const failedPackets = stationStats.filter((item) => item.stats.rx_pkt_fail > 0 || item.stats.tx_pkt_fail > 0);
  if (failedPackets.length > 0) {
    suggestions.push("存在收发包失败的终端,建议结合协商速率、RSSI 和连接频段排查无线质量。");
  }
  if (suggestions.length === 0) {
    suggestions.push("当前指标未显示明显弱信号或丢包,可继续结合用户发生时间查询体验趋势。");
  }
  return suggestions;
}

function compactResult(result) {
  return {
    title: result.title,
    summary: result.summary,
    data: result.data,
  };
}

function stepTrace(tool, result) {
  return {
    tool,
    title: result.title,
    summary: result.summary,
  };
}

function requiredString(value, fieldName) {
  const trimmed = String(value || "").trim();
  if (!trimmed) {
    const err = new Error(`${fieldName} 不能为空`);
    err.code = "invalid_argument";
    throw err;
  }
  return trimmed;
}

function normalize(value) {
  return String(value || "").trim().toLowerCase().replace(/[:.\-\s]/g, "");
}