@uptimizr/db 2.0.1 → 2.1.0
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.
- package/AGENTS.md +228 -3
- package/README.md +13 -0
- package/dist/duckdb/migrations.d.ts.map +1 -1
- package/dist/duckdb/migrations.js +169 -0
- package/dist/duckdb/migrations.js.map +1 -1
- package/dist/duckdb/panelSpecs.d.ts +29 -0
- package/dist/duckdb/panelSpecs.d.ts.map +1 -0
- package/dist/duckdb/panelSpecs.js +104 -0
- package/dist/duckdb/panelSpecs.js.map +1 -0
- package/dist/duckdb/projectMetadata.d.ts +43 -0
- package/dist/duckdb/projectMetadata.d.ts.map +1 -0
- package/dist/duckdb/projectMetadata.js +222 -0
- package/dist/duckdb/projectMetadata.js.map +1 -0
- package/dist/duckdb/subscriptions.d.ts +45 -0
- package/dist/duckdb/subscriptions.d.ts.map +1 -0
- package/dist/duckdb/subscriptions.js +202 -0
- package/dist/duckdb/subscriptions.js.map +1 -0
- package/dist/index.d.ts +18 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +48 -3
- package/dist/index.js.map +1 -1
- package/dist/insights/anomalies.d.ts +213 -0
- package/dist/insights/anomalies.d.ts.map +1 -0
- package/dist/insights/anomalies.js +340 -0
- package/dist/insights/anomalies.js.map +1 -0
- package/dist/insights/baseline.d.ts +51 -0
- package/dist/insights/baseline.d.ts.map +1 -0
- package/dist/insights/baseline.js +48 -0
- package/dist/insights/baseline.js.map +1 -0
- package/dist/insights/buckets.d.ts +114 -0
- package/dist/insights/buckets.d.ts.map +1 -0
- package/dist/insights/buckets.js +220 -0
- package/dist/insights/buckets.js.map +1 -0
- package/dist/insights/changepoint.d.ts +130 -0
- package/dist/insights/changepoint.d.ts.map +1 -0
- package/dist/insights/changepoint.js +209 -0
- package/dist/insights/changepoint.js.map +1 -0
- package/dist/insights/evaluate.d.ts +63 -0
- package/dist/insights/evaluate.d.ts.map +1 -0
- package/dist/insights/evaluate.js +158 -0
- package/dist/insights/evaluate.js.map +1 -0
- package/dist/insights/health.d.ts +219 -0
- package/dist/insights/health.d.ts.map +1 -0
- package/dist/insights/health.js +349 -0
- package/dist/insights/health.js.map +1 -0
- package/dist/insights/index.d.ts +45 -0
- package/dist/insights/index.d.ts.map +1 -0
- package/dist/insights/index.js +40 -0
- package/dist/insights/index.js.map +1 -0
- package/dist/insights/measures.d.ts +222 -0
- package/dist/insights/measures.d.ts.map +1 -0
- package/dist/insights/measures.js +431 -0
- package/dist/insights/measures.js.map +1 -0
- package/dist/insights/movers.d.ts +123 -0
- package/dist/insights/movers.d.ts.map +1 -0
- package/dist/insights/movers.js +166 -0
- package/dist/insights/movers.js.map +1 -0
- package/dist/insights/significance.d.ts +221 -0
- package/dist/insights/significance.d.ts.map +1 -0
- package/dist/insights/significance.js +531 -0
- package/dist/insights/significance.js.map +1 -0
- package/dist/insights/stats.d.ts +104 -0
- package/dist/insights/stats.d.ts.map +1 -0
- package/dist/insights/stats.js +181 -0
- package/dist/insights/stats.js.map +1 -0
- package/dist/insights/windows.d.ts +95 -0
- package/dist/insights/windows.d.ts.map +1 -0
- package/dist/insights/windows.js +97 -0
- package/dist/insights/windows.js.map +1 -0
- package/dist/metadata.d.ts +178 -1
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +80 -0
- package/dist/metadata.js.map +1 -1
- package/dist/narrative/build.d.ts +78 -0
- package/dist/narrative/build.d.ts.map +1 -0
- package/dist/narrative/build.js +437 -0
- package/dist/narrative/build.js.map +1 -0
- package/dist/narrative/index.d.ts +14 -0
- package/dist/narrative/index.d.ts.map +1 -0
- package/dist/narrative/index.js +13 -0
- package/dist/narrative/index.js.map +1 -0
- package/dist/narrative/text.d.ts +25 -0
- package/dist/narrative/text.d.ts.map +1 -0
- package/dist/narrative/text.js +41 -0
- package/dist/narrative/text.js.map +1 -0
- package/dist/parity/cases.d.ts +3 -0
- package/dist/parity/cases.d.ts.map +1 -1
- package/dist/parity/cases.js +387 -7
- package/dist/parity/cases.js.map +1 -1
- package/dist/parity/compare.d.ts +19 -0
- package/dist/parity/compare.d.ts.map +1 -1
- package/dist/parity/compare.js +19 -0
- package/dist/parity/compare.js.map +1 -1
- package/dist/parity/fixtures.d.ts.map +1 -1
- package/dist/parity/fixtures.js +25 -0
- package/dist/parity/fixtures.js.map +1 -1
- package/dist/query/aggregations.d.ts +46 -1
- package/dist/query/aggregations.d.ts.map +1 -1
- package/dist/query/aggregations.js +86 -0
- package/dist/query/aggregations.js.map +1 -1
- package/dist/query/clickhouseDialect.d.ts.map +1 -1
- package/dist/query/clickhouseDialect.js +5 -1
- package/dist/query/clickhouseDialect.js.map +1 -1
- package/dist/query/customEventVocabulary.d.ts +46 -0
- package/dist/query/customEventVocabulary.d.ts.map +1 -0
- package/dist/query/customEventVocabulary.js +127 -0
- package/dist/query/customEventVocabulary.js.map +1 -0
- package/dist/query/dsl/builders.d.ts +38 -0
- package/dist/query/dsl/builders.d.ts.map +1 -0
- package/dist/query/dsl/builders.js +35 -0
- package/dist/query/dsl/builders.js.map +1 -0
- package/dist/query/dsl/compare.d.ts +167 -0
- package/dist/query/dsl/compare.d.ts.map +1 -0
- package/dist/query/dsl/compare.js +358 -0
- package/dist/query/dsl/compare.js.map +1 -0
- package/dist/query/dsl/compile.d.ts +89 -0
- package/dist/query/dsl/compile.d.ts.map +1 -0
- package/dist/query/dsl/compile.js +174 -0
- package/dist/query/dsl/compile.js.map +1 -0
- package/dist/query/dsl/explain.d.ts +115 -0
- package/dist/query/dsl/explain.d.ts.map +1 -0
- package/dist/query/dsl/explain.js +143 -0
- package/dist/query/dsl/explain.js.map +1 -0
- package/dist/query/dsl/generic.d.ts +127 -0
- package/dist/query/dsl/generic.d.ts.map +1 -0
- package/dist/query/dsl/generic.js +323 -0
- package/dist/query/dsl/generic.js.map +1 -0
- package/dist/query/dsl/index.d.ts +30 -0
- package/dist/query/dsl/index.d.ts.map +1 -0
- package/dist/query/dsl/index.js +23 -0
- package/dist/query/dsl/index.js.map +1 -0
- package/dist/query/dsl/order.d.ts +41 -0
- package/dist/query/dsl/order.d.ts.map +1 -0
- package/dist/query/dsl/order.js +55 -0
- package/dist/query/dsl/order.js.map +1 -0
- package/dist/query/dsl/significance.d.ts +130 -0
- package/dist/query/dsl/significance.d.ts.map +1 -0
- package/dist/query/dsl/significance.js +224 -0
- package/dist/query/dsl/significance.js.map +1 -0
- package/dist/query/index.d.ts +2 -0
- package/dist/query/index.d.ts.map +1 -1
- package/dist/query/index.js +8 -0
- package/dist/query/index.js.map +1 -1
- package/dist/query/summary/index.d.ts +4 -2
- package/dist/query/summary/index.d.ts.map +1 -1
- package/dist/query/summary/index.js +2 -1
- package/dist/query/summary/index.js.map +1 -1
- package/dist/query/summary/labels.d.ts +101 -0
- package/dist/query/summary/labels.d.ts.map +1 -0
- package/dist/query/summary/labels.js +261 -0
- package/dist/query/summary/labels.js.map +1 -0
- package/dist/query/summary/reading.d.ts.map +1 -1
- package/dist/query/summary/reading.js +21 -2
- package/dist/query/summary/reading.js.map +1 -1
- package/dist/query/summary/schema.d.ts +250 -20
- package/dist/query/summary/schema.d.ts.map +1 -1
- package/dist/query/summary/schema.js +135 -123
- package/dist/query/summary/schema.js.map +1 -1
- package/dist/query/summary/summarize.d.ts.map +1 -1
- package/dist/query/summary/summarize.js +91 -5
- package/dist/query/summary/summarize.js.map +1 -1
- package/dist/query/summary/types.d.ts +77 -2
- package/dist/query/summary/types.d.ts.map +1 -1
- package/dist/query/types.d.ts +47 -1
- package/dist/query/types.d.ts.map +1 -1
- package/dist/subscriptions.d.ts +208 -0
- package/dist/subscriptions.d.ts.map +1 -0
- package/dist/subscriptions.js +153 -0
- package/dist/subscriptions.js.map +1 -0
- package/llms.txt +9 -0
- package/package.json +8 -8
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **`scene_health`** — "which scene is in trouble, and why?" (ADR 0051 §4, sketch §D).
|
|
3
|
+
*
|
|
4
|
+
* One number per scene, and — more importantly — the six numbers it was built
|
|
5
|
+
* from. A single score is only useful if it can be taken apart: every factor
|
|
6
|
+
* reports the **metric id** behind it, the **raw** value that metric produced,
|
|
7
|
+
* the **normalised** 0–100 it contributed and the **weight** it carried, so
|
|
8
|
+
* "the lobby is at 38" is always one step away from "because its 5th-percentile
|
|
9
|
+
* FPS is 19 against a project norm of 47".
|
|
10
|
+
*
|
|
11
|
+
* Pure TypeScript over the same bucket series as every other primitive.
|
|
12
|
+
*
|
|
13
|
+
* ## The score is relative, and says so
|
|
14
|
+
*
|
|
15
|
+
* There is no absolute scale on which 60 FPS is "healthy" — a marketing
|
|
16
|
+
* configurator and a six-player VR game do not share one — so every factor is
|
|
17
|
+
* normalised against **the project's own baseline over the previous equal
|
|
18
|
+
* window**, using the same robust centre-and-spread `movers` ranks with
|
|
19
|
+
* (median, and a MAD floored at 1% of the level). A factor exactly at the
|
|
20
|
+
* project norm scores **50**; {@link HEALTH_Z_SPAN} robust deviations better
|
|
21
|
+
* scores 100, the same distance worse scores 0.
|
|
22
|
+
*
|
|
23
|
+
* That makes the score a comparison, not a grade: a project where every scene
|
|
24
|
+
* is equally bad reads 50 across the board. It is the right shape for the
|
|
25
|
+
* question actually asked ("*which* scene should I look at this week"), and the
|
|
26
|
+
* registry entry's caveats say plainly that it is not a quality bar.
|
|
27
|
+
*
|
|
28
|
+
* ## Factors are declared, not inferred
|
|
29
|
+
*
|
|
30
|
+
* {@link HEALTH_FACTORS} is a fixed, ordered catalog. Each entry names the
|
|
31
|
+
* metric it reads, whether it is a level or a rate (and which catalog series
|
|
32
|
+
* supply the numerator and denominator), which direction is good, and its
|
|
33
|
+
* default weight. Nothing here is derived from a request, so the fan-out is a
|
|
34
|
+
* compile-time constant and a caller can only change the **weights** — never
|
|
35
|
+
* which series are read.
|
|
36
|
+
*/
|
|
37
|
+
import { BUCKET_SECONDS } from "./measures.js";
|
|
38
|
+
import { referenceSpread } from "./movers.js";
|
|
39
|
+
import { median, round } from "./stats.js";
|
|
40
|
+
import { DAY_MS, floorToBucket } from "./windows.js";
|
|
41
|
+
/**
|
|
42
|
+
* The six factors, in the order they are reported.
|
|
43
|
+
*
|
|
44
|
+
* The weights are a judgement, and they are declared here (and surfaced in the
|
|
45
|
+
* registry entry, so they appear in `capabilities` and in the generated tool
|
|
46
|
+
* catalog) precisely so that judgement is arguable rather than hidden: errors
|
|
47
|
+
* and raw smoothness dominate, frustration signals matter, and exploration is a
|
|
48
|
+
* tiebreak. A project that disagrees passes `weights`.
|
|
49
|
+
*/
|
|
50
|
+
export const HEALTH_FACTORS = [
|
|
51
|
+
{
|
|
52
|
+
id: "perf_stability",
|
|
53
|
+
metric: "perf_summary",
|
|
54
|
+
numerator: "p05",
|
|
55
|
+
direction: "up",
|
|
56
|
+
weight: 0.25,
|
|
57
|
+
unit: "FPS",
|
|
58
|
+
note: "The 5th-percentile FPS of the scene's sampled frames — how bad it gets, not how good it " +
|
|
59
|
+
"usually is. A scene whose median is fine and whose p05 is not is a stuttering scene.",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: "jank_rate",
|
|
63
|
+
metric: "jank_rate",
|
|
64
|
+
numerator: "numerator",
|
|
65
|
+
denominator: "denominator",
|
|
66
|
+
direction: "down",
|
|
67
|
+
weight: 0.2,
|
|
68
|
+
unit: "long frames per sampled window",
|
|
69
|
+
note: "Long frames per sampled perf window, pooled across sessions. The metric's own endpoint " +
|
|
70
|
+
"reports a per-session median instead, so the two agree in direction but not in value.",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: "error_rate",
|
|
74
|
+
metric: "error_heatmap",
|
|
75
|
+
denominator: "denominator",
|
|
76
|
+
direction: "down",
|
|
77
|
+
weight: 0.25,
|
|
78
|
+
unit: "errors per session",
|
|
79
|
+
note: "Runtime errors and engine diagnostics per session started. Divided by sessions because an " +
|
|
80
|
+
"error count with no traffic behind it says nothing.",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "dead_click_rate",
|
|
84
|
+
metric: "dead_clicks",
|
|
85
|
+
denominator: "denominator",
|
|
86
|
+
direction: "down",
|
|
87
|
+
weight: 0.15,
|
|
88
|
+
unit: "share of clicks",
|
|
89
|
+
note: "The share of clicks that hit no mesh at all — the 3D discoverability signal. Rage-click " +
|
|
90
|
+
"bursts are not folded in: they are defined by the gap between consecutive clicks and have " +
|
|
91
|
+
"no portable per-bucket form.",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "coverage",
|
|
95
|
+
metric: "scene_coverage",
|
|
96
|
+
denominator: "denominator",
|
|
97
|
+
direction: "up",
|
|
98
|
+
weight: 0.1,
|
|
99
|
+
unit: "camera samples per session",
|
|
100
|
+
note: "Positioned camera samples per session — how much of the scene visitors actually move " +
|
|
101
|
+
"through. A true voxel-coverage percentage needs the registered scene bounds and has no " +
|
|
102
|
+
"portable per-bucket form, so this is the portable proxy, read only against the project's " +
|
|
103
|
+
"own baseline.",
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: "xr_abandonment",
|
|
107
|
+
metric: "xr_abandonment",
|
|
108
|
+
numerator: "numerator",
|
|
109
|
+
denominator: "denominator",
|
|
110
|
+
direction: "up",
|
|
111
|
+
weight: 0.05,
|
|
112
|
+
unit: "XR interactions per XR session",
|
|
113
|
+
note: "Interactions per headset session. Absent (not zero) in a project with no XR traffic: a " +
|
|
114
|
+
"scene nobody visited in VR is not an unhealthy VR scene.",
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
/** Every factor id, for validating a `weights` override. */
|
|
118
|
+
export const HEALTH_FACTOR_IDS = HEALTH_FACTORS.map((factor) => factor.id);
|
|
119
|
+
/** The declared default weights, as the registry entry publishes them. */
|
|
120
|
+
export const HEALTH_DEFAULT_WEIGHTS = Object.fromEntries(HEALTH_FACTORS.map((factor) => [factor.id, factor.weight]));
|
|
121
|
+
/**
|
|
122
|
+
* How many robust deviations from the project norm span half the scale.
|
|
123
|
+
*
|
|
124
|
+
* At the norm a factor scores 50; `HEALTH_Z_SPAN` deviations better scores 100
|
|
125
|
+
* and the same distance worse scores 0. Four is chosen so that the everyday
|
|
126
|
+
* range of a metric occupies the middle of the scale rather than saturating it
|
|
127
|
+
* — a scene one MAD off the norm reads 62 or 38, which is a nudge, and it takes
|
|
128
|
+
* a genuinely unusual reading to bottom out.
|
|
129
|
+
*/
|
|
130
|
+
export const HEALTH_Z_SPAN = 4;
|
|
131
|
+
/** Decimals a health number is rounded to before it leaves the API. */
|
|
132
|
+
export const HEALTH_PRECISION = 6;
|
|
133
|
+
/**
|
|
134
|
+
* Sessions below which a scene's score is not worth reading.
|
|
135
|
+
*
|
|
136
|
+
* Not a filter: the row is returned with its `sampleSize` so a caller can see
|
|
137
|
+
* why, exactly as `movers` keeps its sub-`minSample` rows.
|
|
138
|
+
*/
|
|
139
|
+
export const HEALTH_MIN_SESSIONS = 20;
|
|
140
|
+
/** How many scenes one request may score when the caller names none. */
|
|
141
|
+
export const HEALTH_DEFAULT_SCENES = 5;
|
|
142
|
+
/** The hard cap on scenes per request — the fan-out is linear in this. */
|
|
143
|
+
export const HEALTH_MAX_SCENES = 10;
|
|
144
|
+
/** Default window, in days. */
|
|
145
|
+
export const HEALTH_DEFAULT_WINDOW_DAYS = 7;
|
|
146
|
+
/**
|
|
147
|
+
* Resolve the window a health score is computed over, and the project baseline
|
|
148
|
+
* window immediately before it.
|
|
149
|
+
*
|
|
150
|
+
* **This is the one insight primitive whose window includes the bucket in
|
|
151
|
+
* progress**, and the difference is deliberate. `baseline` and `movers` floor
|
|
152
|
+
* `until` down to the last complete bucket because they report *counts*, and a
|
|
153
|
+
* third of a day of traffic compared against whole days reads as a collapse
|
|
154
|
+
* every morning. Every health factor is a **rate or a percentile** — errors per
|
|
155
|
+
* session, long frames per sampled window, the 5th-percentile FPS — and none of
|
|
156
|
+
* those is distorted by a partial bucket: half a day of data gives half the
|
|
157
|
+
* numerator *and* half the denominator.
|
|
158
|
+
*
|
|
159
|
+
* Flooring here would instead make the score answer about **yesterday**, which
|
|
160
|
+
* is the wrong answer to 'which scene is in trouble' and, on a dashboard whose
|
|
161
|
+
* range is the last hour, would show an empty tile all day.
|
|
162
|
+
*
|
|
163
|
+
* The baseline is the equal window immediately before — the same 'previous
|
|
164
|
+
* equal window' reference `movers` uses, so a health score and a movers list
|
|
165
|
+
* are read against the same past.
|
|
166
|
+
*/
|
|
167
|
+
export function resolveHealthWindows(opts) {
|
|
168
|
+
const width = BUCKET_SECONDS[opts.bucket] * 1000;
|
|
169
|
+
// Round `until` **up**, so the bucket the request lands in is whole.
|
|
170
|
+
const until = Math.ceil((opts.until ?? opts.now) / width) * width;
|
|
171
|
+
const days = opts.windowDays ?? HEALTH_DEFAULT_WINDOW_DAYS;
|
|
172
|
+
const sinceRaw = floorToBucket(opts.since ?? until - days * DAY_MS, opts.bucket);
|
|
173
|
+
const since = sinceRaw >= until ? until - width : sinceRaw;
|
|
174
|
+
const span = until - since;
|
|
175
|
+
return { range: { since, until }, baseline: { since: since - span, until: since } };
|
|
176
|
+
}
|
|
177
|
+
/** The sum of a series' finite bucket values; `null` when it carried none. */
|
|
178
|
+
function total(rows) {
|
|
179
|
+
if (rows == null)
|
|
180
|
+
return null;
|
|
181
|
+
let sum = 0;
|
|
182
|
+
let seen = 0;
|
|
183
|
+
for (const row of rows) {
|
|
184
|
+
if (row.value != null && Number.isFinite(row.value)) {
|
|
185
|
+
sum += row.value;
|
|
186
|
+
seen += 1;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return seen === 0 ? null : sum;
|
|
190
|
+
}
|
|
191
|
+
/** The mean of a series' finite bucket values; `null` when it carried none. */
|
|
192
|
+
function levelOf(rows) {
|
|
193
|
+
if (rows == null)
|
|
194
|
+
return null;
|
|
195
|
+
const sum = total(rows);
|
|
196
|
+
if (sum == null)
|
|
197
|
+
return null;
|
|
198
|
+
let seen = 0;
|
|
199
|
+
for (const row of rows)
|
|
200
|
+
if (row.value != null && Number.isFinite(row.value))
|
|
201
|
+
seen += 1;
|
|
202
|
+
return seen === 0 ? null : sum / seen;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* The factor's value over a window.
|
|
206
|
+
*
|
|
207
|
+
* A rate is the **window ratio** — total numerator over total denominator, not
|
|
208
|
+
* the mean of the per-bucket ratios. A day with three sessions and a day with
|
|
209
|
+
* three hundred must not weigh the same in "errors per session", and averaging
|
|
210
|
+
* the daily ratios is exactly the mistake that makes them.
|
|
211
|
+
*/
|
|
212
|
+
function windowValue(series, spec) {
|
|
213
|
+
if (spec.denominator == null)
|
|
214
|
+
return levelOf(series.numerator);
|
|
215
|
+
const numerator = total(series.numerator) ?? 0;
|
|
216
|
+
const denominator = total(series.denominator);
|
|
217
|
+
if (denominator == null || denominator <= 0)
|
|
218
|
+
return null;
|
|
219
|
+
return numerator / denominator;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* The per-bucket series the baseline's centre and spread are taken over.
|
|
223
|
+
*
|
|
224
|
+
* For a rate that is the bucket-by-bucket ratio, so the spread reflects how
|
|
225
|
+
* much the *rate* normally moves rather than how much traffic does. Buckets
|
|
226
|
+
* whose denominator is zero are dropped: a day with no sessions has no error
|
|
227
|
+
* rate, and counting it as zero would drag every baseline down.
|
|
228
|
+
*/
|
|
229
|
+
function baselineSeries(series, spec) {
|
|
230
|
+
if (spec.denominator == null) {
|
|
231
|
+
return series.numerator
|
|
232
|
+
.map((row) => row.value)
|
|
233
|
+
.filter((value) => value != null && Number.isFinite(value));
|
|
234
|
+
}
|
|
235
|
+
const numerators = new Map();
|
|
236
|
+
for (const row of series.numerator) {
|
|
237
|
+
if (row.value != null && Number.isFinite(row.value))
|
|
238
|
+
numerators.set(row.bucket, row.value);
|
|
239
|
+
}
|
|
240
|
+
const out = [];
|
|
241
|
+
for (const row of series.denominator ?? []) {
|
|
242
|
+
if (row.value == null || !Number.isFinite(row.value) || row.value <= 0)
|
|
243
|
+
continue;
|
|
244
|
+
out.push((numerators.get(row.bucket) ?? 0) / row.value);
|
|
245
|
+
}
|
|
246
|
+
return out;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Map a raw value onto 0–100 against a baseline centre and spread.
|
|
250
|
+
*
|
|
251
|
+
* Linear in the robust z and clamped, rather than a logistic or a percentile
|
|
252
|
+
* rank: a reader has to be able to do the arithmetic backwards from the row.
|
|
253
|
+
* `null` when the baseline carried nothing to compare against — an unscored
|
|
254
|
+
* factor, never a default of 50, because "we have no history for this" and
|
|
255
|
+
* "this is exactly normal" are different statements.
|
|
256
|
+
*/
|
|
257
|
+
export function normaliseFactor(raw, baseline, direction) {
|
|
258
|
+
const centre = median(baseline);
|
|
259
|
+
if (raw == null || centre == null)
|
|
260
|
+
return { score: null, centre };
|
|
261
|
+
const spread = referenceSpread(baseline);
|
|
262
|
+
if (spread == null || !(spread > 0)) {
|
|
263
|
+
// A baseline with no spread at all (one bucket, or a perfectly flat one)
|
|
264
|
+
// cannot say how unusual anything is. Equal to it is normal; anything else
|
|
265
|
+
// is unquantifiable, so the factor reports the direction and nothing more.
|
|
266
|
+
if (raw === centre)
|
|
267
|
+
return { score: 50, centre };
|
|
268
|
+
const better = direction === "up" ? raw > centre : raw < centre;
|
|
269
|
+
return { score: better ? 100 : 0, centre };
|
|
270
|
+
}
|
|
271
|
+
const z = (raw - centre) / spread;
|
|
272
|
+
const oriented = direction === "up" ? z : -z;
|
|
273
|
+
const score = 50 + (50 * oriented) / HEALTH_Z_SPAN;
|
|
274
|
+
return { score: Math.min(100, Math.max(0, score)), centre };
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Resolve the weights a request scores with.
|
|
278
|
+
*
|
|
279
|
+
* An override replaces a factor's weight; factors the override does not mention
|
|
280
|
+
* keep their declared weight, so `weights={"error_rate":0.6}` means "I care
|
|
281
|
+
* much more about errors" rather than "score errors only". Unknown ids are
|
|
282
|
+
* rejected at the route, not silently ignored.
|
|
283
|
+
*/
|
|
284
|
+
export function resolveWeights(override) {
|
|
285
|
+
const weights = { ...HEALTH_DEFAULT_WEIGHTS };
|
|
286
|
+
for (const [id, value] of Object.entries(override ?? {})) {
|
|
287
|
+
if (!Object.prototype.hasOwnProperty.call(weights, id))
|
|
288
|
+
continue;
|
|
289
|
+
if (Number.isFinite(value) && value >= 0)
|
|
290
|
+
weights[id] = value;
|
|
291
|
+
}
|
|
292
|
+
return weights;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Score one scene.
|
|
296
|
+
*
|
|
297
|
+
* `inputs` is keyed by factor id; a factor with no entry (a project with no XR
|
|
298
|
+
* traffic, a scene with no perf samples) is reported with `score: null` and
|
|
299
|
+
* excluded from the weighted mean rather than dropped from the row — the reason
|
|
300
|
+
* a score is missing a factor has to be visible in the answer.
|
|
301
|
+
*/
|
|
302
|
+
export function computeSceneHealth(opts) {
|
|
303
|
+
const weights = resolveWeights(opts.weights);
|
|
304
|
+
const factors = [];
|
|
305
|
+
let weighted = 0;
|
|
306
|
+
let weightTotal = 0;
|
|
307
|
+
for (const spec of HEALTH_FACTORS) {
|
|
308
|
+
const weight = weights[spec.id] ?? 0;
|
|
309
|
+
const input = opts.inputs[spec.id];
|
|
310
|
+
const raw = input == null ? null : windowValue(input.current, spec);
|
|
311
|
+
const baseline = input == null ? [] : baselineSeries(input.baseline, spec);
|
|
312
|
+
const { score, centre } = normaliseFactor(raw, baseline, spec.direction);
|
|
313
|
+
if (score != null && weight > 0) {
|
|
314
|
+
weighted += weight * score;
|
|
315
|
+
weightTotal += weight;
|
|
316
|
+
}
|
|
317
|
+
factors.push({
|
|
318
|
+
id: spec.id,
|
|
319
|
+
metric: spec.metric,
|
|
320
|
+
raw: round(raw, HEALTH_PRECISION),
|
|
321
|
+
baseline: round(centre, HEALTH_PRECISION),
|
|
322
|
+
score: round(score, HEALTH_PRECISION),
|
|
323
|
+
weight,
|
|
324
|
+
unit: spec.unit,
|
|
325
|
+
note: score == null
|
|
326
|
+
? `${spec.note} Not scored: ${raw == null ? "the scene produced no value for it in this window" : "the project has no baseline for it in the preceding window"}.`
|
|
327
|
+
: spec.note,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
scene: opts.scene,
|
|
332
|
+
score: weightTotal > 0 ? round(weighted / weightTotal, HEALTH_PRECISION) : null,
|
|
333
|
+
factors,
|
|
334
|
+
sampleSize: opts.sampleSize,
|
|
335
|
+
since: opts.since,
|
|
336
|
+
until: opts.until,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
/** Least healthy first, then by scene id so the order is a function of the rows. */
|
|
340
|
+
export function rankSceneHealth(rows) {
|
|
341
|
+
return [...rows].sort((a, b) => {
|
|
342
|
+
const left = a.score ?? Number.POSITIVE_INFINITY;
|
|
343
|
+
const right = b.score ?? Number.POSITIVE_INFINITY;
|
|
344
|
+
if (left !== right)
|
|
345
|
+
return left - right;
|
|
346
|
+
return a.scene < b.scene ? -1 : a.scene > b.scene ? 1 : 0;
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/insights/health.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **Insight primitives** — `baseline` and `movers` (ADR 0051 §4, sketch §D).
|
|
3
|
+
*
|
|
4
|
+
* The shape of this directory is the design:
|
|
5
|
+
*
|
|
6
|
+
* - `measures.ts` declares, per comparable registry metric, how its primary
|
|
7
|
+
* column is reproduced as a per-bucket series;
|
|
8
|
+
* - `buckets.ts` renders exactly one generic, dialect-agnostic query from that
|
|
9
|
+
* declaration (parity-tested on DuckDB, ClickHouse, Postgres and SQL Server);
|
|
10
|
+
* - `stats.ts`, `baseline.ts`, `movers.ts` and `windows.ts` are **pure
|
|
11
|
+
* TypeScript** over the rows that query returns — no SQL, no I/O, no dialect.
|
|
12
|
+
*
|
|
13
|
+
* Statistics never run in SQL. Five engines disagree about `quantile`, `median`
|
|
14
|
+
* and `stddev`, and an insight that changes when a self-hoster switches store is
|
|
15
|
+
* not an insight. `anomalies`, `significance` and `scene_health` (ADR 0051 §4)
|
|
16
|
+
* are designed to slot in here the same way: a new pure module over the same
|
|
17
|
+
* bucket series, with no new per-dialect SQL.
|
|
18
|
+
*/
|
|
19
|
+
export { BUCKETABLE_METRIC_IDS, BUCKET_MEASURES, BUCKET_SECONDS, MOVERS_DEFAULT_METRICS, MOVERS_MAX_METRICS, bucketMeasureFor, isBucketGrain, isBucketableMetric, } from "./measures.js";
|
|
20
|
+
export type { BucketAggregate, BucketGrain, BucketMeasure, BucketPredicate, BucketRollup, BucketValueColumn, } from "./measures.js";
|
|
21
|
+
export { buildMetricBuckets, toMetricBucketRows } from "./buckets.js";
|
|
22
|
+
export type { MetricBucketOptions, MetricBucketRow } from "./buckets.js";
|
|
23
|
+
export { evaluateBucketMeasure } from "./evaluate.js";
|
|
24
|
+
export type { BucketEventLike, EvaluateBucketOptions } from "./evaluate.js";
|
|
25
|
+
export { BASELINE_PRECISION, computeBaseline } from "./baseline.js";
|
|
26
|
+
export type { BaselineRow } from "./baseline.js";
|
|
27
|
+
export { MIN_SPREAD_FRACTION, MOVERS_PRECISION, computeMover, rankMovers, referenceSpread, rollupWindow, } from "./movers.js";
|
|
28
|
+
export type { MoverInput, MoverRow } from "./movers.js";
|
|
29
|
+
export { MAD_TO_SIGMA, ROBUST_Z_EPSILON, leastSquaresSlope, mean, median, medianAbsoluteDeviation, quantile, relativeChange, robustZ, round, sum, trendOf, } from "./stats.js";
|
|
30
|
+
export { DAY_MS, DEFAULT_BASELINE_WINDOW_DAYS, DEFAULT_MOVERS_RANGE_DAYS, MAX_BASELINE_WINDOW_DAYS, floorToBucket, inWindow, resolveBaselineWindow, resolveMoversWindows, spanningWindow, } from "./windows.js";
|
|
31
|
+
export type { ResolvedWindow } from "./windows.js";
|
|
32
|
+
export { ANOMALY_MAX_CONTRIBUTOR_SCANS, ANOMALY_MIN_TRAILING, ANOMALY_PRECISION, ANOMALY_TRAILING_BUCKETS, DEFAULT_ANOMALY_SENSITIVITY, MAX_ANOMALY_SENSITIVITY, MIN_ANOMALY_SENSITIVITY, attributeContributor, clampSensitivity, contributorDimensionFor, contributorWindows, detectAnomalies, inContributorWindow, } from "./anomalies.js";
|
|
33
|
+
export type { AnomalyContributor, AnomalyKind, AnomalyRow, AnomalyWindow, DetectAnomaliesOptions, } from "./anomalies.js";
|
|
34
|
+
export { CUSUM_DECISION_FACTOR, CUSUM_SLACK, cusumChangePoints, rollingRobustStats, } from "./changepoint.js";
|
|
35
|
+
export type { ChangePoint, RollingRobustPoint } from "./changepoint.js";
|
|
36
|
+
export { byBucketThenDimension } from "./buckets.js";
|
|
37
|
+
export { BUCKET_SPLIT_COLUMNS, isBucketSplitDimension } from "./measures.js";
|
|
38
|
+
export type { BucketSplitDimension } from "./measures.js";
|
|
39
|
+
export { BUCKET_MEASURE_VARIANTS, bucketVariantFor, resolveBucketMeasure } from "./measures.js";
|
|
40
|
+
export type { BucketVariant } from "./measures.js";
|
|
41
|
+
export { SIGNIFICANCE_ALPHA, SIGNIFICANCE_PRECISION, Z_80, binomialCdf, computeSignificance, newcombeDifferenceInterval, normalCdf, normalTwoSidedP, poissonRateTest, regularizedIncompleteBeta, studentTCritical, studentTwoSidedP, twoProportionTest, welchTest, wilsonBounds, } from "./significance.js";
|
|
42
|
+
export type { SignificanceArm, SignificanceInput, SignificanceRow, SignificanceTest, } from "./significance.js";
|
|
43
|
+
export { HEALTH_DEFAULT_SCENES, HEALTH_DEFAULT_WEIGHTS, HEALTH_DEFAULT_WINDOW_DAYS, HEALTH_FACTORS, HEALTH_FACTOR_IDS, HEALTH_MAX_SCENES, HEALTH_MIN_SESSIONS, HEALTH_PRECISION, HEALTH_Z_SPAN, computeSceneHealth, normaliseFactor, rankSceneHealth, resolveHealthWindows, resolveWeights, } from "./health.js";
|
|
44
|
+
export type { HealthFactorInput, HealthFactorRow, HealthFactorSpec, HealthSeries, SceneHealthRow, } from "./health.js";
|
|
45
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/insights/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **Insight primitives** — `baseline` and `movers` (ADR 0051 §4, sketch §D).
|
|
3
|
+
*
|
|
4
|
+
* The shape of this directory is the design:
|
|
5
|
+
*
|
|
6
|
+
* - `measures.ts` declares, per comparable registry metric, how its primary
|
|
7
|
+
* column is reproduced as a per-bucket series;
|
|
8
|
+
* - `buckets.ts` renders exactly one generic, dialect-agnostic query from that
|
|
9
|
+
* declaration (parity-tested on DuckDB, ClickHouse, Postgres and SQL Server);
|
|
10
|
+
* - `stats.ts`, `baseline.ts`, `movers.ts` and `windows.ts` are **pure
|
|
11
|
+
* TypeScript** over the rows that query returns — no SQL, no I/O, no dialect.
|
|
12
|
+
*
|
|
13
|
+
* Statistics never run in SQL. Five engines disagree about `quantile`, `median`
|
|
14
|
+
* and `stddev`, and an insight that changes when a self-hoster switches store is
|
|
15
|
+
* not an insight. `anomalies`, `significance` and `scene_health` (ADR 0051 §4)
|
|
16
|
+
* are designed to slot in here the same way: a new pure module over the same
|
|
17
|
+
* bucket series, with no new per-dialect SQL.
|
|
18
|
+
*/
|
|
19
|
+
export { BUCKETABLE_METRIC_IDS, BUCKET_MEASURES, BUCKET_SECONDS, MOVERS_DEFAULT_METRICS, MOVERS_MAX_METRICS, bucketMeasureFor, isBucketGrain, isBucketableMetric, } from "./measures.js";
|
|
20
|
+
export { buildMetricBuckets, toMetricBucketRows } from "./buckets.js";
|
|
21
|
+
export { evaluateBucketMeasure } from "./evaluate.js";
|
|
22
|
+
export { BASELINE_PRECISION, computeBaseline } from "./baseline.js";
|
|
23
|
+
export { MIN_SPREAD_FRACTION, MOVERS_PRECISION, computeMover, rankMovers, referenceSpread, rollupWindow, } from "./movers.js";
|
|
24
|
+
export { MAD_TO_SIGMA, ROBUST_Z_EPSILON, leastSquaresSlope, mean, median, medianAbsoluteDeviation, quantile, relativeChange, robustZ, round, sum, trendOf, } from "./stats.js";
|
|
25
|
+
export { DAY_MS, DEFAULT_BASELINE_WINDOW_DAYS, DEFAULT_MOVERS_RANGE_DAYS, MAX_BASELINE_WINDOW_DAYS, floorToBucket, inWindow, resolveBaselineWindow, resolveMoversWindows, spanningWindow, } from "./windows.js";
|
|
26
|
+
// --- anomalies (#306) ------------------------------------------------------
|
|
27
|
+
// The third insight primitive: which *bucket* was abnormal, in what way, and
|
|
28
|
+
// which dimension value inside the metric accounts for it. Two pure modules over
|
|
29
|
+
// the same bucket series — `changepoint.ts` holds the generic algorithms,
|
|
30
|
+
// `anomalies.ts` the primitive itself. The only new store-facing surface is the
|
|
31
|
+
// optional `groupBy` on `buildMetricBuckets`, exported above.
|
|
32
|
+
export { ANOMALY_MAX_CONTRIBUTOR_SCANS, ANOMALY_MIN_TRAILING, ANOMALY_PRECISION, ANOMALY_TRAILING_BUCKETS, DEFAULT_ANOMALY_SENSITIVITY, MAX_ANOMALY_SENSITIVITY, MIN_ANOMALY_SENSITIVITY, attributeContributor, clampSensitivity, contributorDimensionFor, contributorWindows, detectAnomalies, inContributorWindow, } from "./anomalies.js";
|
|
33
|
+
export { CUSUM_DECISION_FACTOR, CUSUM_SLACK, cusumChangePoints, rollingRobustStats, } from "./changepoint.js";
|
|
34
|
+
export { byBucketThenDimension } from "./buckets.js";
|
|
35
|
+
export { BUCKET_SPLIT_COLUMNS, isBucketSplitDimension } from "./measures.js";
|
|
36
|
+
// --- significance / scene health (#307) ------------------------------------
|
|
37
|
+
export { BUCKET_MEASURE_VARIANTS, bucketVariantFor, resolveBucketMeasure } from "./measures.js";
|
|
38
|
+
export { SIGNIFICANCE_ALPHA, SIGNIFICANCE_PRECISION, Z_80, binomialCdf, computeSignificance, newcombeDifferenceInterval, normalCdf, normalTwoSidedP, poissonRateTest, regularizedIncompleteBeta, studentTCritical, studentTwoSidedP, twoProportionTest, welchTest, wilsonBounds, } from "./significance.js";
|
|
39
|
+
export { HEALTH_DEFAULT_SCENES, HEALTH_DEFAULT_WEIGHTS, HEALTH_DEFAULT_WINDOW_DAYS, HEALTH_FACTORS, HEALTH_FACTOR_IDS, HEALTH_MAX_SCENES, HEALTH_MIN_SESSIONS, HEALTH_PRECISION, HEALTH_Z_SPAN, computeSceneHealth, normaliseFactor, rankSceneHealth, resolveHealthWindows, resolveWeights, } from "./health.js";
|
|
40
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/insights/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,cAAc,EACd,sBAAsB,EACtB,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACb,kBAAkB,GACnB,MAAM,eAAe,CAAC;AAUvB,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAGtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAGtD,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGpE,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,eAAe,EACf,YAAY,GACb,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,IAAI,EACJ,MAAM,EACN,uBAAuB,EACvB,QAAQ,EACR,cAAc,EACd,OAAO,EACP,KAAK,EACL,GAAG,EACH,OAAO,GACR,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,MAAM,EACN,4BAA4B,EAC5B,yBAAyB,EACzB,wBAAwB,EACxB,aAAa,EACb,QAAQ,EACR,qBAAqB,EACrB,oBAAoB,EACpB,cAAc,GACf,MAAM,cAAc,CAAC;AAGtB,8EAA8E;AAC9E,6EAA6E;AAC7E,iFAAiF;AACjF,0EAA0E;AAC1E,gFAAgF;AAChF,8DAA8D;AAC9D,OAAO,EACL,6BAA6B,EAC7B,oBAAoB,EACpB,iBAAiB,EACjB,wBAAwB,EACxB,2BAA2B,EAC3B,uBAAuB,EACvB,uBAAuB,EACvB,oBAAoB,EACpB,gBAAgB,EAChB,uBAAuB,EACvB,kBAAkB,EAClB,eAAe,EACf,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AASxB,OAAO,EACL,qBAAqB,EACrB,WAAW,EACX,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAE7E,8EAA8E;AAE9E,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAGhG,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,IAAI,EACJ,WAAW,EACX,mBAAmB,EACnB,0BAA0B,EAC1B,SAAS,EACT,eAAe,EACf,eAAe,EACf,yBAAyB,EACzB,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,SAAS,EACT,YAAY,GACb,MAAM,mBAAmB,CAAC;AAQ3B,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,0BAA0B,EAC1B,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,cAAc,GACf,MAAM,aAAa,CAAC"}
|