package main import ( "fmt" "strings" ) // renderMarkdown генерирует готовый Markdown-документ из структуры PostMortem. func renderMarkdown(pm PostMortem) string { var sb strings.Builder line := func(s string) { sb.WriteString(s + "\n") } linef := func(format string, a ...any) { sb.WriteString(fmt.Sprintf(format, a...) + "\n") } blank := func() { sb.WriteString("\n") } hr := func() { line("---"); blank() } // ── Заголовок ──────────────────────────────────────────────────────────── line("# 🔥 Post Mortem: " + pm.Title) blank() linef("> **Статус:** %s ", pm.Status) linef("> **Severity:** %s ", pm.Severity) linef("> **Дата инцидента:** %s ", pm.IncidentDate) linef("> **Дата постмортема:** %s ", pm.PostmortemDate) linef("> **Автор:** %s ", pm.Author) linef("> **Участники разбора:** %s ", pm.Participants) blank() hr() // ── Резюме ─────────────────────────────────────────────────────────────── line("## 📋 Краткое резюме") blank() line(pm.Summary) blank() hr() // ── Impact ─────────────────────────────────────────────────────────────── line("## 💥 Влияние (Impact)") blank() line("| Параметр | Значение |") line("|----------|----------|") linef("| Длительность | %s |", pm.Duration) linef("| Затронутые пользователи | %s |", pm.AffectedUsers) linef("| Затронутые сервисы | %s |", pm.AffectedServices) linef("| Финансовые потери | %s |", pm.FinancialLoss) linef("| Нарушение SLA | %s |", pm.SLABreach) linef("| Обращений в поддержку | %s |", pm.SupportTickets) blank() hr() // ── Хронология ─────────────────────────────────────────────────────────── line("## 🕐 Хронология (Timeline)") blank() line("| Время | Событие |") line("|-------|---------|") for _, e := range pm.Timeline { linef("| %s | %s |", e.Time, e.Event) } blank() hr() // ── Корневая причина ───────────────────────────────────────────────────── line("## 🔍 Корневая причина (Root Cause)") blank() line(pm.RootCause) blank() line("### Анализ «5 Почему»") blank() for i, item := range pm.WhyAnalysis { linef("%d. **Почему** %s → %s", i+1, item.Why, item.Answer) } blank() hr() // ── Сопутствующие факторы ──────────────────────────────────────────────── line("## ⚠️ Сопутствующие факторы") blank() for _, f := range pm.Contributing { linef("- %s", f) } blank() hr() // ── Что сработало хорошо ───────────────────────────────────────────────── line("## ✅ Что сработало хорошо") blank() for _, f := range pm.WentWell { linef("- %s", f) } blank() hr() // ── Что улучшить ───────────────────────────────────────────────────────── line("## 🔧 Что можно улучшить") blank() for _, f := range pm.ToImprove { linef("- %s", f) } blank() hr() // ── Action Items ───────────────────────────────────────────────────────── line("## 📌 Action Items") blank() line("| # | Задача | Ответственный | Дедлайн | Статус |") line("|---|--------|---------------|---------|--------|") for i, item := range pm.ActionItems { linef("| %d | %s | %s | %s | 🔲 Открыта |", i+1, item.Task, item.Assignee, item.Deadline) } blank() hr() // ── Ссылки ─────────────────────────────────────────────────────────────── line("## 💬 Дополнительные материалы") blank() linef("- Тикет инцидента: %s", pm.TicketLink) linef("- Логи / дашборд: %s", pm.LogsLink) linef("- PR / деплой: %s", pm.PRLink) linef("- Запись встречи: %s", pm.MeetingLink) blank() hr() // ── Футер ──────────────────────────────────────────────────────────────── line("*Документ подготовлен в соответствии с процессом blameless post mortem. Цель — улучшение системы, а не поиск виноватых.*") return sb.String() }