diff --git a/.gitea/workflows/update-readme.yaml b/.gitea/workflows/update-readme.yaml
index d66c80c..1441e0e 100644
--- a/.gitea/workflows/update-readme.yaml
+++ b/.gitea/workflows/update-readme.yaml
@@ -3,6 +3,7 @@ on:
push:
paths:
- 'public/code-stats.json'
+ - 'public/heatmap.json'
workflow_dispatch:
jobs:
@@ -26,6 +27,11 @@ jobs:
elif command -v apt-get &> /dev/null; then apt-get update && apt-get install -y jq; fi
fi
+ if ! command -v python3 &> /dev/null; then
+ if command -v apk &> /dev/null; then apk add --no-cache python3;
+ elif command -v apt-get &> /dev/null; then apt-get update && apt-get install -y python3; fi
+ fi
+
# Use our own heatmap.json instead of Gitea API
HEATMAP_FILE="public/heatmap.json"
TOTAL_COMMITS=$(jq '[.days[] | .count] | add' "$HEATMAP_FILE")
@@ -37,6 +43,8 @@ jobs:
FORMATTED_LOC=$(printf "%'d" $LOC)
echo "FORMATTED_LOC=$FORMATTED_LOC" >> $GITHUB_ENV
+ python3 scripts/generate_heatmap_svg.py public/heatmap.json public/heatmap.svg
+
sed "s/REPLACE_ME_LOC/$FORMATTED_LOC/g" README.template | \
sed "s/REPLACE_ME_COMMITS/$TOTAL_COMMITS/g" > README.md
@@ -44,11 +52,11 @@ jobs:
run: |
git config user.name "Ares-Bot"
git config user.email "ares@beane.me"
- git add README.md
+ git add README.md public/heatmap.svg
if git diff --staged --quiet; then
echo "No changes to commit"
else
- git commit -m "chore: sync code velocity to ${{ env.FORMATTED_LOC }} LOC [skip ci]"
+ git commit -m "chore: sync profile metrics [skip ci]"
git push http://patrick:${{ secrets.PAT_TOKEN }}@gitea-http:3000/${{ github.repository }}.git ${{ github.ref_name }}
fi
diff --git a/README.md b/README.md
index 6810fb3..24a25ec 100644
--- a/README.md
+++ b/README.md
@@ -77,6 +77,10 @@ Actively used across this environment or in adjacent projects:
- **Ares:** Ryzen 9 9950X sustaining ~0.06 load avg while running Gitea and a Kubernetes control plane
- **Resilience:** Automated failover between AWS and peer nodes
+## 📈 Activity
+
+
+
---
### 🧩 Deployment Patterns
diff --git a/README.template b/README.template
index 8196743..e0f361d 100644
--- a/README.template
+++ b/README.template
@@ -77,6 +77,10 @@ Actively used across this environment or in adjacent projects:
- **Ares:** Ryzen 9 9950X sustaining ~0.06 load avg while running Gitea and a Kubernetes control plane
- **Resilience:** Automated failover between AWS and peer nodes
+## 📈 Activity
+
+
+
---
### 🧩 Deployment Patterns
diff --git a/public/heatmap.svg b/public/heatmap.svg
new file mode 100644
index 0000000..dfb3386
--- /dev/null
+++ b/public/heatmap.svg
@@ -0,0 +1,392 @@
+
diff --git a/scripts/generate_heatmap_svg.py b/scripts/generate_heatmap_svg.py
new file mode 100644
index 0000000..dd2f21f
--- /dev/null
+++ b/scripts/generate_heatmap_svg.py
@@ -0,0 +1,148 @@
+#!/usr/bin/env python3
+import json
+import math
+import sys
+from datetime import datetime, timedelta
+from pathlib import Path
+
+
+COLORS = [
+ "#ebedf0",
+ "#9be9a8",
+ "#40c463",
+ "#30a14e",
+ "#216e39",
+]
+
+CELL = 12
+GAP = 2
+RECT = CELL - GAP
+LEFT_PAD = 30
+TOP_PAD = 20
+RIGHT_PAD = 10
+BOTTOM_PAD = 10
+FONT_FAMILY = "-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif"
+LABEL_COLOR = "#57606a"
+
+
+def load_heatmap(path):
+ with path.open() as handle:
+ return json.load(handle)
+
+
+def date_range(start, end):
+ current = start
+ while current <= end:
+ yield current
+ current += timedelta(days=1)
+
+
+def previous_sunday(day):
+ return day - timedelta(days=(day.weekday() + 1) % 7)
+
+
+def next_saturday(day):
+ return day + timedelta(days=(5 - day.weekday()) % 7)
+
+
+def intensity(count, max_count):
+ if count <= 0 or max_count <= 0:
+ return 0
+ return max(1, min(4, math.ceil((count / max_count) * 4)))
+
+
+def weekday_row(day):
+ return (day.weekday() + 1) % 7
+
+
+def month_labels(start, end):
+ labels = [(0, start.strftime("%b"))]
+ total_weeks = ((end - start).days // 7) + 1
+
+ for week in range(total_weeks):
+ week_start = start + timedelta(days=week * 7)
+ for offset in range(7):
+ day = week_start + timedelta(days=offset)
+ if day.day == 1 and week != 0:
+ labels.append((week, day.strftime("%b")))
+ break
+
+ return labels
+
+
+def build_rect(day, start, counts, max_count):
+ date_str = day.isoformat()
+ count = counts.get(date_str, 0)
+ level = intensity(count, max_count)
+ x = LEFT_PAD + (((day - start).days // 7) * CELL)
+ y = TOP_PAD + (weekday_row(day) * CELL)
+
+ commit_label = "commit" if count == 1 else "commits"
+
+ return (
+ f''
+ f"{date_str}: {count} {commit_label}"
+ ""
+ )
+
+
+def generate_svg(data):
+ counts = {entry["date"]: entry["count"] for entry in data["days"]}
+ max_count = data["max_daily_commits"]
+
+ start = datetime.strptime(data["from_date"], "%Y-%m-%d").date()
+ end = datetime.strptime(data["to_date"], "%Y-%m-%d").date()
+ aligned_start = previous_sunday(start)
+ aligned_end = next_saturday(end)
+ weeks = ((aligned_end - aligned_start).days // 7) + 1
+
+ width = LEFT_PAD + (weeks * CELL) + RIGHT_PAD
+ height = TOP_PAD + (7 * CELL) + BOTTOM_PAD
+
+ svg = [
+ f'")
+ return "\n".join(svg) + "\n"
+
+
+def main():
+ if len(sys.argv) not in {2, 3}:
+ print(
+ "Usage: generate_heatmap_svg.py [output.svg]",
+ file=sys.stderr,
+ )
+ return 1
+
+ input_path = Path(sys.argv[1])
+ output = generate_svg(load_heatmap(input_path))
+
+ if len(sys.argv) == 3:
+ output_path = Path(sys.argv[2])
+ output_path.parent.mkdir(parents=True, exist_ok=True)
+ output_path.write_text(output)
+ else:
+ sys.stdout.write(output)
+
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())