Productivity Dashboard: Development Preparation
Setting up to build the Productivity Dashboard: one folder shape per job plus prompts, k3d, a free local LLM through LM Studio, and configuration-driven people and teams.
The most useful piece of development setup on this project costs nothing: a local LLM. Generators talk to AI through one client that can point at Azure OpenAI, OpenAI or LM Studio, so on a laptop the whole pipeline runs against a local model with no API key and no bill. Prompt iteration (the work that matters most in this project) becomes free and fast. The rest of the setup is the discipline a team needs to take over after five months of solo work: one folder shape, one Taskfile, one cluster that runs on a laptop.
Repository and branching strategy
The project lives in a monorepo that also hosts other cronjob-based tools, sharing a library of clients in cronjobs/common/ (GitHub, Jira, storage, AI, paths). Each component is a top-level folder: prod-github-collector/, prod-jira-collector/, prod-epic-data-collector/, the four prod-*-summary-generator/ folders and productivity-ui/, plus productivity-shared-infra/ for shared cluster resources.
One quirk to know on day one: the generator and collector source (including prompts/) exists twice, in the top-level prod-*/ folders and under productivity-tools/cronjobs/<name>/, and the Dockerfiles copy from productivity-tools/. The two trees are identical today, but an edit to the wrong copy never reaches an image. Picking one source of truth (and deleting the other) is a small change that removes a whole class of “my prompt change didn’t do anything” confusion.
Branches are short-lived and named after the Jira issue, merged to main by pull request. CI builds only the components a change touches.
Coding standards
- Use the shared clients:
GitHubAPIClient,JiraAPIClient,BaseAIClient,FileShareStorageClient, andpath_utilsfor every path. - Configuration, not constants: people, teams, PODs, schedules and field lists live in config.
- Prompts are code: they live in
prompts/, change by pull request, and are reviewed with sample output (see below). - Type hints and Pydantic models at the API boundary (
backend/models.py). - SonarQube’s rules in CI as the enforced baseline.
Project structure
1
2
3
4
5
6
7
8
<component>/
├── Dockerfile
├── requirements.txt
├── config.yaml or src/config.py # defaults
├── src/main.py # entrypoint
├── prompts/ # generators only: daily/weekly/monthly .txt or Jinja2 .j2
├── helm/ # CronJob chart (values, configYaml)
└── tests/
productivity-ui/ has backend/ (FastAPI services: storage, search, refresh, cache), frontend/ (Vite, Tailwind), helm/ and a single multi-stage Dockerfile.
TODO: The repo docs still say the
prod-*cronjobs have no in-treehelm/folder and call the POD generatorpod-summary-generator; the repo now has charts for every component andprod-pod-summary-generator. Worth updating the docs.
Local development setup
1
2
3
4
5
6
7
8
9
10
# Whole stack in k3d
task k3d-create
task productivity-build-all
task productivity-import-all
task productivity-deploy-local
task port-forward-productivity-ui # http://127.0.0.1:9000
# UI only, with hot reload
cd productivity-ui/backend && uvicorn main:app --reload --host 0.0.0.0 --port 9000
cd productivity-ui/frontend && npm install && npm run dev # Vite on :5173
For a generator, run src/main.py directly with PYTHONPATH including cronjobs/common, and the AI provider set to LM Studio:
1
2
3
4
export ai_provider=lmstudio
export LMSTUDIO_ENDPOINT=http://127.0.0.1:1234 # host.k3d.internal:1234 from inside k3d
export TARGET_USER="Engineer A" TARGET_DATE=2026-10-01 FORCE_REGENERATE=true
python src/main.py
Use 127.0.0.1 rather than localhost everywhere; the repo docs call IPv6 resolution a recurring papercut. For local models in general, see Running LLMs on a home PC.
Environment configuration
Three kinds of configuration:
- People and teams, as environment variable tuples on each job:
1 2
department_0=<dept> team_0_0=DevOps name_0_0_0=Engineer A github_username_0_0_0=<github-login> jira_email_0_0_0=<jira-email>
- UI behaviour, in the UI chart’s
configblock, rendered to a ConfigMap and read throughGET /api/config:visible_views,visible_teams(for exampleDevOps, SRE, CNS),period_tabs, the POD list and the generator schedules. - Providers and secrets:
ai_providerplus provider keys; GitHub App and Jira credentials from Key Vault.
Each chart has values.yaml (AKS) and values-local.yaml (k3d); any new key in one must appear in the other.
Dependency management
Python dependencies are pinned per component; the frontend uses package.json with a committed lockfile; image versions are in versions.yaml. Dependabot grouped updates keep the per-component pins from turning into a dozen separate PRs.
Feature flags
The ConfigMap is the flag system. visible_views turns user, team, epic or POD pages on and off; visible_teams controls which teams appear; period_tabs controls which periods show. A new view can be deployed hidden and switched on once its summaries have been checked, with no frontend release.
For prompts, the equivalent of a flag is a second template: run old and new prompts side by side on the same days and compare before switching.
Documentation standards
Docs live in the repo (MkDocs) with one page per concern: README, architecture, cronjobs, deployment, storage, UI and tech stack, each linking to source paths. Two additions specific to this project:
- A prompt changelog: what changed in each prompt and why, with a before/after sample.
- A data scope page: exactly which fields are collected from GitHub and Jira, kept in sync with the collector configs. This is the page HR and privacy will ask for.
Development workflow and code review process
- Branch from
main, named after the Jira issue. - Iterate locally with LM Studio; test in k3d.
- Open a PR; CI builds changed components and runs SonarQube and CodeQL.
- Review by a code owner. For prompt changes, the PR includes old and new output for the same sample days.
- Merge; release by bumping
versions.yaml; ArgoCD promotes.
Checklist
- LM Studio set up for local generator runs
- Every component follows the folder shape, including
prompts/for generators - People, teams and PODs only in configuration
- Prompt changes reviewed with before/after samples
- Data scope page kept in sync with collector configs
- Docs updated for charts and the POD generator name
values.yamlandvalues-local.yamlin sync
