From c4c47ab445f340d1235011f6de5798d2a5f8ce04 Mon Sep 17 00:00:00 2001
From: NickiCloud
Date: Wed, 2 Sep 2026 21:15:59 +0200
Subject: [PATCH] feat: add productivity biorhythm clock, developer
achievements and quote [skip ci]
---
.gitea/workflows/update-profile.yml | 5 +-
README.md | 63 ++++++++---
heatmap.svg | 30 ++---
productivity.svg | 83 ++++++++++++++
src/ProfileManager/Models/ProfileStats.cs | 15 +++
src/ProfileManager/Program.cs | 26 +++--
src/ProfileManager/Services/ReadmeInjector.cs | 39 ++++++-
.../Services/StatsCalculator.cs | 73 +++++++++++-
src/ProfileManager/Services/SvgGenerator.cs | 104 ++++++++++++++++++
9 files changed, 398 insertions(+), 40 deletions(-)
create mode 100644 productivity.svg
diff --git a/.gitea/workflows/update-profile.yml b/.gitea/workflows/update-profile.yml
index f927d9a..ef63985 100644
--- a/.gitea/workflows/update-profile.yml
+++ b/.gitea/workflows/update-profile.yml
@@ -13,6 +13,7 @@ on:
paths-ignore:
- 'heatmap.svg'
- 'team-stats.svg'
+ - 'productivity.svg'
- 'README.md'
jobs:
@@ -50,9 +51,9 @@ jobs:
git config --global user.email "bot@webcore-consulting.dev"
# Prüfen, ob sich SVG-Dateien oder README.md geändert haben
- if git status --porcelain | grep -E "(heatmap.svg|team-stats.svg|README.md)"; then
+ if git status --porcelain | grep -E "(heatmap.svg|team-stats.svg|productivity.svg|README.md)"; then
echo "Änderungen an Grafiken oder README.md erkannt. Committe..."
- git add heatmap.svg team-stats.svg README.md
+ git add heatmap.svg team-stats.svg productivity.svg README.md
# GANZ WICHTIG: [skip ci] verhindert Endlosschleifen in Gitea Actions!
git commit -m "chore(profile): update personal & team telemetry [skip ci]"
diff --git a/README.md b/README.md
index 754dae6..4578b06 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@
-
+
@@ -115,11 +115,11 @@ Ich bin **Nicki (Nicklas)**, angehender Softwareentwickler mit fundiertem Fokus
> *Echtzeit-Feed der letzten Aktionen und Commits über alle privaten & Team-Repositories.*
-- ⚡ **feat: add team telemetry dashboard, byte-level language analytics and tabs [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 2 Minuten*
-- ⚡ **fix(svg): remove utf-8 bom and add xml declaration for gitea typesniffer [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 10 Minuten*
-- ⚡ **chore(profile): update activity heatmap & stats (22 contributions) [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 18 Minuten*
-- ⚡ **Initial profile setup** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 19 Minuten*
-- ✨ **Repository neu erstellt** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 55 Minuten*
+- ⚡ **feat: add live commit radar, activity feed and homelab telemetry [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 2 Minuten*
+- ⚡ **feat: add team telemetry dashboard, byte-level language analytics and tabs [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 6 Minuten*
+- ⚡ **fix(svg): remove utf-8 bom and add xml declaration for gitea typesniffer [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 14 Minuten*
+- ⚡ **chore(profile): update activity heatmap & stats (22 contributions) [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 21 Minuten*
+- ⚡ **Initial profile setup** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 22 Minuten*
@@ -127,7 +127,37 @@ Ich bin **Nicki (Nicklas)**, angehender Softwareentwickler mit fundiertem Fokus
-🛡️ Tab 4: Homelab, Server-Infrastruktur & Telemetrie
+⏱️ Tab 4: Coding Biorhythmus, Produktivität & Trophäen
+
+> *Analyse der aktiven Entwicklungsphasen, Trophäen und Entwickler-Weisheiten.*
+
+
+

+
+
+#### 🏆 Entwickler-Erfolge & Achievements:
+
+| Trophy | Rang | Beschreibung | Status |
+| :--- | :---: | :--- | :---: |
+| 🏆 **Streak Master** | `Gold` | Mindestens 4 Tage ununterbrochen aktiv gecodet | **4 Tage** ✅ |
+| 💾 **Code Sovereign** | `Diamond` | Über 1,5 MB Code in Gitea verwaltet | **1,9 MB** ✅ |
+| ⚡ **Zero-Dependency** | `Master` | Reines .NET 10 BCL ohne fremde NuGet-Pakete | **100% Native** ✅ |
+| 🌙 **Night-Owl Veteran** | `Elite` | Hohe nächtliche Coding-Aktivität | **26,3% Nachts** ✅ |
+| 🏰 **Homelab Overlord** | `Legendary` | Self-Hosted Debian, Docker, Gitea, Mailcow | **Vollautonom** ✅ |
+
+
+#### 💡 Tägliche Entwickler-Philosophie:
+
+> *"Clean code always looks like it was written by someone who cares."*
+> — **Robert C. Martin**
+
+
+
+
+
+
+
+🛡️ Tab 5: Homelab, Server-Infrastruktur & Telemetrie
```
┌─────────────────────────────────────────────────────────────┐
@@ -158,6 +188,19 @@ Ich bin **Nicki (Nicklas)**, angehender Softwareentwickler mit fundiertem Fokus
+
+
+
+🎮 Tab 6: Gaming Battlestation & Loadouts
+
+| Game | Fokus & Rolle | Lieblingselement / Operator |
+| :--- | :--- | :--- |
+| ⛏️ **Minecraft** | Technical Automation & Redstone | Automatisierte Farmen, Create Mod, Server-Hosting |
+| 🎯 **Rainbow Six Siege** | Tactical Anchor & Area Denial | Smoke, Mute, Thermite, Thatcher |
+| ⚔️ **Warframe** | High-Level Survival & Grinding | Mesa Prime, Wisp, Volt |
+
+
+
---
## 🛠️ Ausgewählte Eigenprojekte
@@ -173,12 +216,6 @@ Ich bin **Nicki (Nicklas)**, angehender Softwareentwickler mit fundiertem Fokus
-### 🎮 Gaming & Fun Corner
-
-[](https://minecraft.net)
-[](https://www.ubisoft.com/game/rainbow-six/siege)
-[](https://www.warframe.com/)
-
```csharp
var nicki = new Developer
{
diff --git a/heatmap.svg b/heatmap.svg
index 66bbbf0..76b594c 100644
--- a/heatmap.svg
+++ b/heatmap.svg
@@ -74,7 +74,7 @@
🦊 NickiCloud • Persönliche Aktivität
-
25 Commits im letzten Jahr
+
26 Commits im letzten Jahr
Sep
@@ -1172,8 +1172,8 @@
01.09.2026: 9 Commits
-
- 02.09.2026: 9 Commits
+
+ 02.09.2026: 10 Commits
03.09.2026: 0 Commits
@@ -1192,7 +1192,7 @@
📦
- 25
+ 26
Jahres-Beiträge
@@ -1209,30 +1209,30 @@
-
- C#: 57,3%
+
+ C#: 57,4%
-
- HTML: 39,4%
+
+ HTML: 39,2%
-
- CSS: 2,3%
+
+ CSS: 2,2%
-
+
JavaScript: 0,4%
-
+
PowerShell: 0,4%
- C# 57,3%
+ C# 57,4%
- HTML 39,4%
+ HTML 39,2%
- CSS 2,3%
+ CSS 2,2%
JavaScript 0,4%
diff --git a/productivity.svg b/productivity.svg
new file mode 100644
index 0000000..52314bc
--- /dev/null
+++ b/productivity.svg
@@ -0,0 +1,83 @@
+
+
diff --git a/src/ProfileManager/Models/ProfileStats.cs b/src/ProfileManager/Models/ProfileStats.cs
index 7341413..b630f1a 100644
--- a/src/ProfileManager/Models/ProfileStats.cs
+++ b/src/ProfileManager/Models/ProfileStats.cs
@@ -26,6 +26,20 @@ public sealed class DayContribution
public required int Row { get; init; } // 0..6 (0=Mo..6=So)
}
+public sealed class ProductivityStats
+{
+ public required double MorningPct { get; init; }
+ public required double AfternoonPct { get; init; }
+ public required double EveningPct { get; init; }
+ public required double NightPct { get; init; }
+ public required int MorningCount { get; init; }
+ public required int AfternoonCount { get; init; }
+ public required int EveningCount { get; init; }
+ public required int NightCount { get; init; }
+ public required string MostActiveDayOfWeek { get; init; }
+ public required string DevArchetype { get; init; }
+}
+
public sealed class TeamStats
{
public required string OrganizationName { get; init; }
@@ -54,4 +68,5 @@ public sealed class ProfileStats
public required List PersonalRepos { get; init; }
public required TeamStats? TeamStats { get; init; }
public required List RecentActivities { get; init; }
+ public required ProductivityStats Productivity { get; init; }
}
diff --git a/src/ProfileManager/Program.cs b/src/ProfileManager/Program.cs
index d53759f..f4c341d 100644
--- a/src/ProfileManager/Program.cs
+++ b/src/ProfileManager/Program.cs
@@ -15,23 +15,28 @@ internal static class Program
====================================================
""");
+ var sw = System.Diagnostics.Stopwatch.StartNew();
+
var username = GetConfigValue(args, "--username", "GITEA_USERNAME", "NickiCloud");
var apiUrl = GetConfigValue(args, "--api-url", "GITEA_API_URL", "https://git.webcore-consulting.dev/api/v1");
var token = GetConfigValue(args, "--token", "GITEA_TOKEN", string.Empty);
var svgPath = GetConfigValue(args, "--output-svg", "OUTPUT_SVG", "heatmap.svg");
var teamSvgPath = GetConfigValue(args, "--team-svg", "TEAM_SVG", "team-stats.svg");
+ var prodSvgPath = GetConfigValue(args, "--prod-svg", "PROD_SVG", "productivity.svg");
var readmePath = GetConfigValue(args, "--readme", "README_PATH", "README.md");
var repoRoot = FindRepoRoot();
if (!Path.IsPathRooted(svgPath)) svgPath = Path.GetFullPath(Path.Combine(repoRoot, svgPath));
if (!Path.IsPathRooted(teamSvgPath)) teamSvgPath = Path.GetFullPath(Path.Combine(repoRoot, teamSvgPath));
+ if (!Path.IsPathRooted(prodSvgPath)) prodSvgPath = Path.GetFullPath(Path.Combine(repoRoot, prodSvgPath));
if (!Path.IsPathRooted(readmePath)) readmePath = Path.GetFullPath(Path.Combine(repoRoot, readmePath));
- Console.WriteLine($"[Config] Target User : {username}");
- Console.WriteLine($"[Config] API Endpoint : {apiUrl}");
- Console.WriteLine($"[Config] Heatmap SVG : {svgPath}");
- Console.WriteLine($"[Config] Team Stats SVG : {teamSvgPath}");
- Console.WriteLine($"[Config] Target README : {readmePath}");
+ Console.WriteLine($"[Config] Target User : {username}");
+ Console.WriteLine($"[Config] API Endpoint : {apiUrl}");
+ Console.WriteLine($"[Config] Heatmap SVG : {svgPath}");
+ Console.WriteLine($"[Config] Team Stats SVG : {teamSvgPath}");
+ Console.WriteLine($"[Config] Productivity SVG : {prodSvgPath}");
+ Console.WriteLine($"[Config] Target README : {readmePath}");
Console.WriteLine();
try
@@ -94,15 +99,22 @@ internal static class Program
await File.WriteAllTextAsync(teamSvgPath, teamSvg, utf8WithoutBom);
}
- // Step 6: Inject all sections into README.md
+ // Step 6: Generate Productivity SVG
+ Console.WriteLine($"[SVG] Rendering Productivity SVG: {prodSvgPath}");
+ var prodSvg = SvgGenerator.GenerateProductivity(stats.Productivity);
+ EnsureDirectory(prodSvgPath);
+ await File.WriteAllTextAsync(prodSvgPath, prodSvg, utf8WithoutBom);
+
+ // Step 7: Inject all sections into README.md
if (File.Exists(readmePath))
{
Console.WriteLine($"[README] Injecting personal, team & language metrics into: {readmePath}");
ReadmeInjector.InjectAll(readmePath, stats);
}
+ sw.Stop();
Console.ForegroundColor = ConsoleColor.Green;
- Console.WriteLine("\n✨ Multi-tier profile update completed successfully!");
+ Console.WriteLine($"\n✨ Multi-tier profile update completed successfully in {sw.ElapsedMilliseconds}ms!");
Console.ResetColor();
return 0;
}
diff --git a/src/ProfileManager/Services/ReadmeInjector.cs b/src/ProfileManager/Services/ReadmeInjector.cs
index 8c2bb1a..778e7f8 100644
--- a/src/ProfileManager/Services/ReadmeInjector.cs
+++ b/src/ProfileManager/Services/ReadmeInjector.cs
@@ -20,6 +20,12 @@ public static class ReadmeInjector
private const string StartTelemetryMarker = "";
private const string EndTelemetryMarker = "";
+ private const string StartQuoteMarker = "";
+ private const string EndQuoteMarker = "";
+
+ private const string StartAchievementsMarker = "";
+ private const string EndAchievementsMarker = "";
+
public static bool InjectAll(string readmePath, ProfileStats stats)
{
if (!File.Exists(readmePath))
@@ -139,9 +145,40 @@ public static class ReadmeInjector
sb.AppendLine($"");
});
+ // 6. Inject Developer Achievements
+ content = ReplaceSection(content, StartAchievementsMarker, EndAchievementsMarker, sb =>
+ {
+ sb.AppendLine("| Trophy | Rang | Beschreibung | Status |");
+ sb.AppendLine("| :--- | :---: | :--- | :---: |");
+ sb.AppendLine($"| 🏆 **Streak Master** | `Gold` | Mindestens 4 Tage ununterbrochen aktiv gecodet | **{stats.CurrentStreak} Tage** ✅ |");
+ sb.AppendLine($"| 💾 **Code Sovereign** | `Diamond` | Über 1,5 MB Code in Gitea verwaltet | **{stats.TeamStats?.FormattedTotalSize ?? "1,9 MB"}** ✅ |");
+ sb.AppendLine($"| ⚡ **Zero-Dependency** | `Master` | Reines .NET 10 BCL ohne fremde NuGet-Pakete | **100% Native** ✅ |");
+ sb.AppendLine($"| 🌙 **Night-Owl Veteran** | `Elite` | Hohe nächtliche Coding-Aktivität | **{stats.Productivity.NightPct:F1}% Nachts** ✅ |");
+ sb.AppendLine($"| 🏰 **Homelab Overlord** | `Legendary` | Self-Hosted Debian, Docker, Gitea, Mailcow | **Vollautonom** ✅ |");
+ });
+
+ // 7. Inject Rotating Dev Wisdom Quote
+ content = ReplaceSection(content, StartQuoteMarker, EndQuoteMarker, sb =>
+ {
+ var quotes = new[]
+ {
+ ("Measure twice, refactor once, ship autonomously.", "Nicki"),
+ ("Premature optimization is the root of all evil – but allocating in hot loops is a close second.", "Donald Knuth / Modern .NET"),
+ ("Simplicity is prerequisite for reliability.", "Edsger W. Dijkstra"),
+ ("First, solve the problem. Then, write the code in C#.", "John Johnson"),
+ ("There are only two hard things in Computer Science: cache invalidation and naming things.", "Phil Karlton"),
+ ("Clean code always looks like it was written by someone who cares.", "Robert C. Martin")
+ };
+
+ int dayOfYear = DateTime.UtcNow.DayOfYear;
+ var (text, author) = quotes[dayOfYear % quotes.Length];
+ sb.AppendLine($"> *\"{text}\"* ");
+ sb.AppendLine($"> — **{author}**");
+ });
+
var utf8WithoutBom = new UTF8Encoding(false);
File.WriteAllText(readmePath, content, utf8WithoutBom);
- Console.WriteLine($"[README] Successfully updated all 5 dynamic sections in {readmePath}");
+ Console.WriteLine($"[README] Successfully updated all 7 dynamic sections in {readmePath}");
return true;
}
diff --git a/src/ProfileManager/Services/StatsCalculator.cs b/src/ProfileManager/Services/StatsCalculator.cs
index 0b6ab9f..bfcf2be 100644
--- a/src/ProfileManager/Services/StatsCalculator.cs
+++ b/src/ProfileManager/Services/StatsCalculator.cs
@@ -133,7 +133,75 @@ public static class StatsCalculator
var overallLanguages = AggregateLanguages(allRepos);
var teamLanguages = AggregateLanguages(teamRepos);
- // 6. Build TeamStats
+ // 6. Productivity & Coding Biorhythm Analysis
+ int morningCount = 0, afternoonCount = 0, eveningCount = 0, nightCount = 0;
+ var dayOfWeekMap = new Dictionary();
+
+ foreach (var act in recentActivities)
+ {
+ var localTime = act.Created.AddHours(2); // Europe/Berlin (CEST)
+ int hour = localTime.Hour;
+ if (hour >= 6 && hour < 12) morningCount++;
+ else if (hour >= 12 && hour < 18) afternoonCount++;
+ else if (hour >= 18 && hour < 24) eveningCount++;
+ else nightCount++;
+
+ dayOfWeekMap[localTime.DayOfWeek] = dayOfWeekMap.GetValueOrDefault(localTime.DayOfWeek, 0) + 1;
+ }
+
+ // Incorporate repo update timestamps for richer sample size
+ foreach (var repo in allRepos)
+ {
+ var localTime = repo.UpdatedAt.AddHours(2);
+ int hour = localTime.Hour;
+ if (hour >= 6 && hour < 12) morningCount++;
+ else if (hour >= 12 && hour < 18) afternoonCount++;
+ else if (hour >= 18 && hour < 24) eveningCount++;
+ else nightCount++;
+
+ dayOfWeekMap[localTime.DayOfWeek] = dayOfWeekMap.GetValueOrDefault(localTime.DayOfWeek, 0) + 1;
+ }
+
+ int totalEvents = morningCount + afternoonCount + eveningCount + nightCount;
+ if (totalEvents == 0) totalEvents = 1;
+
+ double morningPct = Math.Round((double)morningCount / totalEvents * 100.0, 1);
+ double afternoonPct = Math.Round((double)afternoonCount / totalEvents * 100.0, 1);
+ double eveningPct = Math.Round((double)eveningCount / totalEvents * 100.0, 1);
+ double nightPct = Math.Round((double)nightCount / totalEvents * 100.0, 1);
+
+ var topDay = dayOfWeekMap.Count > 0 ? dayOfWeekMap.OrderByDescending(kv => kv.Value).First().Key : DayOfWeek.Wednesday;
+ string topDayName = topDay switch
+ {
+ DayOfWeek.Monday => "Montag",
+ DayOfWeek.Tuesday => "Dienstag",
+ DayOfWeek.Wednesday => "Mittwoch",
+ DayOfWeek.Thursday => "Donnerstag",
+ DayOfWeek.Friday => "Freitag",
+ DayOfWeek.Saturday => "Samstag",
+ DayOfWeek.Sunday => "Sonntag",
+ _ => "Mittwoch"
+ };
+
+ string archetype = (nightCount + eveningCount >= morningCount + afternoonCount)
+ ? "🌙 Night-Owl (Höchste Produktivität am Abend & nachts)"
+ : "⚡ Day-Runner (Fokus während der regulären Tagesstunden)";
+
+ var productivity = new ProductivityStats
+ {
+ MorningPct = morningPct,
+ AfternoonPct = afternoonPct,
+ EveningPct = eveningPct,
+ NightPct = nightPct,
+ MorningCount = morningCount,
+ AfternoonCount = afternoonCount,
+ EveningCount = eveningCount,
+ NightCount = nightCount,
+ MostActiveDayOfWeek = topDayName,
+ DevArchetype = archetype
+ };
+
+ // 7. Build TeamStats
TeamStats? teamStats = null;
if (teamRepos.Count > 0 || orgs.Count > 0)
{
@@ -164,7 +232,8 @@ public static class StatsCalculator
OverallLanguages = overallLanguages,
PersonalRepos = personalRepos,
TeamStats = teamStats,
- RecentActivities = recentActivities
+ RecentActivities = recentActivities,
+ Productivity = productivity
};
}
diff --git a/src/ProfileManager/Services/SvgGenerator.cs b/src/ProfileManager/Services/SvgGenerator.cs
index 7960040..b5889ed 100644
--- a/src/ProfileManager/Services/SvgGenerator.cs
+++ b/src/ProfileManager/Services/SvgGenerator.cs
@@ -325,6 +325,110 @@ public static class SvgGenerator
return sb.ToString();
}
+ public static string GenerateProductivity(ProductivityStats prod)
+ {
+ var sb = new StringBuilder(8192);
+
+ const int svgWidth = 740;
+ const int svgHeight = 220;
+ const int gridStartX = 34;
+
+ sb.AppendLine("""""");
+ sb.AppendLine($"""");
+ return sb.ToString();
+ }
+
private static void RenderMetricCard(StringBuilder sb, int x, int y, int w, int h, string icon, string value, string label)
{
sb.AppendLine($" ");