115 lines
5.2 KiB
Python
115 lines
5.2 KiB
Python
from core.models import ProgressReport, Task
|
|
|
|
class MarkdownBuilder:
|
|
@staticmethod
|
|
def _parse_progress(p_val: str) -> int:
|
|
if p_val == "1":
|
|
return 100
|
|
try:
|
|
return int(p_val.replace("%", ""))
|
|
except:
|
|
return 0
|
|
|
|
@staticmethod
|
|
def _get_status_badge(status: str) -> str:
|
|
s = status.upper().strip()
|
|
if "COMPLETED" in s:
|
|
return ""
|
|
if "DURING" in s or "PROCESS" in s:
|
|
return ""
|
|
if "NOT STARTED" in s:
|
|
return ""
|
|
return f"`{status}`"
|
|
|
|
@staticmethod
|
|
def build_table(report: ProgressReport) -> str:
|
|
if not report.tasks:
|
|
return "_No tasks found._"
|
|
|
|
header = "| Phase | ID | Task | Assignee | Timeline | Progress | Status |\n"
|
|
separator = "| :--- | :--- | :--- | :--- | :--- | :--- | :--- |\n"
|
|
rows = []
|
|
|
|
current_phase = ""
|
|
for task in report.tasks:
|
|
phase_display = f"**{task.phase}**" if task.phase != current_phase else ""
|
|
current_phase = task.phase
|
|
|
|
badge = MarkdownBuilder._get_status_badge(task.status)
|
|
prog_val = MarkdownBuilder._parse_progress(task.progress_val)
|
|
|
|
filled = prog_val // 10
|
|
bar = "█" * filled + "░" * (10 - filled)
|
|
|
|
rows.append(
|
|
f"| {phase_display} | `{task.task_id}` | {task.task_name} | `{task.assignee}` | {task.start_date} - {task.end_date} | `{bar} {prog_val}%` | {badge} |"
|
|
)
|
|
|
|
return header + separator + "\n".join(rows)
|
|
|
|
@staticmethod
|
|
def build_team_progress(report: ProgressReport) -> str:
|
|
if not report.tasks:
|
|
return ""
|
|
|
|
phases = {}
|
|
for t in report.tasks:
|
|
if t.phase not in phases:
|
|
phases[t.phase] = []
|
|
phases[t.phase].append(t)
|
|
|
|
total_tasks = len(report.tasks)
|
|
completed = len([t for t in report.tasks if "COMPLETED" in t.status.upper()])
|
|
in_progress = len([t for t in report.tasks if "DURING" in t.status.upper()])
|
|
avg_progress = sum(MarkdownBuilder._parse_progress(t.progress_val) for t in report.tasks) / total_tasks
|
|
|
|
# Build phase rows — NO leading spaces (prevents GitHub treating them as code blocks)
|
|
phase_rows_html = ""
|
|
colors = ["#B026FF", "#33CCFF", "#FF3333", "#FFD700"]
|
|
for i, (name, t_list) in enumerate(phases.items()):
|
|
p_total = len(t_list)
|
|
p_done = len([t for t in t_list if "COMPLETED" in t.status.upper()])
|
|
p_avg = sum(MarkdownBuilder._parse_progress(t.progress_val) for t in t_list) / p_total
|
|
color = colors[i % len(colors)]
|
|
|
|
# Key fix: use textwrap.dedent-style — keep lines flush left, no indentation
|
|
phase_rows_html += (
|
|
f'<tr>'
|
|
f'<td colspan="2" style="padding-top:10px;font-size:12px;color:#EEE;">'
|
|
f'<table width="100%" border="0" cellpadding="0" cellspacing="0">'
|
|
f'<tr><td align="left">{name}</td><td align="right">{p_avg:.0f}% ({p_done}/{p_total})</td></tr>'
|
|
f'</table>'
|
|
f'<div style="width:100%;background:#222;height:6px;border-radius:3px;overflow:hidden;margin-top:4px;">'
|
|
f'<div style="width:{p_avg}%;background:{color};height:100%;"></div>'
|
|
f'</div>'
|
|
f'</td>'
|
|
f'</tr>'
|
|
)
|
|
|
|
# Entire dashboard on as few lines as possible — no indented lines
|
|
dashboard = (
|
|
'<div align="center">'
|
|
'<table width="100%" border="0" cellpadding="0" cellspacing="0" style="background:#0D1117;border:1px solid #30363D;border-radius:8px;">'
|
|
'<tr>'
|
|
'<td width="40%" align="center" valign="middle" style="padding:25px;border-right:1px solid #30363D;">'
|
|
'<img src="https://api.iconify.design/lucide/target.svg?color=%23FFD700" width="32" height="32"/>'
|
|
f'<div style="margin:10px 0 5px 0;color:#FFD700;font-size:12px;letter-spacing:1px;font-weight:bold;">SYSTEM READINESS</div>'
|
|
f'<div style="font-size:48px;font-weight:800;color:#FFF;margin:5px 0;">{avg_progress:.1f}%</div>'
|
|
'<table border="0" cellpadding="0" cellspacing="0" style="margin-top:15px;">'
|
|
'<tr>'
|
|
f'<td align="center" style="padding:0 10px;"><div style="color:#2ea44f;font-size:16px;font-weight:bold;">{completed}</div><div style="color:#8B949E;font-size:9px;">DONE</div></td>'
|
|
f'<td style="border-left:1px solid #30363D;padding:0 10px;" align="center"><div style="color:#005cc5;font-size:16px;font-weight:bold;">{in_progress}</div><div style="color:#8B949E;font-size:9px;">ACTIVE</div></td>'
|
|
f'<td style="border-left:1px solid #30363D;padding:0 10px;" align="center"><div style="color:#8B949E;font-size:16px;font-weight:bold;">{total_tasks - completed - in_progress}</div><div style="color:#8B949E;font-size:9px;">BACKLOG</div></td>'
|
|
'</tr>'
|
|
'</table>'
|
|
'</td>'
|
|
'<td width="60%" valign="top" style="padding:20px;">'
|
|
'<div style="color:#8B949E;font-size:11px;text-transform:uppercase;letter-spacing:1px;font-weight:bold;margin-bottom:10px;">Phase Breakdown</div>'
|
|
f'<table width="100%" border="0" cellpadding="0" cellspacing="0">{phase_rows_html}</table>'
|
|
'</td>'
|
|
'</tr>'
|
|
'</table>'
|
|
'</div>'
|
|
)
|
|
|
|
return dashboard |