Skip to content

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 Rules
there apply here, most importantly: never fabricate data, aggregate at ingest not query time
where 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 fixes
the 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 the
new screen. PR-17 (R&D) can happen at any point — it touches no shared code — but should land
last 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` was
opened 2026-09-02 specifically to test/evaluate the `GET /wit/workitems/{id}/updates` revision
history API against real ADO project data — it **supersedes** the original 2026-08-28 decision to
track 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, two
items move forward as real, separately-scoped feature work off the back of this spike — SP
attribution via work item history, and an `itemsClosed`-alongside-`velocity` display in the Team
Contribution 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 — never
fabricate data, aggregate/normalize at ingest not query time where applicable, zero-data is a
valid state not an error, and **Hard Rule 12: no automatic git actions**. The following are
additive, 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
```text
You are implementing PR-15 of the Team Analytics Enhancements playbook
(docs/playbooks/v7-team-analytics-enhancements-playbook.md) on branch
feature/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), plus
store/schema.sql's contributors table definition, before changing anything.
T15 (velocity trend contributor filtering): when contributor_emails is non-empty, the
story-points weekly series in the velocity-trend block must be computed from the work_items
table (assignee_email IN contributor_emails, state in _DONE_STATE_VOCAB, changed_date in the
current window), bucketed into the same weekly buckets the commits series already uses. When
contributor_emails is empty, behavior must be byte-identical to today (still reads
boards_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 matched
contributor's copilot_user_usage.ai_credits_used value (via the same email-matching heuristic
already used for aiEnabled), or null when there's no Copilot match — never 0 for "no data" (Hard
Rule 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/project
affiliation column in store/schema.sql — if not, stop and report this rather than guessing at a
join condition for project-scoped filtering. Rewrite the team-table base query so it's driven by
contributors with a LEFT JOIN daily_commits (filters in the ON clause, not WHERE), so contributors
with zero matching daily_commits rows still appear with commits=0. Check every downstream
consumer in query_aggregate() — pr_by_contrib lookup, contributor_velocity lookup via
query_team_velocity(), copilot_loc_by_email/aiEnabled lookup, and the final sort — for any
assumption that a team_raw row implies nonzero commit activity, and fix any that don't already
tolerate 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-points
series reflects only that contributor's closed work items, (c) a contributor with zero closed
work items → all-zero trend series, not a crash, (d) aiCreditsUsed populated for a matched
contributor and null (not 0) for an unmatched one, (e) a contributor with zero daily_commits rows
still appears in teamRows with commits=0 and correct PR/velocity/aiEnabled values, without
crashing the sort. Also update public/dashboard.test.mjs or public/tests/ for the new column if
that test setup covers TeamTable.vue's shape.
Do not change the KPI cards' "Active Contributors" count logic (it intentionally counts only
committers) without flagging the question first. Do not run any git command — implementation and
tests 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

  1. In store/db.py, add query_project_detail(project_id, db_path=...) returning a shape parallel to whatever query_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 throughout query_aggregate() rather than re-deriving new SQL for data that’s already queried elsewhere.
  2. In api/server.py, add GET /api/project-detail?projectId=..., following the same read-only/degrade-to-empty convention as /api/contributor-detail.
  3. In public/src/api.js, add fetchProjectDetail(apiBase, projectId) mirroring fetchContributorDetail().
  4. In public/src/router/index.js, add a /project route; create public/src/views/ProjectView.vue modeled on ContributorView.vue’s loading/tab structure (reuse DetailToolTabs.vue if the same per-tool tab pattern — Copilot / ADO / Stories / Claude — applies at project scope).
  5. Add a ProjectHeader.vue component (mirrors ContributorHeader.vue) and wire a navigation entry point (likely a project name link somewhere in DashboardView.vue — confirm the intended entry point with the user/design before finalizing, since none currently exists).
  6. Confirm with the user whether “user stories” at project scope means all of a project’s boards_work_items/work_items rows (not just one contributor’s) before building the Stories tab — this is a materially different query than query_user_stories_detail()’s existing per-contributor version.
  7. 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 branch
feature/112962-project-details-screen-v7-pr16, task T17. This branch should be created from an
up-to-date dev that already includes PR-15 (T15/T16/T18) — confirm the team-table join fix is
present before building the per-project contributor breakdown, so this new screen doesn't
inherit 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(), and
api/server.py's /api/contributor-detail handler before building anything — the new Project
Details screen should mirror these patterns at project scope rather than diverging
stylistically 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, unknown
project → empty/zero-data, not an error), and a public/ test file covering the new view's basic
render/fetch flow if the existing test setup supports component tests (check public/tests/ and
public/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)

  1. Confirm via Azure DevOps REST API docs (GET .../wit/workitems/{id}/updates or WIQL asof) 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).
  2. Propose a concrete “work items moved” definition (e.g. count of System.State field-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.
  3. Identify schema changes needed (e.g. a new work_item_history/work_item_transitions table) and estimate connector-side cost (likely ado_boards_connector.py extension, one additional API call per item vs. the current batch-fetch approach).
  4. 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.
  5. 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 branch
feature/rd-velocity-attribution-v7-pr17. This is a research task — do not write or modify
any product code, schema, or connector in this session.
Read connectors/ado_boards_connector.py's discovery/WIQL logic and store/schema.sql's
boards_work_items/work_items tables first, to confirm neither stores state-transition history
today (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.md
as 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-capability
check; (c) required schema additions and estimated connector-side cost for either; (d) an
explicit call-out of any part of either ask that the API cannot support without new
webhook/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 stop
and 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 velocityTrend output with/without a contributor_emails filter against a known project’s work_items data 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 in teamRows with commits: 0 for 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_items velocity trend series (T15), no aiCreditsUsed field (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.