Data Rules47 rulesall = document standard
re-verify the laws live → VerifyTransparency as a mechanism: this is the LIVE registry of every transformation between the raw Jira/ADO/BambooHR data and a number on screen — every rule, filter and fix; nothing happens silently. Each rule shows what controls it (fixed in code / a calculation policy / a tunable value / an owner tool) and its current per-project state. A deviation from the document standard glows red here, badges Metrics & Rules, and Verify recomputes the conservation laws on every load. If a number “doesn't eyeball-match Jira”, the cause is almost certainly one of these rows.
DOCa requirement of the source document/meeting — the spec says soFIXa correctness fix — without it the numbers would simply be technically wrongHONESTYan honesty rule — it discloses a gap instead of papering over itOWNERan owner tool — manual, reversible, always disclosedⓘ — hover the icon on any rule: a plain-words explanation + an example
Ingest (what changes during sync)
What happens at the moment data is downloaded from Jira / Azure DevOps / BambooHR into the local database. These rules fix the raw material (locales, API paging, stuck records) — before any math.
| FIX | Accept-Language: en on every Jira request — status/type names don't depend on the account locale (the “Chinese statuses” incident).i | always · in code |
| FIX | Status category is resolved by status ID from the changelog (name is only a fallback): Jira names are not unique.i | always · in code |
| FIX | Ticket upsert heals issueType, key and projectKey — moves/renames/locale poisoning don't freeze in.i | always · in code |
| DOC | PR↔ticket link: jira_keys extracted by regex from PR title + source branch, uppercased.i | always · in code |
| FIX | PR commits are fetched in a continuation loop ($top=500 per page, until exhausted) — the earliest commit survives for a PR of ANY size.i | always · in code |
| FIX | Builds and release deployments are fetched in continuation loops (builds used to be cut at 200).i | always · in code |
| HONESTY | A deployment without completedOn/status is skipped — an unfinished run is not an event.i | always · in code |
| HONESTY | Deployments upsert with onConflictDoNothing — a re-sync can never wipe manual failure flags.i | always · in code |
| FIX | Disabled (isDisabled) ADO repos are skipped — their PR API errors out.i | always · in code |
| FIX | Employee country: full name → ISO-2 via dictionary; a holiday's country comes from its name prefix (“US …”).i | always · in code |
| FIX | sync_runs rows stuck “running” for >2h are closed as error “interrupted”.i | always · in code |
Cohort (THE single entry for ticket metrics)
The cohort = which tickets count as “done in the window” at all. Every ticket metric (Throughput, Lead, Cycle, TTFP…) reads exactly this one list — that's why the counts always reconcile across pages.
| DOC | done_at = the LAST entry into a done status inside the window; the current status must still be done (re-opened tickets drop out).i | doneStatusesADS: Done, Resolved, ClosedEVO: Done, Resolved, Closed |
| DOC | All windows are half-open [from, to) — no double counting on boundaries.i | always · in code |
| DOC | Excluded issue types (Epics) never enter any metric.i | excludedIssueTypesADS: EpicEVO: Epic |
| OWNER | Manual exclusions — ONE shared mask: a ticket, an epic/feature WITH all descendants, an ADO repo (its PRs leave PR/TTFP/LtC) or a pipeline (its runs leave DF/CFR); disclosed by the “✕ N” chip and in Data quality.i | owner toolcurrently excluded: 0 |
| DOC | who-filter: assignee ∈ AI-agent roster / not; deployments, PRs and bugs are who-blind.i | always · in code |
Lead / Cycle / Active
The time metrics: how long a ticket lived (Lead — created→done), how long the work took (Cycle — started→done), and how much of it was hands-on work (Active — minus waiting, weekends, vacations).
| DOC | Lead = created → done_at in calendar days (weekends included — the document standard).i | includeNonWorkingDaysADS: ONEVO: ON |
| DOC | Cycle start = the FIRST transition into the In-Progress category (≤ done_at); never started → cycle = null, not 0. Empty list = the canonical category rule.i | cycleStartStatusesADS: —EVO: — |
| DOC | Time after a bounce back into a “new”-category status is SUBTRACTED from Cycle (the meeting's own “bug to fix”).i | subtractBounceBackADS: ONEVO: ON |
| FIX | Cycle/TTFP are clamped at Math.max(0, …).i | always · in code |
| DOC | Active: non-passive statuses only, weekdays only, minus vacations (BambooHR) and country holidays, capped per day; SHOWN in a selectable unit (calendar ÷24 · working days ÷8/9 · hours), chart always calendar.i | passiveStatusesADS: On Hold, Blocked, Failed on QA, Ready To Test, Ready To MergeEVO: On Hold, Blocked, Failed on QA, Ready To Test, Ready To MergeworkingHoursPerDayADS: 8EVO: 8 |
| HONESTY | Active for people not linked to BambooHR is computed without vacations (a “no HR” badge discloses it).i | always · in code |
TTFP / LtC / PR
The code-side metrics: how fast the first pull request appears (TTFP), how long code travels to production (Lead Time for Changes), and PR throughput/acceptance.
| DOC | TTFP = the first PR opened AT/AFTER work start (spec M5); bounce-back time is subtracted (“same rules as Cycle”).i | always · in code |
| DOC | Tickets whose ONLY PRs predate the start → a separate disclosed prBeforeStart bucket (not 0 and not “no PR”); bucket sums = cohort (a law).i | ttfpAnomalyBucketADS: ONEVO: ON |
| DOC | LtC = first commit → the FIRST successful prod deploy AFTER merge; changes not yet in prod are honestly not counted.i | always · in code |
| DOC | PR metrics: only terminal (completed/abandoned) PRs closed in the window; acceptance = merged/(merged+abandoned).i | always · in code |
| DOC | Agent PR authorship: author_id ∈ the roster (adoUserId).i | always · in code |
Deployments (DF / CFR)
The DORA deployment metrics: how often we ship to production (Deployment Frequency) and what share of those shipments breaks (Change Failure Rate).
| FIX | Environment tier by name: LOWER tokens are checked FIRST (substring guard: “pre-production” is not prod); unrecognized → unknown, kept out of prod and disclosed as a count.i | preProdEnvNamesADS: pre-prod, preprod, pre-production, staging, stage, canary, alpha, beta, qa, test, dev, sandbox, uat, featureEVO: pre-prod, preprod, pre-production, staging, stage, canary, alpha, beta, qa, test, dev, sandbox, uat, featureprodEnvNamesADS: production, prod, prd, live, stableEVO: production, prod, prd, live, stable |
| FIX | Build pipelines marked as deploying: tier by the DEFINITION NAME; unrecognizable name = PROD (the mark itself means “this deploys”).i | always · in code |
| DOC | CFR denominator = ELIGIBLE runs (success ∪ failure); canceled/notDeployed are excluded and disclosed (ineligibleProd).i | cfrEligibleOnlyADS: ONEVO: ON |
| FIX | The manual flag OVERRIDES the result: a flagged succeeded run is a failure and NOT a success (succeeded+failed=total — a law).i | manualFlagOverridesResultADS: ONEVO: ON |
| DOC | DF = successful prod deploys; rates per day / week / month (month = monthDays).i | successResultsADS: succeededEVO: succeededmonthDaysADS: 30EVO: 30 |
Bugs (Quality)
Quality: how many bugs appear and how severe they are; split into production / pre-production when the tickets carry environment labels.
| DOC | A bug = Bug/Incident CREATED in the window.i | unplannedIssueTypesADS: Bug, IncidentEVO: Bug, Incident |
| HONESTY | severity NULL → “Unspecified” (never dropped); first matching severity field wins.i | severityFieldsADS: customfield_10050, customfield_10511, customfield_10093EVO: customfield_10050, customfield_10511, customfield_10093 |
| DOC | Partial env labeling: prod + preProd + unknown = all (a law); headline = prod once ANY label exists, but the unlabeled remainder is always disclosed.i | always · in code |
| HONESTY | Environment comes from ticket labels; no labels → the prod/pre-prod split is honestly “unavailable”, ALL bugs still shown + which labels are needed.i | prodEnvLabelsADS: production, prodEVO: production, prodpreProdEnvLabelsADS: pre-prod, preprod, pre-production, staging, qaEVO: pre-prod, preprod, pre-production, staging, qa |
Status breakdown / Bottlenecks / CFD
Where the time actually goes: per-status time totals, what is stuck in work right now (Aging WIP) and where queues grow week over week (the Cumulative Flow Diagram).
| DOC | Status Breakdown: intervals from start to done; done statuses excluded; the “new” category is shown but marked “excluded from Cycle”.i | always · in code |
| HONESTY | Law: Σ of non-new buckets ≤ Σ cycle; the gap = re-opened tickets' time inside done (measured and disclosed on Verify).i | always · in code |
| FIX | Workflow strip: one pill per status NAME (dominant category by transition count).i | breakdownMergeSameNamePillsADS: ONEVO: ON |
| HONESTY | Aging WIP: only the In-Progress category (backlog rot like “Removed, 777 d” is not WIP).i | agingWipOnlyInProgressADS: ONEVO: ON |
| HONESTY | CFD: work stages only, top-8 by peak (the backlog and done bands drowned the queues that matter).i | cfdWorkStagesOnlyADS: ONEVO: ON |
| OWNER | Suspect thresholds (180/365/730/custom) are view-level — they never change data.i | always · in code |
Display level (stored data untouched)
How numbers LOOK on screen — rounding, medians, one abbreviation. Nothing here changes the stored data.
| DOC | round1 for display; MEDIANS for “averages” (robust to outliers — a spec decision).i | always · in code |
| FIX | “Product Backlog Item” → “PBI” (full name kept in the title attribute).i | always · in code |
| HONESTY | Names/categories/keys are NEVER rewritten for display — the PBI shortening is the only one.i | always · in code |
Owner / admin (visibility, not data)
What the owner can switch on/off by hand — and the honesty boundary that no switch can cross.
| OWNER | Admin flags hide pages/elements for everyone; the owner key reveals all; rule and exclusion writes are key-gated.i | owner tool |
| HONESTY | Hiding never touches the honesty disclosures: the “✕ N excluded” chip and Data quality stay visible always.i | always · in code |