package tools // 站点分析结果(只读,与 ToolBox“站点分析管理”同源):分析任务由页面创建,这里只读取结果。 import ( "context" "errors" "fmt" "strings" "github.com/mark3labs/mcp-go/mcp" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "fwh-toolbox-server/agentmcp/kit" "fwh-toolbox-server/domain" "fwh-toolbox-server/dto" "fwh-toolbox-server/services" ) const siteAnalysisSource = "ToolBox 站点分析" // ---- site_analysis_list ---- type siteAnalysisListArgs struct { SN string `json:"sn,omitempty" jsonschema:"站点 SN;不填查全部"` Progress string `json:"progress,omitempty" jsonschema:"进度:pending、running、complete、error"` Page int `json:"page,omitempty" jsonschema:"页码,默认 1"` Size int `json:"size,omitempty" jsonschema:"每页条数,1~100,默认 20"` } var SiteAnalysisList = kit.Def[siteAnalysisListArgs]{ Name: "site_analysis_list", Title: "查询站点分析任务", Description: "查询 ToolBox 中已创建的站点分析任务(页面创建,分析一段时间内站点的上下线、模式切换、告警、事件与运行数据)。" + "返回 total 与 items:id(用于 site_analysis_timeline、site_analysis_export)、sn、description、timePeriod(分析时间段)、item(分析项)、progress、trigger、user。", ReadOnly: true, Properties: map[string]map[string]any{"progress": {"enum": []string{"pending", "running", "complete", "error"}}}, Handler: func(ctx context.Context, a siteAnalysisListArgs) (*mcp.CallToolResult, error) { page, size := kit.Page(a.Page), kit.Limit(a.Size, 20, 100) var sn, progress *string if v := strings.TrimSpace(a.SN); v != "" { sn = &v } if a.Progress != "" { progress = &a.Progress } res, err := services.GetSiteAnalysisManageList(uint(page), uint(size), sn, nil, progress, nil) if err != nil { return kit.Upstream(siteAnalysisSource, err), nil } return kit.List(res.Data, int(res.Total), size, "用 page 翻页,或用 sn 缩小范围"), nil }, } // ---- site_analysis_timeline ---- type siteAnalysisTimelineArgs struct { ID string `json:"id" jsonschema:"分析任务 id(site_analysis_list 返回)"` Type string `json:"type,omitempty" jsonschema:"只看某类:offline(离线)、online(上线)、mode_change(模式切换)、alarm(告警)、event(事件);默认全部"` Limit int `json:"limit,omitempty" jsonschema:"最多返回条数,1~500,默认 200"` } var SiteAnalysisTimeline = kit.Def[siteAnalysisTimelineArgs]{ Name: "site_analysis_timeline", Title: "查看站点分析时间线", Description: "查看一个已完成的站点分析任务的时间线:上下线、模式切换、告警、事件,按时间排序,以表格返回(time、type、info、data,data 超过 300 字截断)。" + "时间线很长或需要运行数据(201 报文)时用 site_analysis_export 导出 CSV 分析。", ReadOnly: true, Properties: map[string]map[string]any{"type": {"enum": []string{"offline", "online", "mode_change", "alarm", "event"}}}, Handler: func(ctx context.Context, a siteAnalysisTimelineArgs) (*mcp.CallToolResult, error) { data, fail := siteAnalysisData(a.ID) if fail != nil { return fail, nil } limit := kit.Limit(a.Limit, 200, 500) rows := [][]string{} for _, it := range data.Data.Timeline { if a.Type != "" && string(it.Type) != a.Type { continue } rows = append(rows, []string{it.Time, string(it.Type), deref(it.Info), truncate(deref(it.Data), 300)}) } if len(rows) == 0 { return kit.Empty("时间线中没有符合条件的记录(任务进度:" + string(data.Progress) + ")。"), nil } return kit.Table([]string{"time", "type", "info", "data"}, rows, limit, "超过 limit 时请指定 type,或用 site_analysis_export 导出"), nil }, } // ---- site_analysis_export ---- type siteAnalysisExportArgs struct { ID string `json:"id" jsonschema:"分析任务 id(site_analysis_list 返回)"` } var SiteAnalysisExport = kit.Def[siteAnalysisExportArgs]{ Name: "site_analysis_export", Title: "导出站点分析结果", Description: "把一个站点分析任务的全部结果导出为 CSV 文件保存到会话工作区:section=timeline 为时间线(type、info、data)," + "section=runtime 为运行数据(data 为 201 报文 JSON)。请用 Python(pandas)读取分析,不要把全文读入上下文。", ReadOnly: true, Handler: func(ctx context.Context, a siteAnalysisExportArgs) (*mcp.CallToolResult, error) { data, fail := siteAnalysisData(a.ID) if fail != nil { return fail, nil } rows := make([][]string, 0, len(data.Data.Timeline)+len(data.Data.Runtime)) for _, it := range data.Data.Timeline { rows = append(rows, []string{"timeline", it.Time, string(it.Type), deref(it.Info), deref(it.Data)}) } for _, it := range data.Data.Runtime { rows = append(rows, []string{"runtime", it.Time, "", "", it.Data_201}) } summary := fmt.Sprintf("站点 %s:时间线 %d 条,运行数据 %d 条。", data.Sn, len(data.Data.Timeline), len(data.Data.Runtime)) return kit.Export("fwh://site-analysis/"+data.Sn+"_"+data.Id.Hex()+".csv", []string{"section", "time", "type", "info", "data"}, rows, summary), nil }, } func siteAnalysisData(idStr string) (*dto.SiteAnalysisDataInfo, *mcp.CallToolResult) { id, err := primitive.ObjectIDFromHex(strings.TrimSpace(idStr)) if err != nil { return nil, kit.Failf("参数 id 格式错误:%s", idStr) } data, err := services.GetSiteAnalysisData(id) if errors.Is(err, mongo.ErrNoDocuments) || (err == nil && data == nil) { return nil, kit.Failf("没有找到分析任务 %s,可以用 site_analysis_list 查看", idStr) } if err != nil { return nil, kit.Upstream(siteAnalysisSource, err) } if data.Progress != domain.ProgressComplete { return nil, kit.Failf("分析任务 %s 尚未完成(进度:%s),完成后再查看结果", idStr, data.Progress) } return data, nil } func deref(s *string) string { if s == nil { return "" } return *s }