package render import ( "fmt" "math" "strings" ) // MarkdownRenderer renders transcript artifacts as Markdown. type MarkdownRenderer struct{} // Render renders the transcript into deterministic Markdown. func (MarkdownRenderer) Render(transcript Transcript, opts Options) (string, error) { var lines []string title := strings.TrimSpace(opts.Title) if title == "" { title = "Transcript" } lines = append(lines, "# "+title, "") if opts.IncludeMetadata { lines = append(lines, fmt.Sprintf("- Application: %s", transcript.Metadata.Application), fmt.Sprintf("- Version: %s", transcript.Metadata.Version), fmt.Sprintf("- Output schema: %s", transcript.Schema), "", ) } for _, segment := range transcript.Segments { parts := make([]string, 0, 4) if opts.IncludeTimestamps { parts = append(parts, fmt.Sprintf("[%s–%s]", formatTimestamp(segment.Start), formatTimestamp(segment.End))) } if opts.IncludeSegmentIDs { parts = append(parts, fmt.Sprintf("[#%d]", segment.ID)) } text := segment.Text if shouldItalicize(segment.Categories) { text = "*" + text + "*" } parts = append(parts, fmt.Sprintf("**%s:** %s", segment.Speaker, text)) lines = append(lines, strings.Join(parts, " ")) lines = append(lines, "") } output := strings.Join(lines, "\n") if !strings.HasSuffix(output, "\n") { output += "\n" } return output, nil } func shouldItalicize(categories []string) bool { for _, category := range categories { switch category { case "background", "backchannel", "filler": return true } } return false } func formatTimestamp(seconds float64) string { total := int(math.Round(seconds)) if total < 0 { total = 0 } hours := total / 3600 minutes := (total % 3600) / 60 remainder := total % 60 return fmt.Sprintf("%02d:%02d:%02d", hours, minutes, remainder) }