diff --git a/app/sprint.py b/app/sprint.py new file mode 100644 index 0000000..799cef6 --- /dev/null +++ b/app/sprint.py @@ -0,0 +1,79 @@ +"""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) diff --git a/app/templates/activity.html b/app/templates/activity.html index 498d2b9..8b296ef 100644 --- a/app/templates/activity.html +++ b/app/templates/activity.html @@ -16,12 +16,14 @@ diff --git a/app/templates/briefing.html b/app/templates/briefing.html index 1cc3f71..76596c9 100644 --- a/app/templates/briefing.html +++ b/app/templates/briefing.html @@ -17,11 +17,13 @@ diff --git a/app/templates/capture.html b/app/templates/capture.html index 2b1d1d3..2462122 100644 --- a/app/templates/capture.html +++ b/app/templates/capture.html @@ -13,12 +13,14 @@ diff --git a/app/templates/digest.html b/app/templates/digest.html index c09f215..618081f 100644 --- a/app/templates/digest.html +++ b/app/templates/digest.html @@ -13,6 +13,7 @@