Major changes: - Add Go backend (backend/) with microservices architecture - Enhanced master-agents-svc: reranker, content-classifier, stealth-crawler, proxy-manager, media-search, fastClassifier, language detection - New web-svc widgets: KnowledgeCard, ProductCard, ProfileCard, VideoCard, UnifiedCard, CardGallery, InlineImageGallery, SourcesPanel, RelatedQuestions - Improved discover-svc with discover-db integration - Docker deployment improvements (Caddyfile, vendor.sh, BUILD.md) - Library-svc: project_id schema migration - Remove deprecated finance-svc and travel-svc - Localization improvements across services Made-with: Cursor
760 lines
18 KiB
Go
760 lines
18 KiB
Go
package labs
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gooseek/backend/internal/llm"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type Generator struct {
|
|
llm llm.Client
|
|
}
|
|
|
|
func NewGenerator(llmClient llm.Client) *Generator {
|
|
return &Generator{llm: llmClient}
|
|
}
|
|
|
|
type GenerateOptions struct {
|
|
Query string
|
|
Data interface{}
|
|
PreferredTypes []VisualizationType
|
|
Theme string
|
|
Locale string
|
|
MaxVisualizations int
|
|
}
|
|
|
|
func (g *Generator) GenerateReport(ctx context.Context, opts GenerateOptions) (*Report, error) {
|
|
analysisPrompt := fmt.Sprintf(`Analyze this data and query to determine the best visualizations.
|
|
|
|
Query: %s
|
|
|
|
Data: %v
|
|
|
|
Determine:
|
|
1. What visualizations would best represent this data?
|
|
2. How should the data be structured for each visualization?
|
|
3. What insights can be highlighted?
|
|
|
|
Respond in JSON format:
|
|
{
|
|
"title": "Report title",
|
|
"sections": [
|
|
{
|
|
"title": "Section title",
|
|
"visualizations": [
|
|
{
|
|
"type": "chart_type",
|
|
"title": "Viz title",
|
|
"dataMapping": { "how to map the data" },
|
|
"insight": "Key insight"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
|
|
Available visualization types: bar_chart, line_chart, pie_chart, donut_chart, table, stat_cards, kpi, comparison, timeline, progress, heatmap, code_block, markdown, collapsible, tabs, accordion`, opts.Query, opts.Data)
|
|
|
|
result, err := g.llm.GenerateText(ctx, llm.StreamRequest{
|
|
Messages: []llm.Message{{Role: "user", Content: analysisPrompt}},
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var analysis struct {
|
|
Title string `json:"title"`
|
|
Sections []struct {
|
|
Title string `json:"title"`
|
|
Visualizations []struct {
|
|
Type string `json:"type"`
|
|
Title string `json:"title"`
|
|
DataMapping map[string]interface{} `json:"dataMapping"`
|
|
Insight string `json:"insight"`
|
|
} `json:"visualizations"`
|
|
} `json:"sections"`
|
|
}
|
|
|
|
jsonStr := extractJSON(result)
|
|
if err := json.Unmarshal([]byte(jsonStr), &analysis); err != nil {
|
|
return g.createDefaultReport(opts)
|
|
}
|
|
|
|
report := &Report{
|
|
ID: uuid.New().String(),
|
|
Title: analysis.Title,
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
Theme: opts.Theme,
|
|
Sections: make([]ReportSection, 0),
|
|
}
|
|
|
|
for _, sec := range analysis.Sections {
|
|
section := ReportSection{
|
|
ID: uuid.New().String(),
|
|
Title: sec.Title,
|
|
Visualizations: make([]Visualization, 0),
|
|
}
|
|
|
|
for _, viz := range sec.Visualizations {
|
|
visualization := g.createVisualization(VisualizationType(viz.Type), viz.Title, opts.Data, viz.DataMapping)
|
|
if visualization != nil {
|
|
section.Visualizations = append(section.Visualizations, *visualization)
|
|
}
|
|
}
|
|
|
|
if len(section.Visualizations) > 0 {
|
|
report.Sections = append(report.Sections, section)
|
|
}
|
|
}
|
|
|
|
return report, nil
|
|
}
|
|
|
|
func (g *Generator) createDefaultReport(opts GenerateOptions) (*Report, error) {
|
|
report := &Report{
|
|
ID: uuid.New().String(),
|
|
Title: "Анализ данных",
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
Sections: []ReportSection{
|
|
{
|
|
ID: uuid.New().String(),
|
|
Title: "Обзор",
|
|
Visualizations: []Visualization{
|
|
g.CreateMarkdown("", formatDataAsMarkdown(opts.Data)),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
return report, nil
|
|
}
|
|
|
|
func (g *Generator) createVisualization(vizType VisualizationType, title string, data interface{}, mapping map[string]interface{}) *Visualization {
|
|
switch vizType {
|
|
case VizBarChart, VizLineChart, VizAreaChart:
|
|
return g.createChartVisualization(vizType, title, data, mapping)
|
|
case VizPieChart, VizDonutChart:
|
|
return g.createPieVisualization(vizType, title, data, mapping)
|
|
case VizTable:
|
|
return g.createTableVisualization(title, data, mapping)
|
|
case VizStatCards:
|
|
return g.createStatCardsVisualization(title, data, mapping)
|
|
case VizKPI:
|
|
return g.createKPIVisualization(title, data, mapping)
|
|
case VizTimeline:
|
|
return g.createTimelineVisualization(title, data, mapping)
|
|
case VizComparison:
|
|
return g.createComparisonVisualization(title, data, mapping)
|
|
case VizProgress:
|
|
return g.createProgressVisualization(title, data, mapping)
|
|
case VizMarkdown:
|
|
content := extractStringFromData(data, mapping, "content")
|
|
viz := g.CreateMarkdown(title, content)
|
|
return &viz
|
|
default:
|
|
viz := g.CreateMarkdown(title, formatDataAsMarkdown(data))
|
|
return &viz
|
|
}
|
|
}
|
|
|
|
func (g *Generator) createChartVisualization(vizType VisualizationType, title string, data interface{}, mapping map[string]interface{}) *Visualization {
|
|
chartData := &ChartData{
|
|
Labels: make([]string, 0),
|
|
Datasets: make([]ChartDataset, 0),
|
|
}
|
|
|
|
if dataMap, ok := data.(map[string]interface{}); ok {
|
|
labels := make([]string, 0)
|
|
values := make([]float64, 0)
|
|
|
|
for k, v := range dataMap {
|
|
labels = append(labels, k)
|
|
values = append(values, toFloat64(v))
|
|
}
|
|
|
|
chartData.Labels = labels
|
|
chartData.Datasets = append(chartData.Datasets, ChartDataset{
|
|
Label: title,
|
|
Data: values,
|
|
})
|
|
}
|
|
|
|
if dataSlice, ok := data.([]interface{}); ok {
|
|
for _, item := range dataSlice {
|
|
if itemMap, ok := item.(map[string]interface{}); ok {
|
|
if label, ok := itemMap["label"].(string); ok {
|
|
chartData.Labels = append(chartData.Labels, label)
|
|
}
|
|
if value, ok := itemMap["value"]; ok {
|
|
if len(chartData.Datasets) == 0 {
|
|
chartData.Datasets = append(chartData.Datasets, ChartDataset{Label: title, Data: []float64{}})
|
|
}
|
|
chartData.Datasets[0].Data = append(chartData.Datasets[0].Data, toFloat64(value))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return &Visualization{
|
|
ID: uuid.New().String(),
|
|
Type: vizType,
|
|
Title: title,
|
|
Data: chartData,
|
|
Config: VisualizationConfig{
|
|
ShowLegend: true,
|
|
ShowTooltip: true,
|
|
ShowGrid: true,
|
|
Animated: true,
|
|
},
|
|
Responsive: true,
|
|
}
|
|
}
|
|
|
|
func (g *Generator) createPieVisualization(vizType VisualizationType, title string, data interface{}, mapping map[string]interface{}) *Visualization {
|
|
chartData := &ChartData{
|
|
Labels: make([]string, 0),
|
|
Datasets: make([]ChartDataset, 0),
|
|
}
|
|
|
|
dataset := ChartDataset{Label: title, Data: []float64{}}
|
|
|
|
if dataMap, ok := data.(map[string]interface{}); ok {
|
|
for k, v := range dataMap {
|
|
chartData.Labels = append(chartData.Labels, k)
|
|
dataset.Data = append(dataset.Data, toFloat64(v))
|
|
}
|
|
}
|
|
|
|
chartData.Datasets = append(chartData.Datasets, dataset)
|
|
|
|
return &Visualization{
|
|
ID: uuid.New().String(),
|
|
Type: vizType,
|
|
Title: title,
|
|
Data: chartData,
|
|
Config: VisualizationConfig{
|
|
ShowLegend: true,
|
|
ShowTooltip: true,
|
|
ShowValues: true,
|
|
Animated: true,
|
|
},
|
|
Style: VisualizationStyle{
|
|
Height: "300px",
|
|
},
|
|
Responsive: true,
|
|
}
|
|
}
|
|
|
|
func (g *Generator) createTableVisualization(title string, data interface{}, mapping map[string]interface{}) *Visualization {
|
|
tableData := &TableData{
|
|
Columns: make([]TableColumn, 0),
|
|
Rows: make([]TableRow, 0),
|
|
}
|
|
|
|
if dataSlice, ok := data.([]interface{}); ok && len(dataSlice) > 0 {
|
|
if firstRow, ok := dataSlice[0].(map[string]interface{}); ok {
|
|
for key := range firstRow {
|
|
tableData.Columns = append(tableData.Columns, TableColumn{
|
|
Key: key,
|
|
Label: formatColumnLabel(key),
|
|
Sortable: true,
|
|
})
|
|
}
|
|
}
|
|
|
|
for _, item := range dataSlice {
|
|
if rowMap, ok := item.(map[string]interface{}); ok {
|
|
tableData.Rows = append(tableData.Rows, TableRow(rowMap))
|
|
}
|
|
}
|
|
}
|
|
|
|
if dataMap, ok := data.(map[string]interface{}); ok {
|
|
tableData.Columns = []TableColumn{
|
|
{Key: "key", Label: "Параметр", Sortable: true},
|
|
{Key: "value", Label: "Значение", Sortable: true},
|
|
}
|
|
|
|
for k, v := range dataMap {
|
|
tableData.Rows = append(tableData.Rows, TableRow{
|
|
"key": k,
|
|
"value": v,
|
|
})
|
|
}
|
|
}
|
|
|
|
tableData.Summary = &TableSummary{
|
|
TotalRows: len(tableData.Rows),
|
|
}
|
|
|
|
return &Visualization{
|
|
ID: uuid.New().String(),
|
|
Type: VizTable,
|
|
Title: title,
|
|
Data: tableData,
|
|
Config: VisualizationConfig{
|
|
Sortable: true,
|
|
Searchable: true,
|
|
Paginated: len(tableData.Rows) > 10,
|
|
PageSize: 10,
|
|
},
|
|
Responsive: true,
|
|
}
|
|
}
|
|
|
|
func (g *Generator) createStatCardsVisualization(title string, data interface{}, mapping map[string]interface{}) *Visualization {
|
|
cardsData := &StatCardsData{
|
|
Cards: make([]StatCard, 0),
|
|
}
|
|
|
|
colors := []string{"#3B82F6", "#10B981", "#F59E0B", "#EF4444", "#8B5CF6", "#EC4899"}
|
|
colorIdx := 0
|
|
|
|
if dataMap, ok := data.(map[string]interface{}); ok {
|
|
for k, v := range dataMap {
|
|
card := StatCard{
|
|
ID: uuid.New().String(),
|
|
Title: formatColumnLabel(k),
|
|
Value: v,
|
|
Color: colors[colorIdx%len(colors)],
|
|
}
|
|
cardsData.Cards = append(cardsData.Cards, card)
|
|
colorIdx++
|
|
}
|
|
}
|
|
|
|
return &Visualization{
|
|
ID: uuid.New().String(),
|
|
Type: VizStatCards,
|
|
Title: title,
|
|
Data: cardsData,
|
|
Config: VisualizationConfig{
|
|
Animated: true,
|
|
},
|
|
Responsive: true,
|
|
}
|
|
}
|
|
|
|
func (g *Generator) createKPIVisualization(title string, data interface{}, mapping map[string]interface{}) *Visualization {
|
|
kpiData := &KPIData{
|
|
Value: data,
|
|
}
|
|
|
|
if dataMap, ok := data.(map[string]interface{}); ok {
|
|
if v, ok := dataMap["value"]; ok {
|
|
kpiData.Value = v
|
|
}
|
|
if v, ok := dataMap["change"].(float64); ok {
|
|
kpiData.Change = v
|
|
if v >= 0 {
|
|
kpiData.ChangeType = "increase"
|
|
} else {
|
|
kpiData.ChangeType = "decrease"
|
|
}
|
|
}
|
|
if v, ok := dataMap["target"]; ok {
|
|
kpiData.Target = v
|
|
}
|
|
if v, ok := dataMap["unit"].(string); ok {
|
|
kpiData.Unit = v
|
|
}
|
|
}
|
|
|
|
return &Visualization{
|
|
ID: uuid.New().String(),
|
|
Type: VizKPI,
|
|
Title: title,
|
|
Data: kpiData,
|
|
Config: VisualizationConfig{
|
|
Animated: true,
|
|
ShowValues: true,
|
|
},
|
|
Style: VisualizationStyle{
|
|
MinHeight: "150px",
|
|
},
|
|
Responsive: true,
|
|
}
|
|
}
|
|
|
|
func (g *Generator) createTimelineVisualization(title string, data interface{}, mapping map[string]interface{}) *Visualization {
|
|
timelineData := &TimelineData{
|
|
Events: make([]TimelineEvent, 0),
|
|
}
|
|
|
|
if dataSlice, ok := data.([]interface{}); ok {
|
|
for _, item := range dataSlice {
|
|
if itemMap, ok := item.(map[string]interface{}); ok {
|
|
event := TimelineEvent{
|
|
ID: uuid.New().String(),
|
|
}
|
|
|
|
if v, ok := itemMap["date"].(string); ok {
|
|
event.Date, _ = time.Parse(time.RFC3339, v)
|
|
}
|
|
if v, ok := itemMap["title"].(string); ok {
|
|
event.Title = v
|
|
}
|
|
if v, ok := itemMap["description"].(string); ok {
|
|
event.Description = v
|
|
}
|
|
|
|
timelineData.Events = append(timelineData.Events, event)
|
|
}
|
|
}
|
|
}
|
|
|
|
sort.Slice(timelineData.Events, func(i, j int) bool {
|
|
return timelineData.Events[i].Date.Before(timelineData.Events[j].Date)
|
|
})
|
|
|
|
return &Visualization{
|
|
ID: uuid.New().String(),
|
|
Type: VizTimeline,
|
|
Title: title,
|
|
Data: timelineData,
|
|
Config: VisualizationConfig{
|
|
Animated: true,
|
|
},
|
|
Responsive: true,
|
|
}
|
|
}
|
|
|
|
func (g *Generator) createComparisonVisualization(title string, data interface{}, mapping map[string]interface{}) *Visualization {
|
|
compData := &ComparisonData{
|
|
Items: make([]ComparisonItem, 0),
|
|
Categories: make([]string, 0),
|
|
}
|
|
|
|
if dataSlice, ok := data.([]interface{}); ok && len(dataSlice) > 0 {
|
|
if firstItem, ok := dataSlice[0].(map[string]interface{}); ok {
|
|
for k := range firstItem {
|
|
if k != "name" && k != "id" && k != "image" {
|
|
compData.Categories = append(compData.Categories, k)
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, item := range dataSlice {
|
|
if itemMap, ok := item.(map[string]interface{}); ok {
|
|
compItem := ComparisonItem{
|
|
ID: uuid.New().String(),
|
|
Values: make(map[string]interface{}),
|
|
}
|
|
|
|
if v, ok := itemMap["name"].(string); ok {
|
|
compItem.Name = v
|
|
}
|
|
if v, ok := itemMap["image"].(string); ok {
|
|
compItem.Image = v
|
|
}
|
|
|
|
for _, cat := range compData.Categories {
|
|
if v, ok := itemMap[cat]; ok {
|
|
compItem.Values[cat] = v
|
|
}
|
|
}
|
|
|
|
compData.Items = append(compData.Items, compItem)
|
|
}
|
|
}
|
|
}
|
|
|
|
return &Visualization{
|
|
ID: uuid.New().String(),
|
|
Type: VizComparison,
|
|
Title: title,
|
|
Data: compData,
|
|
Config: VisualizationConfig{
|
|
ShowLabels: true,
|
|
},
|
|
Responsive: true,
|
|
}
|
|
}
|
|
|
|
func (g *Generator) createProgressVisualization(title string, data interface{}, mapping map[string]interface{}) *Visualization {
|
|
progressData := &ProgressData{
|
|
Current: 0,
|
|
Total: 100,
|
|
ShowValue: true,
|
|
Animated: true,
|
|
}
|
|
|
|
if dataMap, ok := data.(map[string]interface{}); ok {
|
|
if v, ok := dataMap["current"]; ok {
|
|
progressData.Current = toFloat64(v)
|
|
}
|
|
if v, ok := dataMap["total"]; ok {
|
|
progressData.Total = toFloat64(v)
|
|
}
|
|
if v, ok := dataMap["label"].(string); ok {
|
|
progressData.Label = v
|
|
}
|
|
}
|
|
|
|
if v, ok := data.(float64); ok {
|
|
progressData.Current = v
|
|
}
|
|
|
|
return &Visualization{
|
|
ID: uuid.New().String(),
|
|
Type: VizProgress,
|
|
Title: title,
|
|
Data: progressData,
|
|
Config: VisualizationConfig{
|
|
Animated: true,
|
|
ShowValues: true,
|
|
},
|
|
Responsive: true,
|
|
}
|
|
}
|
|
|
|
func (g *Generator) CreateBarChart(title string, labels []string, values []float64) Visualization {
|
|
return Visualization{
|
|
ID: uuid.New().String(),
|
|
Type: VizBarChart,
|
|
Title: title,
|
|
Data: &ChartData{
|
|
Labels: labels,
|
|
Datasets: []ChartDataset{
|
|
{Label: title, Data: values},
|
|
},
|
|
},
|
|
Config: VisualizationConfig{
|
|
ShowLegend: true,
|
|
ShowTooltip: true,
|
|
Animated: true,
|
|
},
|
|
Responsive: true,
|
|
}
|
|
}
|
|
|
|
func (g *Generator) CreateLineChart(title string, labels []string, datasets []ChartDataset) Visualization {
|
|
return Visualization{
|
|
ID: uuid.New().String(),
|
|
Type: VizLineChart,
|
|
Title: title,
|
|
Data: &ChartData{
|
|
Labels: labels,
|
|
Datasets: datasets,
|
|
},
|
|
Config: VisualizationConfig{
|
|
ShowLegend: true,
|
|
ShowTooltip: true,
|
|
ShowGrid: true,
|
|
Animated: true,
|
|
},
|
|
Responsive: true,
|
|
}
|
|
}
|
|
|
|
func (g *Generator) CreatePieChart(title string, labels []string, values []float64) Visualization {
|
|
return Visualization{
|
|
ID: uuid.New().String(),
|
|
Type: VizPieChart,
|
|
Title: title,
|
|
Data: &ChartData{
|
|
Labels: labels,
|
|
Datasets: []ChartDataset{
|
|
{Label: title, Data: values},
|
|
},
|
|
},
|
|
Config: VisualizationConfig{
|
|
ShowLegend: true,
|
|
ShowTooltip: true,
|
|
ShowValues: true,
|
|
},
|
|
Responsive: true,
|
|
}
|
|
}
|
|
|
|
func (g *Generator) CreateTable(title string, columns []TableColumn, rows []TableRow) Visualization {
|
|
return Visualization{
|
|
ID: uuid.New().String(),
|
|
Type: VizTable,
|
|
Title: title,
|
|
Data: &TableData{
|
|
Columns: columns,
|
|
Rows: rows,
|
|
Summary: &TableSummary{TotalRows: len(rows)},
|
|
},
|
|
Config: VisualizationConfig{
|
|
Sortable: true,
|
|
Searchable: true,
|
|
Paginated: len(rows) > 10,
|
|
PageSize: 10,
|
|
},
|
|
Responsive: true,
|
|
}
|
|
}
|
|
|
|
func (g *Generator) CreateStatCards(title string, cards []StatCard) Visualization {
|
|
return Visualization{
|
|
ID: uuid.New().String(),
|
|
Type: VizStatCards,
|
|
Title: title,
|
|
Data: &StatCardsData{Cards: cards},
|
|
Config: VisualizationConfig{
|
|
Animated: true,
|
|
},
|
|
Responsive: true,
|
|
}
|
|
}
|
|
|
|
func (g *Generator) CreateKPI(title string, value interface{}, change float64, unit string) Visualization {
|
|
changeType := "neutral"
|
|
if change > 0 {
|
|
changeType = "increase"
|
|
} else if change < 0 {
|
|
changeType = "decrease"
|
|
}
|
|
|
|
return Visualization{
|
|
ID: uuid.New().String(),
|
|
Type: VizKPI,
|
|
Title: title,
|
|
Data: &KPIData{
|
|
Value: value,
|
|
Change: change,
|
|
ChangeType: changeType,
|
|
Unit: unit,
|
|
},
|
|
Config: VisualizationConfig{
|
|
Animated: true,
|
|
},
|
|
Responsive: true,
|
|
}
|
|
}
|
|
|
|
func (g *Generator) CreateMarkdown(title string, content string) Visualization {
|
|
return Visualization{
|
|
ID: uuid.New().String(),
|
|
Type: VizMarkdown,
|
|
Title: title,
|
|
Data: &MarkdownData{Content: content},
|
|
Responsive: true,
|
|
}
|
|
}
|
|
|
|
func (g *Generator) CreateCodeBlock(title, code, language string) Visualization {
|
|
return Visualization{
|
|
ID: uuid.New().String(),
|
|
Type: VizCodeBlock,
|
|
Title: title,
|
|
Data: &CodeBlockData{
|
|
Code: code,
|
|
Language: language,
|
|
ShowLineNum: true,
|
|
Copyable: true,
|
|
},
|
|
Responsive: true,
|
|
}
|
|
}
|
|
|
|
func (g *Generator) CreateTabs(title string, tabs []TabItem) Visualization {
|
|
return Visualization{
|
|
ID: uuid.New().String(),
|
|
Type: VizTabs,
|
|
Title: title,
|
|
Data: &TabsData{Tabs: tabs},
|
|
Responsive: true,
|
|
}
|
|
}
|
|
|
|
func (g *Generator) CreateAccordion(title string, items []AccordionItem) Visualization {
|
|
return Visualization{
|
|
ID: uuid.New().String(),
|
|
Type: VizAccordion,
|
|
Title: title,
|
|
Data: &AccordionData{Items: items},
|
|
Config: VisualizationConfig{
|
|
Animated: true,
|
|
},
|
|
Responsive: true,
|
|
}
|
|
}
|
|
|
|
func (g *Generator) CreateHeatmap(title string, xLabels, yLabels []string, values [][]float64) Visualization {
|
|
return Visualization{
|
|
ID: uuid.New().String(),
|
|
Type: VizHeatmap,
|
|
Title: title,
|
|
Data: &HeatmapData{
|
|
XLabels: xLabels,
|
|
YLabels: yLabels,
|
|
Values: values,
|
|
},
|
|
Config: VisualizationConfig{
|
|
ShowTooltip: true,
|
|
ShowLabels: true,
|
|
},
|
|
Responsive: true,
|
|
}
|
|
}
|
|
|
|
func extractJSON(text string) string {
|
|
re := regexp.MustCompile(`(?s)\{.*\}`)
|
|
match := re.FindString(text)
|
|
if match != "" {
|
|
return match
|
|
}
|
|
return "{}"
|
|
}
|
|
|
|
func toFloat64(v interface{}) float64 {
|
|
switch val := v.(type) {
|
|
case float64:
|
|
return val
|
|
case float32:
|
|
return float64(val)
|
|
case int:
|
|
return float64(val)
|
|
case int64:
|
|
return float64(val)
|
|
case string:
|
|
f, _ := strconv.ParseFloat(val, 64)
|
|
return f
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func formatColumnLabel(key string) string {
|
|
key = strings.ReplaceAll(key, "_", " ")
|
|
key = strings.ReplaceAll(key, "-", " ")
|
|
|
|
words := strings.Fields(key)
|
|
for i, word := range words {
|
|
if len(word) > 0 {
|
|
words[i] = strings.ToUpper(string(word[0])) + strings.ToLower(word[1:])
|
|
}
|
|
}
|
|
|
|
return strings.Join(words, " ")
|
|
}
|
|
|
|
func extractStringFromData(data interface{}, mapping map[string]interface{}, key string) string {
|
|
if dataMap, ok := data.(map[string]interface{}); ok {
|
|
if v, ok := dataMap[key].(string); ok {
|
|
return v
|
|
}
|
|
}
|
|
return fmt.Sprintf("%v", data)
|
|
}
|
|
|
|
func formatDataAsMarkdown(data interface{}) string {
|
|
jsonBytes, err := json.MarshalIndent(data, "", " ")
|
|
if err != nil {
|
|
return fmt.Sprintf("%v", data)
|
|
}
|
|
return "```json\n" + string(jsonBytes) + "\n```"
|
|
}
|