toga-ai 1.0.434 → 1.0.435

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-23
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.434",
3
+ "version": "1.0.435",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",