summaryrefslogtreecommitdiff
path: root/internal/reporter/reporter.go
blob: 2f4f7dcee9ffcbf84bd1c3bc1bb33279fee115d3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package reporter

import (
	"encoding/json"
	"fmt"
	"io"
	"text/tabwriter"
	"time"

	"github.com/thepeterstone/claudomator/internal/storage"
)

// Reporter generates reports from execution data.
type Reporter interface {
	Generate(w io.Writer, executions []*storage.Execution) error
}

// ConsoleReporter outputs a formatted table.
type ConsoleReporter struct{}

func (r *ConsoleReporter) Generate(w io.Writer, executions []*storage.Execution) error {
	if len(executions) == 0 {
		fmt.Fprintln(w, "No executions found.")
		return nil
	}

	tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0)
	fmt.Fprintln(tw, "ID\tTASK\tSTATUS\tEXIT\tCOST\tDURATION\tSTARTED")

	var totalCost float64
	var completed, failed int

	for _, e := range executions {
		dur := e.EndTime.Sub(e.StartTime)
		totalCost += e.CostUSD
		if e.Status == "COMPLETED" {
			completed++
		} else {
			failed++
		}

		fmt.Fprintf(tw, "%.8s\t%.8s\t%s\t%d\t$%.4f\t%v\t%s\n",
			e.ID, e.TaskID, e.Status, e.ExitCode, e.CostUSD,
			dur.Round(time.Second), e.StartTime.Format("2006-01-02 15:04"))
	}
	tw.Flush()

	fmt.Fprintf(w, "\nSummary: %d completed, %d failed, total cost $%.4f\n", completed, failed, totalCost)
	return nil
}

// JSONReporter outputs JSON.
type JSONReporter struct {
	Pretty bool
}

func (r *JSONReporter) Generate(w io.Writer, executions []*storage.Execution) error {
	enc := json.NewEncoder(w)
	if r.Pretty {
		enc.SetIndent("", "  ")
	}
	return enc.Encode(executions)
}

// HTMLReporter generates a standalone HTML report.
type HTMLReporter struct{}

func (r *HTMLReporter) Generate(w io.Writer, executions []*storage.Execution) error {
	var totalCost float64
	var completed, failed int
	for _, e := range executions {
		totalCost += e.CostUSD
		if e.Status == "COMPLETED" {
			completed++
		} else {
			failed++
		}
	}

	fmt.Fprint(w, `<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title>Claudomator Report</title>
<style>
  * { box-sizing: border-box; margin: 0; padding: 0; }
  body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; background: #0f172a; color: #e2e8f0; padding: 1rem; }
  .container { max-width: 960px; margin: 0 auto; }
  h1 { font-size: 1.5rem; margin-bottom: 1rem; color: #7dd3fc; }
  .summary { display: flex; gap: 1rem; margin-bottom: 1.5rem; flex-wrap: wrap; }
  .stat { background: #1e293b; padding: 1rem; border-radius: 0.5rem; flex: 1; min-width: 120px; }
  .stat .label { font-size: 0.75rem; color: #94a3b8; text-transform: uppercase; }
  .stat .value { font-size: 1.5rem; font-weight: bold; margin-top: 0.25rem; }
  .ok { color: #4ade80; } .fail { color: #f87171; } .cost { color: #fbbf24; }
  table { width: 100%; border-collapse: collapse; background: #1e293b; border-radius: 0.5rem; overflow: hidden; }
  th { background: #334155; padding: 0.75rem; text-align: left; font-size: 0.75rem; text-transform: uppercase; color: #94a3b8; }
  td { padding: 0.75rem; border-top: 1px solid #334155; font-size: 0.875rem; }
  tr:hover { background: #334155; }
  .status-COMPLETED { color: #4ade80; } .status-FAILED { color: #f87171; } .status-TIMED_OUT { color: #fbbf24; }
</style></head><body><div class="container">
<h1>Claudomator Report</h1>
<div class="summary">`)

	fmt.Fprintf(w, `<div class="stat"><div class="label">Completed</div><div class="value ok">%d</div></div>`, completed)
	fmt.Fprintf(w, `<div class="stat"><div class="label">Failed</div><div class="value fail">%d</div></div>`, failed)
	fmt.Fprintf(w, `<div class="stat"><div class="label">Total Cost</div><div class="value cost">$%.4f</div></div>`, totalCost)
	fmt.Fprintf(w, `<div class="stat"><div class="label">Total</div><div class="value">%d</div></div>`, len(executions))

	fmt.Fprint(w, `</div><table><thead><tr><th>ID</th><th>Task</th><th>Status</th><th>Exit</th><th>Cost</th><th>Duration</th><th>Started</th></tr></thead><tbody>`)

	for _, e := range executions {
		dur := e.EndTime.Sub(e.StartTime).Round(time.Second)
		fmt.Fprintf(w, `<tr><td>%.8s</td><td>%.8s</td><td class="status-%s">%s</td><td>%d</td><td>$%.4f</td><td>%v</td><td>%s</td></tr>`,
			e.ID, e.TaskID, e.Status, e.Status, e.ExitCode, e.CostUSD, dur, e.StartTime.Format("2006-01-02 15:04"))
	}

	fmt.Fprint(w, `</tbody></table></div></body></html>`)
	return nil
}