package reports import ( "bytes" "fmt" "html" "strings" "time" "github.com/jung-kurt/gofpdf" ) // todayStr stamps report generation time (UTC, date precision is enough). func todayStr() string { return time.Now().UTC().Format("2006-01-02 15:04") } // ToMarkdown renders the report as Markdown. func (r Report) ToMarkdown() string { var b strings.Builder fmt.Fprintf(&b, "# %s — FlowSight Report (%s)\n\n", r.Ticker, r.GeneratedAt) for _, s := range r.Sections { fmt.Fprintf(&b, "## %s\n\n%s\n\n", s.Name, s.Body) if len(s.Citations) > 0 { b.WriteString("Citations:\n") for _, c := range s.Citations { fmt.Fprintf(&b, "- %s %s @ %s\n", c.Endpoint, c.Ticker, c.SnapshotAt) } b.WriteString("\n") } } return b.String() } // ToHTML renders the report as a standalone page. All dynamic values are // HTML-escaped — bodies come from stored snapshots/LLM and must never be able // to inject script into the exported file. func (r Report) ToHTML() string { esc := html.EscapeString var b strings.Builder b.WriteString("") b.WriteString(esc(r.Ticker) + " — FlowSight Report") fmt.Fprintf(&b, "

%s — FlowSight Report (%s)

", esc(r.Ticker), esc(r.GeneratedAt)) for _, s := range r.Sections { fmt.Fprintf(&b, "

%s

%s

", esc(s.Name), esc(s.Body)) if len(s.Citations) > 0 { b.WriteString("") } } b.WriteString("") return b.String() } // ToPDF renders the report server-side (pure Go, no system deps). // Font: DejaVuSans (Unicode) instead of core Helvetica so Indonesian text // (Em-dash, quotes, Rp, accents) renders correctly — latin1() was turning // every non-Latin rune into '?'. func (r Report) ToPDF() ([]byte, error) { pdf := gofpdf.New("P", "mm", "A4", "") pdf.SetFontLocation("/usr/share/fonts/truetype/dejavu/") pdf.AddUTF8Font("DejaVu", "", "DejaVuSans.ttf") pdf.AddUTF8Font("DejaVu", "B", "DejaVuSans-Bold.ttf") pdf.AddPage() pdf.SetFont("DejaVu", "B", 14) pdf.Cell(0, 10, r.Ticker+" — FlowSight Report") pdf.Ln(12) pdf.SetFont("DejaVu", "", 10) pdf.Cell(0, 6, "Generated "+r.GeneratedAt) pdf.Ln(8) for _, s := range r.Sections { pdf.SetFont("DejaVu", "B", 12) pdf.Cell(0, 8, s.Name) pdf.Ln(8) pdf.SetFont("DejaVu", "", 10) pdf.MultiCell(0, 5, s.Body, "", "", false) if len(s.Citations) > 0 { pdf.SetFont("DejaVu", "", 8) for _, c := range s.Citations { pdf.MultiCell(0, 4, c.Endpoint+" "+c.Ticker+" @ "+c.SnapshotAt, "", "", false) } } pdf.Ln(4) } var buf bytes.Buffer if err := pdf.Output(&buf); err != nil { return nil, err } return buf.Bytes(), nil }