"""Sprint story (roadmap #4): the milestone's tale, written by the journal. Nobody writes the end-of-milestone report by hand. Kanbante's event journal already knows what shipped, how long it took and what dragged — this module just turns that into clean markdown, ready to archive in ObsiGate. """ def format_sprint_markdown( repo: str, milestone_title: str, due_on: str | None, period_label: str, delivered: list[dict], carried: list[dict], counts: dict, avg_cycle_days: float | None, ) -> str: lines = [f"# 📖 Récit de sprint — {repo} · {milestone_title}", ""] lines.append(f"*Période : {period_label}*") if due_on: lines.append(f"*Échéance du milestone : {due_on[:10]}*") lines.append("") lines.append(f"## ✅ Livré ({len(delivered)})") if delivered: for item in delivered: cycle = ( f" — ⏱️ {item['cycle_days']} j de cycle" if item.get("cycle_days") is not None else "" ) lines.append( f"- [#{item['number']}]({item.get('html_url', '')}) " f"{item.get('title', '')}{cycle}".rstrip() ) else: lines.append("- Rien n'a été fermé dans ce milestone pour l'instant.") lines.append("") slowest = sorted( (d for d in delivered if d.get("cycle_days") is not None), key=lambda d: -d["cycle_days"], )[:3] if slowest: lines.append("## 🐌 Les plus lentes") for item in slowest: lines.append( f"- #{item['number']} {item.get('title', '')} " f"— {item['cycle_days']} j de cycle" ) lines.append("") lines.append(f"## ↩️ Reporté ({len(carried)})") if carried: for item in carried: lines.append( f"- #{item['number']} {item.get('title', '')} " f"— encore en *{item.get('column_label', 'backlog')}*" ) else: lines.append("- Rien : tout le milestone est livré. 🎉") lines.append("") lines.append("## 📈 En chiffres") lines.append(f"- Issues du milestone : {len(delivered) + len(carried)}") lines.append(f"- Fermées : {len(delivered)}") if avg_cycle_days is not None: lines.append(f"- Temps de cycle moyen : {avg_cycle_days} j") lines.append(f"- Déplacements de cartes : {counts.get('moved', 0)}") lines.append(f"- Commentaires : {counts.get('commented', 0)}") lines.append(f"- Réouvertures : {counts.get('reopened', 0)}") lines.append("") lines.append( "*Récit généré par Kanbante à partir de son journal d'événements " "et des issues Gitea du milestone.*" ) return "\n".join(lines)