Post

Productivity Dashboard: Data and Integration

The Productivity Dashboard's data: per-person JSON and Markdown on Azure Files, GitHub and Jira contracts, the schedule as an event order, and what happens when someone changes team.

Productivity Dashboard: Data and Integration

Everything the Productivity Dashboard knows lives in a folder tree shaped like the organization chart: department, team, person, date. That makes it easy to browse and easy to reason about, and it has one consequence that’s easy to miss until it happens: when a person changes team, their history doesn’t move with them. This post covers the data, who owns it, how it flows, and the edge cases that come with modelling people as paths.

Data ownership and classification

DataWriterClassification
…/{user}/daily/{date}/github.json, jira.jsonGitHub and Jira collectorsConfidential: employee activity
…/Epics/{EPIC}/epic-metadata.json, jira-updates.jsonEpic data collectorInternal
…/Users/{user}/{period}/…/summary.mdUser generatorConfidential: about a named person, used in reviews
…/{team}/{period}/…/summary.mdTeam generatorInternal
…/Epics/{EPIC}/…summary.mdEpic generatorInternal
{org_prefix}/PODS/{pod}/…POD generatorInternal (per-member files: confidential)

Each path has exactly one writer; the UI only reads. Per-person data is the sensitive category: not because of what’s in it (work activity, no PHI) but because of how it’s used.

Data retention and deletion

Today there’s no automated pruning: every dated file is kept indefinitely. For an engineering dashboard that would be fine; for personal data used in performance reviews it needs a decision:

  • How long are per-person collector files and summaries kept? (A review cycle plus a margin is a common answer.)
  • What happens to a person’s data when they leave?
  • Are team, epic and POD summaries, which are less personal, kept longer?

A pruning job is simple once the rules exist, for example a CronJob running find /data/<dept>/<team>/<user>/daily -mtime +N -delete per rule. The rules are the hard part, and they’re HR’s and legal’s, not engineering’s.

TODO: What retention did the HR, legal and privacy review set for per-person data, and what happens to it when someone leaves?

Database schema and migrations

The schema is the folder layout plus three file shapes:

FileShape
github.jsonmetadata, commits[], pull_requests[], reviews[], summary{}
jira.jsonmetadata, issues_created[], issues_updated[], comments[], sprint_issues[], summary{}
summary.mdMarkdown with the sections the prompt asks for

Fields collected are configurable per collector (for example comment_fields in the Jira collector), which makes the data scope a config change rather than a code change. That’s useful, and also exactly why the data scope should be reviewed whenever that config changes.

Data validation and quality

  • No activity is explicit: the GitHub collector writes no_activity: true for a day with nothing, so “no data” and “collector didn’t run” are different states.
  • Coverage: every configured person should have both files for each working day; a missing file is usually a wrong GitHub username or Jira email in the identity config.
  • Output check: every PR number and Jira key in a summary should appear in that day’s source files. This one check catches most hallucinations that matter.

The CollectorStats component already shows whether collector data exists for a date; turning that into a daily coverage report per team would surface identity mistakes the day they happen.

Import and export requirements

  • Import: none; all data comes from GitHub and Jira.
  • Export: summaries can be printed or exported from the UI (PrintExport), which is how they reach review documents and stakeholder emails.
  • Source export: raw collector data for a user and date range (/api/collector-data/aggregated), which is the evidence behind any summary.

External systems and third-party integrations

SystemDirectionAuthUsed by
GitHub REST and GraphQLReadPer-org GitHub App; PAT fallbackGitHub and epic collectors
Jira Cloud RESTReadEmail + API tokenJira and epic collectors
Azure OpenAIRead / write (prompts)API keyAll generators (production)
OpenAI, LM StudioSameAPI key / noneAlternatives; LM Studio for local development
Kubernetes APIWrite (Jobs)ServiceAccount + RoleUI refresh
Azure FilesRead / writeWorkload IdentityAll components

The epic collector also links GitHub branches to Jira story keys, so code work shows up under the right epic even when the PR doesn’t mention the ticket in its title.

Event and messaging contracts

There’s no message bus; the contracts are files and time:

UTCJobReadsWrites
02:00Epic data collectorJira, GitHubEpic data
03:00Epic summary generatorEpic dataEpic summaries
04:00User summary generatorPrevious run’s github.json / jira.jsonUser summaries
04:00POD summary generatorUser data filtered by Jira labelPOD summaries
05:00Team summary generatorUser summaries (waits up to 300 s)Team summaries
06:00GitHub and Jira collectorsGitHub, JiraToday’s collector files

Generators run before the collectors on the same day on purpose: they summarize the previous run’s collection, so yesterday’s summaries are ready before the Americas’ working day, and the 06:00 collection feeds tomorrow. The team generator’s wait is the one explicit dependency; the others rely on the clock.

TODO: Which calendar day does the 06:00 UTC collection cover: the day that just ended, or the current UTC day? If it’s the current day, a “daily” summary only sees activity before 06:00 UTC, which would miss most of the Americas’ working day. Worth stating precisely in the docs.

Backup and restore strategy

DataRegenerable?Backup need
Collector filesPartly; GitHub and Jira search windows and edits change historyYes
SummariesOnly approximately; a new prompt or model writes different textYes, if used in reviews
Code, prompts, configFrom GitNo

Azure Files share snapshots through Azure Backup cover this, with a retention that matches the agreed data retention (and no longer, since backups are also personal data).

Data migration and reconciliation

The migration that happens all the time is people changing teams. Paths include the department and team, so after a move, new data lands under the new team while history stays under the old one. A monthly summary in the month of the move only sees part of the month, and a user page for the new team starts empty.

Two fixes, from cheapest to cleanest:

  1. A small migration script run with each team change that moves the person’s folders to the new path.
  2. Key per-person data on a stable ID (for example the Jira account ID) instead of team-based paths, with the team as metadata.

Reconciliation for accuracy is a weekly sample: pick a few people and days, compare the summary with the raw data view, and record the result as the accuracy KPI.

TODO: How are team moves handled today?

Checklist

  • Per-person data classified confidential, with readers agreed
  • Retention and leaver rules from HR, legal and privacy implemented as a pruning job
  • Collector field lists reviewed whenever they change
  • Daily coverage report per team for identity mistakes
  • Output key check against source on every summary
  • Collection day boundary documented precisely
  • Team moves handled by a script or stable IDs
  • Share backups with retention equal to the data retention

This post is licensed under CC BY 4.0 by the author.