siteanalysis.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. package tools
  2. // 站点分析结果(只读,与 ToolBox“站点分析管理”同源):分析任务由页面创建,这里只读取结果。
  3. import (
  4. "context"
  5. "errors"
  6. "fmt"
  7. "strings"
  8. "github.com/mark3labs/mcp-go/mcp"
  9. "go.mongodb.org/mongo-driver/bson/primitive"
  10. "go.mongodb.org/mongo-driver/mongo"
  11. "fwh-toolbox-server/agentmcp/kit"
  12. "fwh-toolbox-server/domain"
  13. "fwh-toolbox-server/dto"
  14. "fwh-toolbox-server/services"
  15. )
  16. const siteAnalysisSource = "ToolBox 站点分析"
  17. // ---- site_analysis_list ----
  18. type siteAnalysisListArgs struct {
  19. SN string `json:"sn,omitempty" jsonschema:"站点 SN;不填查全部"`
  20. Progress string `json:"progress,omitempty" jsonschema:"进度:pending、running、complete、error"`
  21. Page int `json:"page,omitempty" jsonschema:"页码,默认 1"`
  22. Size int `json:"size,omitempty" jsonschema:"每页条数,1~100,默认 20"`
  23. }
  24. var SiteAnalysisList = kit.Def[siteAnalysisListArgs]{
  25. Name: "site_analysis_list",
  26. Title: "查询站点分析任务",
  27. Description: "查询 ToolBox 中已创建的站点分析任务(页面创建,分析一段时间内站点的上下线、模式切换、告警、事件与运行数据)。" +
  28. "返回 total 与 items:id(用于 site_analysis_timeline、site_analysis_export)、sn、description、timePeriod(分析时间段)、item(分析项)、progress、trigger、user。",
  29. ReadOnly: true,
  30. Properties: map[string]map[string]any{"progress": {"enum": []string{"pending", "running", "complete", "error"}}},
  31. Handler: func(ctx context.Context, a siteAnalysisListArgs) (*mcp.CallToolResult, error) {
  32. page, size := kit.Page(a.Page), kit.Limit(a.Size, 20, 100)
  33. var sn, progress *string
  34. if v := strings.TrimSpace(a.SN); v != "" {
  35. sn = &v
  36. }
  37. if a.Progress != "" {
  38. progress = &a.Progress
  39. }
  40. res, err := services.GetSiteAnalysisManageList(uint(page), uint(size), sn, nil, progress, nil)
  41. if err != nil {
  42. return kit.Upstream(siteAnalysisSource, err), nil
  43. }
  44. return kit.List(res.Data, int(res.Total), size, "用 page 翻页,或用 sn 缩小范围"), nil
  45. },
  46. }
  47. // ---- site_analysis_timeline ----
  48. type siteAnalysisTimelineArgs struct {
  49. ID string `json:"id" jsonschema:"分析任务 id(site_analysis_list 返回)"`
  50. Type string `json:"type,omitempty" jsonschema:"只看某类:offline(离线)、online(上线)、mode_change(模式切换)、alarm(告警)、event(事件);默认全部"`
  51. Limit int `json:"limit,omitempty" jsonschema:"最多返回条数,1~500,默认 200"`
  52. }
  53. var SiteAnalysisTimeline = kit.Def[siteAnalysisTimelineArgs]{
  54. Name: "site_analysis_timeline",
  55. Title: "查看站点分析时间线",
  56. Description: "查看一个已完成的站点分析任务的时间线:上下线、模式切换、告警、事件,按时间排序,以表格返回(time、type、info、data,data 超过 300 字截断)。" +
  57. "时间线很长或需要运行数据(201 报文)时用 site_analysis_export 导出 CSV 分析。",
  58. ReadOnly: true,
  59. Properties: map[string]map[string]any{"type": {"enum": []string{"offline", "online", "mode_change", "alarm", "event"}}},
  60. Handler: func(ctx context.Context, a siteAnalysisTimelineArgs) (*mcp.CallToolResult, error) {
  61. data, fail := siteAnalysisData(a.ID)
  62. if fail != nil {
  63. return fail, nil
  64. }
  65. limit := kit.Limit(a.Limit, 200, 500)
  66. rows := [][]string{}
  67. for _, it := range data.Data.Timeline {
  68. if a.Type != "" && string(it.Type) != a.Type {
  69. continue
  70. }
  71. rows = append(rows, []string{it.Time, string(it.Type), deref(it.Info), truncate(deref(it.Data), 300)})
  72. }
  73. if len(rows) == 0 {
  74. return kit.Empty("时间线中没有符合条件的记录(任务进度:" + string(data.Progress) + ")。"), nil
  75. }
  76. return kit.Table([]string{"time", "type", "info", "data"}, rows, limit, "超过 limit 时请指定 type,或用 site_analysis_export 导出"), nil
  77. },
  78. }
  79. // ---- site_analysis_export ----
  80. type siteAnalysisExportArgs struct {
  81. ID string `json:"id" jsonschema:"分析任务 id(site_analysis_list 返回)"`
  82. }
  83. var SiteAnalysisExport = kit.Def[siteAnalysisExportArgs]{
  84. Name: "site_analysis_export",
  85. Title: "导出站点分析结果",
  86. Description: "把一个站点分析任务的全部结果导出为 CSV 文件保存到会话工作区:section=timeline 为时间线(type、info、data)," +
  87. "section=runtime 为运行数据(data 为 201 报文 JSON)。请用 Python(pandas)读取分析,不要把全文读入上下文。",
  88. ReadOnly: true,
  89. Handler: func(ctx context.Context, a siteAnalysisExportArgs) (*mcp.CallToolResult, error) {
  90. data, fail := siteAnalysisData(a.ID)
  91. if fail != nil {
  92. return fail, nil
  93. }
  94. rows := make([][]string, 0, len(data.Data.Timeline)+len(data.Data.Runtime))
  95. for _, it := range data.Data.Timeline {
  96. rows = append(rows, []string{"timeline", it.Time, string(it.Type), deref(it.Info), deref(it.Data)})
  97. }
  98. for _, it := range data.Data.Runtime {
  99. rows = append(rows, []string{"runtime", it.Time, "", "", it.Data_201})
  100. }
  101. summary := fmt.Sprintf("站点 %s:时间线 %d 条,运行数据 %d 条。", data.Sn, len(data.Data.Timeline), len(data.Data.Runtime))
  102. return kit.Export("fwh://site-analysis/"+data.Sn+"_"+data.Id.Hex()+".csv", []string{"section", "time", "type", "info", "data"}, rows, summary), nil
  103. },
  104. }
  105. func siteAnalysisData(idStr string) (*dto.SiteAnalysisDataInfo, *mcp.CallToolResult) {
  106. id, err := primitive.ObjectIDFromHex(strings.TrimSpace(idStr))
  107. if err != nil {
  108. return nil, kit.Failf("参数 id 格式错误:%s", idStr)
  109. }
  110. data, err := services.GetSiteAnalysisData(id)
  111. if errors.Is(err, mongo.ErrNoDocuments) || (err == nil && data == nil) {
  112. return nil, kit.Failf("没有找到分析任务 %s,可以用 site_analysis_list 查看", idStr)
  113. }
  114. if err != nil {
  115. return nil, kit.Upstream(siteAnalysisSource, err)
  116. }
  117. if data.Progress != domain.ProgressComplete {
  118. return nil, kit.Failf("分析任务 %s 尚未完成(进度:%s),完成后再查看结果", idStr, data.Progress)
  119. }
  120. return data, nil
  121. }
  122. func deref(s *string) string {
  123. if s == nil {
  124. return ""
  125. }
  126. return *s
  127. }