toga-ai 1.0.434 → 1.0.436
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-24
|
|
10
10
|
owners: ["jcardinal", "kyalamarthi"]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/Worker/Team/Sprint.php
|
|
@@ -178,7 +178,28 @@ All sprint state lives in the Team database (`_underscore::DB_TEAM`). Core table
|
|
|
178
178
|
(`reworkEvents`, `hygieneMisses`, `unjustifiedStatusChanges`,
|
|
179
179
|
`unjustifiedWorkEffortChanges`, `timeInProgressThisSprint`). Task statuses use the
|
|
180
180
|
`_Model_Team_Task::STATUS_*` constants (e.g. `STATUS_IN__DONE`).
|
|
181
|
-
- **`Tasks_Developers`** — task ↔ developer assignment join table.
|
|
181
|
+
- **`Tasks_Developers`** — task ↔ developer assignment join table. This is a **many-to-many**
|
|
182
|
+
relationship: a task can have several assigned developers. Any per-developer points rollup
|
|
183
|
+
(e.g. "points by dev") credits **each** assigned developer the task's *full* points — the
|
|
184
|
+
points are not split across assignees.
|
|
185
|
+
|
|
186
|
+
**Column vocabularies & datetime columns (dashboard-relevant).** For current-state reporting the
|
|
187
|
+
exact values matter:
|
|
188
|
+
|
|
189
|
+
- **`workTypeNow` / `workTypeAtLock`** — enum `COMMITTED / CONDITIONAL / STRETCH / UNPLANNED`.
|
|
190
|
+
MySQL string comparison is **case-insensitive** by default, so `'Committed'`/`'STRETCH'`/etc.
|
|
191
|
+
all match regardless of case.
|
|
192
|
+
- **`statusNow` / `statusAtLock` / `statusAtEnd`** — free-form varchar, values stored
|
|
193
|
+
**lower-case**: `complete`, `to do`, `in progress`, `stage review`, `hotfix review`,
|
|
194
|
+
`back-end review`, `ui review`, `on hold`, `roadblocked`, `awaiting client`, `rework`,
|
|
195
|
+
`pseudocode`, `ongoing`, `archived` (list is open-ended — treat unknown values as "review", see
|
|
196
|
+
the status rollup below).
|
|
197
|
+
- **`dtDone` vs `dtCompleted` are two different datetime columns** and are **not**
|
|
198
|
+
interchangeable. `dtDone IS NOT NULL` is the Power BI "done" test used by the KPI tiles and
|
|
199
|
+
burndown; `dtCompleted IS NOT NULL` is the "Complete" test used by the per-dev segment
|
|
200
|
+
breakdown. Do not substitute one for the other.
|
|
201
|
+
- **`sprintPointsNow` / `sprintPointsAtLock`** — the points measured by every dashboard metric
|
|
202
|
+
below use `sprintPointsNow` (current state), not the at-lock value.
|
|
182
203
|
|
|
183
204
|
`CaptureSprintEnd()` and `CaptureSprintDaily()` are the private sync routines that walk the
|
|
184
205
|
ClickUp sprint list (paginated, `include_timl=true`), upsert `Sprints`/`Developers`/`Tasks`/
|
|
@@ -249,8 +270,102 @@ Neither is "wrong" — they answer different questions (scored performance vs. l
|
|
|
249
270
|
If a tile or report must **agree with TOGa IQ's canonical scoring**, reconcile it to definition
|
|
250
271
|
(1); otherwise expect current-state tiles to diverge from sprint scores.
|
|
251
272
|
|
|
273
|
+
## Power BI dashboard metric definitions (DAX → SQL)
|
|
274
|
+
|
|
275
|
+
These are the **current-state / Power BI-parity** definitions behind the TOGa IQ sprint
|
|
276
|
+
dashboard (the same set of tiles and charts the Power BI report shows). They translate the
|
|
277
|
+
Power BI DAX into SQL against the `Team` schema so any consumer — a worker2 report, an api2
|
|
278
|
+
Record Script tile, or a local prototype — produces numbers that match Power BI. All of them
|
|
279
|
+
use current-state columns (`workTypeNow`, `statusNow`, `sprintPointsNow`) except where noted,
|
|
280
|
+
and follow the **Power BI / current-state definition** above (`dtDone IS NOT NULL`), not the
|
|
281
|
+
canonical scoring definition.
|
|
282
|
+
|
|
283
|
+
### KPI tiles — points by work type
|
|
284
|
+
Per tile: `points = SUM(sprintPointsNow)`, `done = dtDone IS NOT NULL`, filtered by a work-type
|
|
285
|
+
column + value. **Non-uniform on purpose — the column differs by tile:**
|
|
286
|
+
|
|
287
|
+
- **Committed / Stretch / Unplanned** filter on **`workTypeNow`** (`'Committed'` / `'STRETCH'` /
|
|
288
|
+
`'UNPLANNED'`).
|
|
289
|
+
- **Conditional** filters on **`workTypeAtLock` = `'Conditional'`** — a *different column*
|
|
290
|
+
(the at-lock baseline), not `workTypeNow`. Miss this and the Conditional tile is wrong.
|
|
291
|
+
|
|
292
|
+
MySQL string comparison is case-insensitive, so casing of the literal does not matter.
|
|
293
|
+
|
|
294
|
+
### Status rollup (status pie) — "Status now Category Group" DAX SWITCH
|
|
295
|
+
A `SWITCH` over `statusNow` (case-insensitive) into five buckets:
|
|
296
|
+
|
|
297
|
+
| Bucket | `statusNow` values |
|
|
298
|
+
|--------|--------------------|
|
|
299
|
+
| **Complete** | `complete`, `archived` |
|
|
300
|
+
| **In Progress** | `in progress`, `rework` |
|
|
301
|
+
| **Stalled** | `on hold`, `roadblocked`, `awaiting client` |
|
|
302
|
+
| **To Do** | `to do` |
|
|
303
|
+
| **Review** | **everything else (SWITCH default)** — all the review-type statuses fall here |
|
|
304
|
+
|
|
305
|
+
The default → **Review** case is load-bearing: any new/unknown status is a Review, not an error.
|
|
306
|
+
Shown by **task count** (dashboard.html) or by **`SUM(sprintPointsNow)`** (dashboard1.html donut
|
|
307
|
+
variant) — same rollup, different measure.
|
|
308
|
+
|
|
309
|
+
### Work Type pie
|
|
310
|
+
`SUM(sprintPointsNow) GROUP BY workTypeNow` — all four work-type values live in `workTypeNow` for
|
|
311
|
+
this chart (unlike the KPI tiles, Conditional is *not* read from `workTypeAtLock` here).
|
|
312
|
+
|
|
313
|
+
### Sprint Points By Dev — "New Status" DAX SWITCH
|
|
314
|
+
Points = `sprintPointsNow`; each task is assigned **one** segment via a SWITCH evaluated in order:
|
|
315
|
+
|
|
316
|
+
1. `dtCompleted IS NOT NULL` → **Complete** (note: `dtCompleted`, not `dtDone`).
|
|
317
|
+
2. else `statusNow NOT IN ('to do','in progress','rework','on hold','roadblocked','awaiting client')`
|
|
318
|
+
→ **In Review**.
|
|
319
|
+
3. else → the task's **`workTypeNow`** value (Committed / Conditional / Unplanned).
|
|
320
|
+
|
|
321
|
+
Joined `Tasks → Tasks_Developers → Developers` and grouped by developer. Because the join is
|
|
322
|
+
many-to-many, each assigned developer is credited the task's **full** `sprintPointsNow`.
|
|
323
|
+
|
|
324
|
+
### Sprint Burndown (over working days)
|
|
325
|
+
Plotted over **working days only** (Mon–Fri, ~10 per two-week sprint; call the count `N`,
|
|
326
|
+
`dayNumber` 1-based):
|
|
327
|
+
|
|
328
|
+
- **Target line:** `CCU_total × (1 − dayNumber / N)`, clamped to `≥ 0`.
|
|
329
|
+
- **Committed actual:** `CCU_total − (CCU points with dtDone on or before that day)`.
|
|
330
|
+
- **All actual:** `CCUS_total − (CCUS points done-to-date)`.
|
|
331
|
+
- Actual series **stop at today** (no future points).
|
|
332
|
+
|
|
333
|
+
Two work-type universes: **CCU** = Committed + Conditional + Unplanned; **CCUS** = CCU + Stretch.
|
|
334
|
+
|
|
335
|
+
### Time Progression
|
|
336
|
+
Business-hours elapsed vs. total. Working days are Mon–Fri only, each **8h (09:00–17:00)**, so a
|
|
337
|
+
10-working-day sprint = **80h**. `% = elapsed business hours ÷ 80`, using the local clock.
|
|
338
|
+
|
|
339
|
+
### Current-sprint auto-resolution
|
|
340
|
+
Dashboards must **never hardcode a sprint number.** Resolve the current sprint as:
|
|
341
|
+
|
|
342
|
+
1. the sprint whose date range contains today: `CURDATE() BETWEEN dateStart AND dateEnd`;
|
|
343
|
+
2. else the most recently started: `ORDER BY (dateStart <= CURDATE()) DESC, dateStart DESC LIMIT 1`.
|
|
344
|
+
|
|
345
|
+
In the local prototype this is exposed as `GET /v2/sprints/current`, and a `/v2/sprints`
|
|
346
|
+
middleware defaults every tile/chart endpoint to it when no `?sprint=` is supplied.
|
|
347
|
+
|
|
348
|
+
> **Prototype provenance & credentials.** These definitions were reverse-engineered and verified
|
|
349
|
+
> against the live `Team` schema (which resolves to the **core cluster reader** — see
|
|
350
|
+
> [per-client database connections](../../_underscore/features/per-client-database-connections.md))
|
|
351
|
+
> by a local Node/React + Express (mysql2) prototype that stands in for the api2 `/v2` engine and
|
|
352
|
+
> returns the standard api2 envelope. The prototype's DB credentials live in an **uncommitted
|
|
353
|
+
> `.env`** (never in the repo or this doc). When productionized, the tiles become api2 Record
|
|
354
|
+
> Scripts registered via **dbchanges2** — see
|
|
355
|
+
> [Record Scripts](../../api2/features/record-scripts.md).
|
|
356
|
+
|
|
252
357
|
## Change history
|
|
253
358
|
|
|
359
|
+
- 2026-07-24 — Documented the full **Power BI dashboard metric definitions (DAX → SQL)** for the
|
|
360
|
+
TOGa IQ sprint dashboard: KPI tiles by work type (with the non-uniformity that **Conditional
|
|
361
|
+
filters on `workTypeAtLock`** while Committed/Stretch/Unplanned filter on `workTypeNow`), the
|
|
362
|
+
status-rollup SWITCH (default → Review), the work-type pie, the per-dev "New Status" segment
|
|
363
|
+
SWITCH (`dtCompleted`-based, many-to-many full-points-per-dev), the burndown target/actual over
|
|
364
|
+
working days (CCU vs CCUS), time-progression (Mon–Fri 8h days, 80h/sprint), and current-sprint
|
|
365
|
+
auto-resolution. Enriched the data model with column vocabularies (workType/status enums), the
|
|
366
|
+
**`dtDone` vs `dtCompleted`** distinction, and the Tasks↔Developers many-to-many points rule.
|
|
367
|
+
Reverse-engineered/verified via a local Node/React + Express prototype (creds in uncommitted
|
|
368
|
+
`.env`). (kyalamarthi)
|
|
254
369
|
- 2026-07-23 — Recorded that two intentionally-different "committed/done" definitions coexist:
|
|
255
370
|
the canonical sprint-scoring definition (`statusNow IN (STATUS_IN__DONE)` + `workTypeAtLock`,
|
|
256
371
|
incl. the reusable `_pointsByWorkType`/`_tasksByWorkType` helpers on `_Model_Team_Sprint` in
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: session
|
|
3
|
+
slug: sprint-dashboard
|
|
4
|
+
title: TOGa IQ Sprint Dashboard prototype (React + Express stand-in)
|
|
5
|
+
author: kyalamarthi
|
|
6
|
+
repos: [test]
|
|
7
|
+
framework: "standalone"
|
|
8
|
+
client: shared
|
|
9
|
+
status: active
|
|
10
|
+
created: 2026-07-24
|
|
11
|
+
updated: 2026-07-24
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Session: sprint-dashboard
|
|
15
|
+
**Date:** 2026-07-24
|
|
16
|
+
**Project/Repo:** test (standalone Node/React prototype in `@krishna/React`; reads 2.0 TOGa IQ `Team` data)
|
|
17
|
+
**Task:** Build a Power BI-parity TOGa IQ sprint dashboard in `test/@krishna/React` — React (CDN, no build) frontend + Express/mysql2 backend that stands in for the api2 `/v2` engine — with KPI tiles, Status/Work Type pies, Sprint Burndown, Sprint Points By Dev, and a Time-Progression header, all auto-resolving the current sprint from the live `Team` schema.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## What WORKED
|
|
22
|
+
- **`backend/server.js`** — Express + a single mysql2 pool, `MOCK_MODE` switch, api2 `/v2` envelope (`{transactionId,timestamp,isSuccess,status,error,messages,meta,data}`), and `express.static` serving the frontend with `Cache-Control: no-store`. Live-verified against the `Team` schema (core-cluster reader): server boots "LIVE (querying MySQL)".
|
|
23
|
+
- **Parameterized tile endpoint** `GET /v2/sprints/tile?category=committed|conditional|stretch|unplanned` driven by a `CATEGORIES` map (per-tile `column`+`value`+mock) and a `COLUMN_ALLOWLIST`. Injection-safe: `value`/`sprint` are bound `?` params, the work-type **column** is only ever taken from the server-side allowlist. Verified all four categories, a clean **400** on `category=bogus`, and the back-compat `/v2/sprints/committed-tile` alias.
|
|
24
|
+
- **Status rollup** (`/status-breakdown`) mirrors the Power BI "Status now Category Group" SWITCH (default→Review); returns **both `count` and `points`** per bucket. **Work Type pie** (`/worktype-breakdown`) = `SUM(sprintPointsNow) GROUP BY workTypeNow`. **Points-by-Dev** (`/points-by-dev`) joins `Tasks→Tasks_Developers→Developers` (many-to-many, full points per dev) with the "New Status" SWITCH (`dtCompleted`→Complete; else statusNow-not-in-active→In Review; else workTypeNow). **Burndown** (`/burndown`) = Target `CCU×(1−day/N)` + actual `total−done-to-date` (CCU vs CCUS) over Mon–Fri working days, actuals stop at today. All live-verified for sprint 83.
|
|
25
|
+
- **Current-sprint auto-resolution** — a `/v2/sprints` middleware defaults every endpoint to the sprint whose range contains today (`CURDATE() BETWEEN dateStart AND dateEnd`, else most recently started), and `GET /v2/sprints/current` feeds the header. Verified → auto-resolves to **sprint 83** (Jul 22–Aug 4); no hardcoded sprint anywhere.
|
|
26
|
+
- **`frontend/dashboard.html`** — 2×2 grid (tiles top; Status/Work Type left column `1fr`; Burndown/Points-by-Dev right column `2fr`), fits one viewport, no scroll. Reusable `Donut`/`PieChart`/`Legend`/`StackedBarChart`/`BurndownChart` + shared `fetchEnvelope`/`useApi`. Time-Progression header bar (business hours) + Full-screen button. Verified via browser screenshots and `evaluate_script` (10px row gaps, `docScrollH == viewportH`).
|
|
27
|
+
- **`frontend/dashboard1.html`** — copy of dashboard.html, since customized into a **donut** variant (center totals + value legend). `USE_MOCK=false` (live). Its **Status donut shows POINTS** (center "208 points", legend point values) via `b.points` + `unit="points"`.
|
|
28
|
+
- **Knowledge captured** — `/capture` UPDATED `knowledge/2.0/apps/worker2/features/team-sprint-management.md` with the full DAX→SQL metric definitions + Team-schema precision; **pushed to `_main`**.
|
|
29
|
+
|
|
30
|
+
## What did NOT work — DO NOT RETRY THESE
|
|
31
|
+
- **Layout attempt #1:** `.page { min-height:100vh }` + `.main-grid { grid-template-rows: minmax(240px,1fr) minmax(240px,1fr) }`. On a 768px viewport the grid grew to **750px** and the bottom row was clipped ~170px (`document.scrollHeight = 939 > 768`). Reason: `min-height` + the `minmax` row **minimums** let the grid exceed the viewport instead of shrinking.
|
|
32
|
+
- **Layout attempt #2:** `.page { height:100vh; overflow:hidden }` + `grid-template-rows: 1fr 1fr` **alone** → rows still **overlapped by exactly 20px** (row-1 cards ran 169→490, row-2 started at 470). Reason: cards were `box-sizing: content-box`, so their `14px×2` padding + `1px×2` border added **30px on top** of the computed `290.5px` track height → each card rendered **321px** and overflowed its grid row. **Root fix:** global `*, *::before, *::after { box-sizing: border-box }`.
|
|
33
|
+
- **Absolutely-positioning the chart SVGs** (`.chart-fill { position:absolute; inset:0 }`) did **not** fix the row overlap on its own — the overlap was the card padding (content-box), not the SVG's intrinsic height. (Kept the absolute-fill regardless — it's correct for scaling charts to their cell — but it was not the fix.)
|
|
34
|
+
- **`/committed-tile` alias via `app._router.handle(req,res)`** — reached into Express internals and was fragile. Refactored both routes to call a shared `serveTile()` function instead.
|
|
35
|
+
- **First full-content Write to each new file** was denied once by the fact-forcing gate (keys on path, ignores content). Use stub-first for large new files to avoid re-emitting the whole body.
|
|
36
|
+
|
|
37
|
+
## Not tried yet (candidates for next session)
|
|
38
|
+
- **Productionize the tiles as real api2 Record Scripts** (`_Model_Team_Sprint::sprintTile` on `_underscore`, registered via **dbchanges2**) to replace the Express stand-in. `sql/register_committed_tile_endpoint.sql` is the starting template. This is the natural next step (user asked how the real data path works: worker2 ingests → Team DB → api2 Record Script serves).
|
|
39
|
+
- **Auth** — `AUTH_TOKEN` in the frontend is an empty placeholder; wire the Bearer JWT + confirm header names against a real `/v2/auth` call.
|
|
40
|
+
- **Reconcile current-state vs canonical scoring** — dashboard uses Power BI current-state (`dtDone`+`workTypeNow`); canonical scoring uses `statusNow IN DONE`+`workTypeAtLock`. Decide which a leadership-facing view should use.
|
|
41
|
+
- **Burndown day-numbering** — currently 1-based (Target starts at 90% on day 1, hits 0 on day N). Confirm against Power BI whether it should be 0-based (start at 100%). One-char change (`k=i` vs `k=i+1`).
|
|
42
|
+
- **Multi-dev point splitting** — Points-by-Dev currently credits each assigned dev the task's full points (matches the KB rule). Add a split option only if Power BI splits.
|
|
43
|
+
- **Time-progression live refresh** — currently a snapshot computed on page load; add a timer if a wall display needs it to tick.
|
|
44
|
+
|
|
45
|
+
## Current file state
|
|
46
|
+
| File | Status | Notes |
|
|
47
|
+
|------|--------|-------|
|
|
48
|
+
| `test/@krishna/React/backend/server.js` | Created/rewritten | Live-verified; current-sprint middleware + 6 `/v2/sprints/*` endpoints; injection-safe; `no-store`; `FALLBACK_SPRINT=82` only if table empty. |
|
|
49
|
+
| `test/@krishna/React/frontend/dashboard.html` | Created | 2×2 dashboard; Status shows task **counts**; box-sizing fix applied; live. |
|
|
50
|
+
| `test/@krishna/React/frontend/dashboard1.html` | Created (copy, then edited) | Donut variant; `USE_MOCK=false`; Status donut shows **points** (`unit="points"`); live. |
|
|
51
|
+
| `test/@krishna/React/frontend/committed-tile.html` | Untouched | Original single-tile page; left as-is. |
|
|
52
|
+
| `test/@krishna/React/sql/tile_query.sql` | Created | Parameterized query reference (documents column/value per tile). |
|
|
53
|
+
| `test/@krishna/React/sql/committed_tile.sql`, `register_committed_tile_endpoint.sql` | Pre-existing | The latter is the api2 Record Script registration template for productionizing. |
|
|
54
|
+
| `test/@krishna/React/backend/.env` | Present, NOT committed | `MOCK_MODE=false`, `DB_NAME=Team`, core-cluster reader host + `admin` user. Credentials live here only — never commit. |
|
|
55
|
+
| `knowledge/2.0/apps/worker2/features/team-sprint-management.md` | UPDATED + pushed | DAX→SQL metric definitions + Team schema vocabularies (`dtDone` vs `dtCompleted`, many-to-many points). |
|
|
56
|
+
|
|
57
|
+
## Decisions made
|
|
58
|
+
- **One parameterized tile endpoint** over four near-identical routes (DRY). Rejected: separate `committed-tile`/`stretch-tile`/… files (the original single-tile approach) — too much duplication for a 4-tile dashboard.
|
|
59
|
+
- **Backend resolves the current sprint per request** (middleware) and the frontend sends **no `?sprint=`**. Rationale: "always show the current sprint, never hardcode." Accepted the minor cost of an extra resolve query per endpoint; `/current` skips the middleware to avoid a double query.
|
|
60
|
+
- **Express + mysql2 as a local api2 stand-in** that imitates the `/v2` envelope, so the React frontend is drop-in for the real api2 later. Rejected: querying the DB from the browser (impossible/insecure) or building the real Record Script now (out of scope for the prototype).
|
|
61
|
+
- **Knowledge placed in `worker2/features/team-sprint-management.md`**, not a new `test/` doc. Rationale: that doc already owns the Team-schema + sprint-metric subject and is cross-linked from api2; the `test` sandbox's per-developer folders are excluded from the KB.
|
|
62
|
+
- **`dashboard1.html` is a separate file** for the donut/points variant; `dashboard.html` stays on task counts. Rationale: user wanted a distinct second dashboard without changing the first.
|
|
63
|
+
- **Global `box-sizing: border-box`** — the correct fix for the row-overlap (not min-heights, not absolute positioning).
|
|
64
|
+
|
|
65
|
+
## Blockers
|
|
66
|
+
None functional. Operational note: **the dev server does not persist across Claude sessions** — it must be restarted each time (`cd test/@krishna/React/backend && npm start`). It also serves `dashboard1.html` automatically (static folder).
|
|
67
|
+
|
|
68
|
+
## Exact next step
|
|
69
|
+
> Restart the server (`cd c:/www/test/@krishna/React/backend && npm start`) and open `http://localhost:3001/dashboard1.html` to confirm it still auto-resolves the current sprint. Then, to move off the prototype stand-in, implement the **api2 Record Script** version of the tiles — `_Model_Team_Sprint::sprintTile()` on `_underscore`, registered via **dbchanges2** using `sql/register_committed_tile_endpoint.sql` as the template — so the dashboard fetches through the real api2/`_underscore` path (worker2 keeps populating the `Team` tables; api2 serves them).
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
_Saved by /session-save on 2026-07-24_
|
package/package.json
CHANGED