feat: metrics dashboards with SVG charts, CSV export, leaderboard (phase 10)

- aggregations over the immutable bountyAwards ledger: totals, weekly
  buckets ($dateTrunc), per-developer and per-customer groupings
- timeline-derived: approval rate, time logged, assigned→approved lead
  time, imported→published atomization lead time, open board depth
- developer + consultant dashboards (admin = global consultant view),
  date-range filters, CSV export of the ledger
- leaderboard (top 10 by bounty) honoring the new leaderboardOptOut
  profile setting
- hand-rolled SVG line and bar charts (~150 lines, no chart library)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
etalon
2026-06-12 20:19:57 +02:00
parent 7d3140334e
commit b1c9a42810
11 changed files with 938 additions and 14 deletions
+6 -2
View File
@@ -38,8 +38,9 @@ func (s *Server) handlePatchProfile(w http.ResponseWriter, r *http.Request) {
Contact *domain.Contact `json:"contact"`
Extra *map[string]any `json:"extra"`
Settings *struct {
Theme *string `json:"theme"`
Notifications *domain.NotificationPrefs `json:"notifications"`
Theme *string `json:"theme"`
Notifications *domain.NotificationPrefs `json:"notifications"`
LeaderboardOptOut *bool `json:"leaderboardOptOut"`
} `json:"settings"`
}
if !decodeJSON(w, r, &req) {
@@ -79,6 +80,9 @@ func (s *Server) handlePatchProfile(w http.ResponseWriter, r *http.Request) {
if req.Settings.Notifications != nil {
set["settings.notifications"] = *req.Settings.Notifications
}
if req.Settings.LeaderboardOptOut != nil {
set["settings.leaderboardOptOut"] = *req.Settings.LeaderboardOptOut
}
}
if len(set) == 0 {
writeError(w, http.StatusBadRequest, "empty_update", "no recognized fields to update")