V7 Build-Out Playbook (T15–T20) — Team Analytics Enhancements
# V7 Build-Out Playbook (T15–T20) — Team Analytics Enhancements
> **File:** `docs/playbooks/v7-team-analytics-enhancements-playbook.md`> **Base branch:** `dev` (integration branch — `main` stays protected/production; PRs branch> from `dev` and merge back into `dev`)> **Branch naming:** `feature/<ado-task-id>-<slug>-v7-pr<n>`> **Plan date:** 2026-08-28> **Owner:** Fatima Ehsan> **Priority:** Independent of other in-flight playbooks — reads/extends tables already> populated by existing, already-merged connectors (`daily_commits`, `boards_work_items`,> `work_items`, `copilot_user_usage`). No new connectors required for T15/T16/T18; T19/T20 are> explicitly scoped as research spikes, not committed implementation.> **Source:** Direct stakeholder request, 2026-08-28 (six items, verbatim scope list).
---
## How to Use This Document
This playbook covers six requested changes, grouped into **three user stories / three PRs**:
- **PR-15 — "Team Table & Trend Fixes" (T15, T16, T18):** three small, independent bug-fix/ enhancement items bundled into one story since each is a narrow, low-blast-radius change to `query_aggregate()`'s existing output shape (a chart-filtering gap, a missing column backed by data that already exists, and a join bug that silently drops zero-commit contributors). None of the three depends on the others, but they're small enough to review and ship together.- **PR-16 — "Project Details Screen" (T17):** kept as its own story — the largest surface area in this batch (new route, view, API endpoint, and store queries), reviewed independently of the smaller fixes above.- **PR-17 — R&D Spike (T19, T20):** explicitly requested as "explore"/"investigate" — produces a written recommendation only, no shipped UI/store change, per this repo's convention of not inventing/reshaping metrics speculatively (see `.github/copilot-instructions.md`). Now tracked under ADO work item **113030** (superseding the original "no ADO ticket" decision below — see §2 PR Plan note). **Completed 2026-09-02** — see `work-items/113030-work-item-revision-history-rnd.md` for full findings. Outcome: `GET /wit/workitems/{id}/updates` confirmed sufficient for both SP attribution (T20) and the conditional gaming-detection idea explored alongside it; velocity formula (`SUM(story_points)`) is unchanged and unaffected. **Decision:** SP attribution and an `itemsClosed`-alongside-`velocity` display move forward as real, separately-scoped feature work (new work item + branch, not yet opened) — the gaming-detection guardrail itself stays on hold pending observed evidence of a real problem, not built speculatively.
**Read `docs/playbooks/v1-dev-productivity-dashboard-playbook.md` §4 first** — all Hard Rulesthere apply here, most importantly: never fabricate data, aggregate at ingest not query timewhere applicable, zero-data is a valid state not an error, and **no automatic git actions**without explicit per-action approval.
---
## 1. Executive Summary
| # | Item | Gap | Depends on ||---|---|---|---|| T15 | Team Velocity Trend chart ignores contributor filter for its story-points series | `query_aggregate()`'s velocity-trend block reads `boards_work_items` (no assignee column) unconditionally, even when `contributor_emails` narrows every other KPI/table on the page; only the commits series in that same chart is already contributor-scoped | None — `work_items` (has `assignee_email`) already exists || T16 | No AI Credits/Token Consumption column in Team Contribution table | `copilot_user_usage.ai_credits_used` is ingested but never surfaced in `query_aggregate()`'s `teamRows` | None — column already populated by `copilot_connector.py` || T17 | No Project Details screen | Per-item metric breakdown (commits, user stories, etc.) only exists at contributor granularity (`ContributorView.vue` / `/api/contributor-detail`); there is no project-scoped equivalent | None — same query patterns as `ContributorView.vue`, scoped by `project_id` instead of `contributor_email` || T18 | Zero-commit contributors (e.g. PMO, QA) missing from Team Contributors list | `query_aggregate()`'s team-table query starts from `daily_commits` `JOIN contributors` — an inner join, so a contributor with zero rows in `daily_commits` for the period never appears at all | None — `contributors` table already has these rows; just need to start the query from `contributors` || T19 (R&D) | Team Velocity formula revamp — exclude commit count, consider "work items moved" | Current `query_team_velocity()` already excludes commit count (it's SP-based, not commit-based) — the ask is really about moving *beyond* story-point totals to a state-transition-count metric ("work items moved") | **Spike complete (2026-09-02)** — `GET /wit/workitems/{id}/updates` confirmed to carry full state-transition history; velocity formula itself (`SUM(story_points)`) confirmed unchanged/not in question — see `work-items/113030-work-item-revision-history-rnd.md` || T20 (R&D) | Story Points attribution via work item history | Current attribution is "whoever is `assignee_email` on the item's current row" — the ask is to attribute by who did the work across the item's full history, not just final assignee | **Spike complete (2026-09-02)** — same endpoint confirmed sufficient for history-based attribution; moving forward as real, separately-scoped feature work (new work item + branch, not yet opened) — see `work-items/113030-work-item-revision-history-rnd.md` §Decision |
### PR Strategy — 3 user stories / 3 PRs
- **PR-15 — "Team Table & Trend Fixes"** — T15 + T16 + T18 bundled into one story/branch. All three are store-layer-driven, small, and independent of each other; bundling keeps the number of small reviews down without mixing in the larger Project Details work.- **PR-16 — "Project Details Screen"** — T17 alone. New route/view/API endpoint — largest PR in this batch, kept separate so it can be reviewed/rolled back independently of the fixes bundle.- **PR-17 — R&D Spike"** — T19 + T20 combined: a findings doc only (e.g. `docs/playbooks/v7-rd-findings.md` or an addendum to this file) — no product code. Do not build a partial/best-guess "work items moved" or "history-based attribution" feature under this PR; ship the findings, let a follow-up playbook implement whatever is decided. **No ADO work item and no `work-items/` file is created for this spike** (explicit decision, 2026-08-28) — it is tracked here in the playbook only, so anyone reviewing this documentation knows what R&D is required without a separate ticket to maintain, same no-formal-ticket precedent as `docs/playbooks/v6-ui-ux-impeccable-playbook.md`.
### Merge Sequencing
PR-15 and PR-16 have no hard dependency on each other, but PR-15 should land first since it fixesthe team-table join (T18) that PR-16's per-project contributor breakdown will also read from —building PR-16 against the corrected row set avoids inheriting the same zero-commit gap in thenew screen. PR-17 (R&D) can happen at any point — it touches no shared code — but should landlast since it's pure research output, not gating anything.
1. **PR-15** (T15, T16, T18) — team-table join fix, AI credits column, velocity trend filtering.2. **PR-16** (T17) — Project Details screen, built against PR-15's corrected team-table query.3. **PR-17** (T19, T20) — R&D findings write-up.
### Model Guidance
| PR | Task | Recommended Model | Effort | Reason ||---|---|---|---|---|| PR-15 | T15 + T16 + T18 (Team Table & Trend Fixes) | Claude Sonnet | medium | Three small, independent store-layer changes: a join rewrite, a new field threaded through an existing shape, and a per-contributor trend-series branch reusing an existing done-state pattern — no single change is large, but together they touch several call sites in `query_aggregate()` that need individual care || PR-16 | T17 (Project Details screen) | Claude Sonnet | high | New route, new view, new API endpoint, several new/adapted store queries scoped by `project_id` — largest surface area, needs care to reuse `ContributorView.vue`'s existing tab/loading patterns rather than diverging || PR-17 | T19 + T20 (R&D spike) | Claude Opus or equivalent "deep reasoning" model | medium | No code to ship; the value is in correctly surveying WIQL/Boards API capability for revision history and writing a sound recommendation — reasoning quality matters more than throughput here |
---
## 2. PR Plan
| PR | Branch | Items | ADO Work Item | Branches from | Merges to | Est. | Status ||---|---|---|---|---|---|---|---|| PR-15 | `feature/112961-team-analytics-fixes-v7-pr15` | T15, T16, T18 | 112961 | `dev` | `dev` | 1–2 sessions | DONE || PR-16 | `feature/112962-project-details-screen-v7-pr16` | T17 | 112962 | `dev` | `dev` | 2–3 sessions | DONE || PR-17 | `feature/113030-work-item-revision-history-rnd-v7-pr17` | T19, T20 | 113030 | `dev` | `dev` | 1 session (research only) | DONE (2026-09-02) |
ADO work item IDs `112961` (PR-15), `112962` (PR-16), and `113030` (PR-17) are real. `113030` wasopened 2026-09-02 specifically to test/evaluate the `GET /wit/workitems/{id}/updates` revisionhistory API against real ADO project data — it **supersedes** the original 2026-08-28 decision totrack PR-17 with no ADO ticket; that decision is kept below for historical record only. See`work-items/112961-team-analytics-fixes.md`, `work-items/112962-project-details-screen.md`, and`work-items/113030-work-item-revision-history-rnd.md` for the three stories.
**Follow-up (not yet a PR/work item in this playbook):** per `113030`'s Decision section, twoitems move forward as real, separately-scoped feature work off the back of this spike — SPattribution via work item history, and an `itemsClosed`-alongside-`velocity` display in the TeamContribution table. Neither has an ADO work item or branch yet; will be scoped as a new playbook/PR once opened, not added retroactively to this one.
---
## 3. Files Touched
| File / path | PR ||---|---|| `store/db.py` (`query_aggregate()`'s velocity-trend block; new helper to bucket `work_items` story points by week when `contributor_emails` is set) | PR-15 || `store/db.py` (`query_aggregate()`'s `teamRows` construction — add `aiCreditsUsed`) | PR-15 || `store/db.py` (team-table base query: `contributors` becomes the driving table via `LEFT JOIN daily_commits`, not the reverse) | PR-15 || `public/src/components/TeamTable.vue`, `mockData.js` (mock parity for the new column) | PR-15 || `tests/test_store_db.py` | PR-15, PR-16 || `public/src/router/index.js` (new `/project` route) | PR-16 || `public/src/views/ProjectView.vue` (new) | PR-16 || `public/src/components/ProjectHeader.vue` (new, mirrors `ContributorHeader.vue`) | PR-16 || `public/src/api.js` (new `fetchProjectDetail()`) | PR-16 || `api/server.py` (new `GET /api/project-detail?projectId=...`) | PR-16 || `store/db.py` (new `query_project_detail()`) | PR-16 || `public/src/components/DashboardView.vue` or `TeamTable.vue` (link/nav into the new Project Details screen — confirm entry point with design before building) | PR-16 || `docs/playbooks/v7-team-analytics-enhancements-playbook.md` (§9, findings appended after the spike) or a new `docs/playbooks/v7-rd-findings.md` | PR-17 |
---
## 4. Hard Rules
(Inherits Hard Rules from `docs/playbooks/v1-dev-productivity-dashboard-playbook.md` §4 — neverfabricate data, aggregate/normalize at ingest not query time where applicable, zero-data is avalid state not an error, and **Hard Rule 12: no automatic git actions**. The following areadditive, specific to this playbook.)
1. **T15** — the story-points series in the velocity trend chart must use the exact same done-state vocabulary (`_DONE_STATE_VOCAB`) and the exact same `work_items`-based per-contributor path already established by `contributor_velocity` in the team-table block — do not invent a second, slightly-different definition of "closed" for the trend chart.2. **T15** — when no contributor filter is active, the trend chart's story-points series must be byte-identical to today's output (still reads `boards_work_items`, the aggregate-at-ingest table) — this change only takes effect when `contributor_emails` is non-empty.3. **T16** — `ai_credits_used` is Copilot-specific and nullable; render `—` (not `0`) for a contributor with no matching Copilot usage row, exactly like every other "no data" convention in this codebase (Hard Rule 3 in the base playbook). Never present it as directly comparable to a token count from another tool (per `store/schema.sql`'s own comment on this column).4. **T18** — a contributor now appearing with zero commits must show `0` (not blank/absent) for commits/PRs, and their velocity/AI-enabled fields must compute correctly from a truly-zero `daily_commits` row set (not crash on `NULL` from the `LEFT JOIN`).5. **T18** — check every downstream consumer of `team_raw` (PR count lookup, per-contributor velocity lookup, AI-enabled lookup, sort) for an assumption that "in `team_raw`" implies "has at least one commit" — several currently do, per the code read during this playbook's research, and each needs to tolerate a zero-commit row without special-casing it as an error.6. **T17** — `/api/project-detail` is read-only, matches the existing route whitelist/shape conventions in `api/server.py`, and must degrade to an empty/zero-data response (not a 500) for a `project_id` with no data — same convention as `/api/contributor-detail`.7. **T17** — do not duplicate query logic that already exists per-project inside `query_aggregate()` (repo bars, PR throughput, velocity) — factor out/reuse rather than re-deriving from raw tables a second time.8. **T19/T20** — do not ship any UI or store change under the R&D PR. If the spike concludes a new connector/table is needed (e.g. work-item revision history), that becomes a new work item and playbook of its own — this PR's deliverable is the written recommendation only.9. **T19/T20** — any proposed "work items moved" or "history-based attribution" metric must be checked against Azure Boards' actual API capability (WIQL `asof`, `Updates`/revisions endpoints) before being recommended — do not propose a formula the org's ADO API can't actually support without per-item webhook capture going forward.
---
## 5. Out of Scope
- **T19/T20 implementation** — explicitly R&D only this pass; see Hard Rules 8–9 above.- **Repo-level Project Details granularity** — T17 is scoped at the project level (matching `daily_commits`/`boards_work_items`' `project_id` dimension), not a further repo-level drill-down within a project; that would be a natural T17 follow-up, not bundled here.- **A dedicated `/api/team-velocity-trend` endpoint** — T15 stays inside the existing `/api/aggregate` response shape (`velocityTrend` field), not split into its own route.- **Retroactive backfill of AI credits for periods before `copilot_connector.py` started capturing `ai_credits_used`** — T16 surfaces whatever is already in the table; no historical reconstruction.- **Claude Code / other non-Copilot tool credit or token consumption** — T16 is Copilot-only, same scope limitation already locked in the V5 playbook for AI-Assisted %.
---
## 6. Per-Session Contract
Every session:
1. Branch from `dev` per the PR Plan above (one branch per story — T15/T16/T18 share a single branch/PR since they're bundled into one story; T17 and T19/T20 each get their own).2. Scoped changes only — no opportunistic cleanup outside the PR's stated files.3. Add/extend a runnable check for any new non-trivial logic (`tests/test_store_db.py` for store-layer changes; `public/tests/` or `public/dashboard.test.mjs` for UI/shape changes).4. All checks green before any git action: `pytest tests/ -v --tb=short` and, for PR-15/PR-16, `cd public && npm run build && npm run test`.5. **Once all checks are green, stop and confirm with the user before staging, committing, or pushing anything** — do not take any git action without explicit approval (Hard Rule 12).6. On approval: `git add -A`, `commit-msg` skill for the message (no Claude co-author trailer), `git push -u origin <branch>`.7. On merge: PR branches from `dev` and merges back into `dev` — `main` is untouched by this playbook's work.8. No PR is opened as part of this workflow — push is the last step, per this repo's standing convention (see V5 playbook §6).
---
## PR 15 — Team Table & Trend Fixes (T15, T16, T18)
**ADO Work Item:** 112961**Branch:** `feature/112961-team-analytics-fixes-v7-pr15`**Branches from:** `dev`**Merges to:** `dev`**Model:** Claude Sonnet**Effort:** medium
### Problem
Three independent, small gaps in `query_aggregate()`'s existing output:
1. **T15 — Velocity trend contributor filtering.** The velocity-trend block computes two series per week: story points (from `boards_work_items`, no assignee dimension) and commits (from `daily_commits`, already filtered by `contrib_filter_dc` when a contributor is selected). When a contributor filter is active, the commits series correctly narrows to that person, but the story-points series silently stays the full team total — the chart ends up showing one filtered line and one unfiltered line with no visual distinction.2. **T16 — AI Credits column.** `copilot_user_usage.ai_credits_used` is ingested by `copilot_connector.py` but never read by `query_aggregate()`'s `teamRows` construction, so the Team Contribution table has no way to show per-user AI credit consumption even though the data already exists.3. **T18 — Zero-commit contributors missing.** The team-table query is `daily_commits JOIN contributors` (inner join) — a contributor with zero rows in `daily_commits` for the period (e.g. PMO, QA roles who don't commit code) never appears in `team_raw`, and therefore never appears in the Team Contributors list, regardless of how long they've been active in `contributors`.
### Required change
**T15** — In `query_aggregate()`'s velocity-trend block:1. When `contributor_emails` is non-empty, switch the story-points weekly bucketing from `boards_work_items` to `work_items`, filtered by `assignee_email IN (...)` and the same `_DONE_STATE_VOCAB` check already used by `query_team_velocity()`'s contributor-scoped path — reuse the existing module-level constant, don't duplicate it.2. Keep the existing `boards_work_items` path unchanged for the no-filter case (Hard Rule 2).3. Bucket by `changed_date` into the same weekly buckets (`_bucket_index`, `window_start_date`/ `window_end_date`) already computed above this block.4. Leave the commits series untouched (already correct).
**T16** — In `query_aggregate()`'s `teamRows` construction:1. Extend the `_copilot_loc_by_contributor_email()` helper (or add a sibling lookup) to also carry `ai_credits_used` keyed by the same resolved contributor email, then add an `aiCreditsUsed` field (nullable — `None` when no Copilot match, never `0`) to each row.2. In `public/src/components/TeamTable.vue`, add a new column rendering `aiCreditsUsed` as `—` when `null`, otherwise the raw number.3. Update `mockData.js`'s team-row generation so the mock path emits the same field shape.
**T18** — In `query_aggregate()`'s team-table base query:1. Rewrite it to drive from `contributors` with a `LEFT JOIN daily_commits` (filters in the `ON` clause, not `WHERE`, so the left join isn't silently turned back into an inner join) — every contributor row exists regardless of commit activity; `SUM(dc.commit_count)` becomes `0` (via `COALESCE`) rather than the row being absent.2. Audit every downstream use of `team_raw`/`team_rows_list` for an "existence implies nonzero commits" assumption (PR count lookup, `contributor_velocity` lookup, AI-enabled lookup, sort) and confirm each tolerates a genuinely-zero-commit row.3. Confirm whether `contributors` has any direct project affiliation column — if not, flag whether a zero-commit contributor can be correctly scoped under a project filter, or should only appear in the "All Projects" view, rather than guessing.
### Prompt — paste verbatim into Claude Code
```textYou are implementing PR-15 of the Team Analytics Enhancements playbook(docs/playbooks/v7-team-analytics-enhancements-playbook.md) on branchfeature/112961-team-analytics-fixes-v7-pr15, bundling three items: T15, T16, T18.
Read store/db.py's query_aggregate() end-to-end first (the "Velocity trend" comment block, the"Team table" comment block, and query_team_velocity()'s contributor_emails branch), plusstore/schema.sql's contributors table definition, before changing anything.
T15 (velocity trend contributor filtering): when contributor_emails is non-empty, thestory-points weekly series in the velocity-trend block must be computed from the work_itemstable (assignee_email IN contributor_emails, state in _DONE_STATE_VOCAB, changed_date in thecurrent window), bucketed into the same weekly buckets the commits series already uses. Whencontributor_emails is empty, behavior must be byte-identical to today (still readsboards_work_items). Reuse _DONE_STATE_VOCAB — do not redefine "closed" a second way.
T16 (AI credits column): add an aiCreditsUsed field to each team-table row — the matchedcontributor's copilot_user_usage.ai_credits_used value (via the same email-matching heuristicalready used for aiEnabled), or null when there's no Copilot match — never 0 for "no data" (HardRule 3, base playbook). Surface as a new column in public/src/components/TeamTable.vue rendering"—" for null. Update mockData.js so the mock aggregate() path produces the same field shape.
T18 (zero-commit contributors): confirm whether contributors has a direct project_id/projectaffiliation column in store/schema.sql — if not, stop and report this rather than guessing at ajoin condition for project-scoped filtering. Rewrite the team-table base query so it's driven bycontributors with a LEFT JOIN daily_commits (filters in the ON clause, not WHERE), so contributorswith zero matching daily_commits rows still appear with commits=0. Check every downstreamconsumer in query_aggregate() — pr_by_contrib lookup, contributor_velocity lookup viaquery_team_velocity(), copilot_loc_by_email/aiEnabled lookup, and the final sort — for anyassumption that a team_raw row implies nonzero commit activity, and fix any that don't alreadytolerate a zero-commit row.
Add/extend tests in tests/test_store_db.py covering all three: (a) no contributor filter →unchanged boards_work_items-based trend output, (b) contributor filter set → trend story-pointsseries reflects only that contributor's closed work items, (c) a contributor with zero closedwork items → all-zero trend series, not a crash, (d) aiCreditsUsed populated for a matchedcontributor and null (not 0) for an unmatched one, (e) a contributor with zero daily_commits rowsstill appears in teamRows with commits=0 and correct PR/velocity/aiEnabled values, withoutcrashing the sort. Also update public/dashboard.test.mjs or public/tests/ for the new column ifthat test setup covers TeamTable.vue's shape.
Do not change the KPI cards' "Active Contributors" count logic (it intentionally counts onlycommitters) without flagging the question first. Do not run any git command — implementation andtests only, then stop and report results.PR 16 — Project Details Screen (T17)
ADO Work Item: 112962
Branch: feature/112962-project-details-screen-v7-pr16
Branches from: dev
Merges to: dev
Model: Claude Sonnet
Effort: high
Problem
Detailed per-item metrics (commit history by repo/date, user stories, Copilot usage) are only
viewable at the contributor level (ContributorView.vue, /api/contributor-detail). There is no
equivalent project-scoped view, so a manager wanting “everything for Project X” has to manually
cross-reference the main dashboard’s project filter against each contributor individually.
Required change
- In
store/db.py, addquery_project_detail(project_id, db_path=...)returning a shape parallel to whateverquery_contributor_detail()/query_aggregate()already expose per project (commit history by repo, PR throughput, velocity, top contributors on that project) — reuse the existing per-project filtering (proj_filter) already used throughoutquery_aggregate()rather than re-deriving new SQL for data that’s already queried elsewhere. - In
api/server.py, addGET /api/project-detail?projectId=..., following the same read-only/degrade-to-empty convention as/api/contributor-detail. - In
public/src/api.js, addfetchProjectDetail(apiBase, projectId)mirroringfetchContributorDetail(). - In
public/src/router/index.js, add a/projectroute; createpublic/src/views/ProjectView.vuemodeled onContributorView.vue’s loading/tab structure (reuseDetailToolTabs.vueif the same per-tool tab pattern — Copilot / ADO / Stories / Claude — applies at project scope). - Add a
ProjectHeader.vuecomponent (mirrorsContributorHeader.vue) and wire a navigation entry point (likely a project name link somewhere inDashboardView.vue— confirm the intended entry point with the user/design before finalizing, since none currently exists). - Confirm with the user whether “user stories” at project scope means all of a project’s
boards_work_items/work_itemsrows (not just one contributor’s) before building the Stories tab — this is a materially different query thanquery_user_stories_detail()’s existing per-contributor version. - This screen should read against PR-15’s corrected team-table query (T18) — a project’s contributor breakdown should include zero-commit contributors too, once PR-15 is merged.
Prompt — paste verbatim into Claude Code
You are implementing PR-16 of the Team Analytics Enhancements playbook(docs/playbooks/v7-team-analytics-enhancements-playbook.md) on branchfeature/112962-project-details-screen-v7-pr16, task T17. This branch should be created from anup-to-date dev that already includes PR-15 (T15/T16/T18) — confirm the team-table join fix ispresent before building the per-project contributor breakdown, so this new screen doesn'tinherit the zero-commit-contributor gap PR-15 fixed.
Read public/src/views/ContributorView.vue, public/src/components/ContributorHeader.vue,public/src/api.js's fetchContributorDetail()/fetchCopilotDetail()/fetchUserStoriesDetail(), andapi/server.py's /api/contributor-detail handler before building anything — the new ProjectDetails screen should mirror these patterns at project scope rather than divergingstylistically or structurally.
Build:1. store/db.py: query_project_detail(project_id, db_path=...) — commit history by repo/date, PR throughput, team velocity (via existing query_team_velocity()), and per-contributor breakdown, all scoped to the given project_id. Reuse existing per-project SQL patterns from query_aggregate() rather than re-deriving.2. api/server.py: GET /api/project-detail?projectId=... — read-only, degrades to an empty (not error) response for an unknown/no-data project_id, matching /api/contributor-detail's convention.3. public/src/api.js: fetchProjectDetail(apiBase, projectId).4. public/src/router/index.js: new /project route.5. public/src/views/ProjectView.vue + public/src/components/ProjectHeader.vue: new, modeled on ContributorView.vue/ContributorHeader.vue's loading-state and tab patterns.6. A navigation entry point into this new screen (e.g. a clickable project name) — propose where and confirm before wiring it in, since no such link exists today.
Add tests: tests/test_store_db.py for query_project_detail() (known project with data, unknownproject → empty/zero-data, not an error), and a public/ test file covering the new view's basicrender/fetch flow if the existing test setup supports component tests (check public/tests/ andpublic/dashboard.test.mjs for the established pattern first).
Do not modify ContributorView.vue's existing behavior. Do not run any git command —implementation and tests only, then stop and report results.PR 17 — R&D Spike: Velocity Formula & Story Point Attribution (T19, T20)
ADO Work Item: None — deliberately not tracked as an ADO user story, and deliberately has no
corresponding work-items/ file (explicit decision, 2026-08-28). This playbook section is the
sole record of what R&D is required — kept here so anyone reviewing project documentation can
see the scope without a ticket to separately maintain.
Branch: feature/rd-velocity-attribution-v7-pr17
Branches from: dev
Merges to: dev
Model: Claude Opus or equivalent deep-reasoning model
Effort: medium (research, not implementation)
Problem
Two related requests, both exploratory:
- T19: Revise Team Velocity to exclude commit count (already true today — see Executive Summary) and instead weight/consider “work items moved” — i.e., a count of state transitions, not just a snapshot total of story points on currently-closed items.
- T20: Attribute story points based on a work item’s full history (who touched it across its
lifecycle), not just its current
assignee_email.
Both require data neither work_items nor boards_work_items currently store: state-transition
history. Both tables hold one row per work item reflecting its current state/assignee only —
there is no revision log.
Required output (not code)
- Confirm via Azure DevOps REST API docs (
GET .../wit/workitems/{id}/updatesor WIQLasof) whether/how state-transition and historical-assignee data can be fetched per work item, and at what cost (API calls scale with item count — check rate-limit/pagination implications for the org’s actual item volume). - Propose a concrete “work items moved” definition (e.g. count of
System.Statefield-change events per period, per project) and a concrete history-based story-point attribution formula (e.g. split points across every assignee an item had while in an active state, weighted by time-in-state) — both must be checkable against real API responses, not assumed. - Identify schema changes needed (e.g. a new
work_item_history/work_item_transitionstable) and estimate connector-side cost (likelyado_boards_connector.pyextension, one additional API call per item vs. the current batch-fetch approach). - Write findings + recommendation into a new section appended to this playbook (§9) or a
sibling
docs/playbooks/v7-rd-findings.md— explicitly flag any formula that the API can’t actually support today, per Hard Rule 9. - Ship no product code. If findings support moving forward, that becomes a new work item and its own playbook (e.g. a “v8” or “v7-implementation” follow-up).
Prompt — paste verbatim into Claude Code
You are running the R&D spike for T19/T20 of the Team Analytics Enhancements playbook(docs/playbooks/v7-team-analytics-enhancements-playbook.md) on branchfeature/rd-velocity-attribution-v7-pr17. This is a research task — do not write or modifyany product code, schema, or connector in this session.
Read connectors/ado_boards_connector.py's discovery/WIQL logic and store/schema.sql'sboards_work_items/work_items tables first, to confirm neither stores state-transition historytoday (only current state/assignee per item).
Investigate Azure DevOps REST API's work item Updates/revisions endpoint (and WIQL `asof`support) for retrieving state-transition history and historical assignee-per-state data.Determine actual feasibility, API call cost at this org's scale, and pagination constraints.
Produce a written recommendation (append to docs/playbooks/v7-team-analytics-enhancements-playbook.mdas a new §9, or a new docs/playbooks/v7-rd-findings.md file — your choice, note which) covering:(a) a concrete "work items moved" metric definition for T19, checked against real API capability;(b) a concrete history-weighted story-point attribution formula for T20, same API-capabilitycheck; (c) required schema additions and estimated connector-side cost for either; (d) anexplicit call-out of any part of either ask that the API cannot support without newwebhook/event capture going forward (per Hard Rule 9 in the playbook).
Do not implement anything. Do not run any git command — write the findings document, then stopand report a summary.7. Verification Samples
python3 -m pytest tests/ -v --tb=short— full suite, after PR-15 and PR-16.cd public && npm run build— clean production build, after PR-15/PR-16 (UI changes).npm run test(vitest) +node public/dashboard.test.mjs— after PR-15/PR-16.- PR-15 (T15): manually diff
velocityTrendoutput with/without acontributor_emailsfilter against a known project’swork_itemsdata to confirm the story-points series actually changes. - PR-15 (T18): confirm a known zero-commit contributor (e.g. a PMO/QA test fixture row in
contributors) now appears inteamRowswithcommits: 0for at least one date range. - PR-17: no runnable verification — deliverable is the findings document itself; review is the “test.”
8. Rollback
Each PR is a single isolated branch merged independently to dev:
- PR-15 — revert restores: unfiltered
boards_work_itemsvelocity trend series (T15), noaiCreditsUsedfield (T16), and the inner-join behavior where zero-commit contributors disappear again (T18). No schema change to undo in any case — all three are additive query/field changes. - PR-16 — revert removes the new route/view/endpoint entirely; no other screen depends on it.
- PR-17 — no code to roll back; reverting removes the findings document only.