@growth-labs/ops-digest 0.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/CHANGELOG.md +7 -0
- package/README.md +129 -0
- package/SPEC.md +25 -0
- package/dist/anomaly.d.ts +3 -0
- package/dist/anomaly.d.ts.map +1 -0
- package/dist/anomaly.js +50 -0
- package/dist/anomaly.js.map +1 -0
- package/dist/engine.d.ts +4 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +149 -0
- package/dist/engine.js.map +1 -0
- package/dist/handlers/common.d.ts +12 -0
- package/dist/handlers/common.d.ts.map +1 -0
- package/dist/handlers/common.js +68 -0
- package/dist/handlers/common.js.map +1 -0
- package/dist/handlers/composite-ratio.d.ts +7 -0
- package/dist/handlers/composite-ratio.d.ts.map +1 -0
- package/dist/handlers/composite-ratio.js +9 -0
- package/dist/handlers/composite-ratio.js.map +1 -0
- package/dist/handlers/d1-row-count-by-event.d.ts +7 -0
- package/dist/handlers/d1-row-count-by-event.d.ts.map +1 -0
- package/dist/handlers/d1-row-count-by-event.js +21 -0
- package/dist/handlers/d1-row-count-by-event.js.map +1 -0
- package/dist/handlers/d1-row-count.d.ts +7 -0
- package/dist/handlers/d1-row-count.d.ts.map +1 -0
- package/dist/handlers/d1-row-count.js +12 -0
- package/dist/handlers/d1-row-count.js.map +1 -0
- package/dist/handlers/index.d.ts +29 -0
- package/dist/handlers/index.d.ts.map +1 -0
- package/dist/handlers/index.js +60 -0
- package/dist/handlers/index.js.map +1 -0
- package/dist/handlers/wae-avg-double.d.ts +7 -0
- package/dist/handlers/wae-avg-double.d.ts.map +1 -0
- package/dist/handlers/wae-avg-double.js +12 -0
- package/dist/handlers/wae-avg-double.js.map +1 -0
- package/dist/handlers/wae-event-count-by-blob.d.ts +7 -0
- package/dist/handlers/wae-event-count-by-blob.d.ts.map +1 -0
- package/dist/handlers/wae-event-count-by-blob.js +14 -0
- package/dist/handlers/wae-event-count-by-blob.js.map +1 -0
- package/dist/handlers/wae-event-count.d.ts +7 -0
- package/dist/handlers/wae-event-count.d.ts.map +1 -0
- package/dist/handlers/wae-event-count.js +11 -0
- package/dist/handlers/wae-event-count.js.map +1 -0
- package/dist/handlers/wae-top-n-by-blob.d.ts +7 -0
- package/dist/handlers/wae-top-n-by-blob.d.ts.map +1 -0
- package/dist/handlers/wae-top-n-by-blob.js +18 -0
- package/dist/handlers/wae-top-n-by-blob.js.map +1 -0
- package/dist/handlers/wae-unique-count.d.ts +7 -0
- package/dist/handlers/wae-unique-count.d.ts.map +1 -0
- package/dist/handlers/wae-unique-count.js +12 -0
- package/dist/handlers/wae-unique-count.js.map +1 -0
- package/dist/idempotency.d.ts +6 -0
- package/dist/idempotency.d.ts.map +1 -0
- package/dist/idempotency.js +25 -0
- package/dist/idempotency.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/render.d.ts +4 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +73 -0
- package/dist/render.js.map +1 -0
- package/dist/schema.d.ts +264 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +102 -0
- package/dist/schema.js.map +1 -0
- package/dist/types.d.ts +146 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/window.d.ts +7 -0
- package/dist/window.d.ts.map +1 -0
- package/dist/window.js +28 -0
- package/dist/window.js.map +1 -0
- package/package.json +40 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { computeCompositeRatio } from './composite-ratio.js';
|
|
2
|
+
import { computeD1RowCount } from './d1-row-count.js';
|
|
3
|
+
import { computeD1RowCountByEvent } from './d1-row-count-by-event.js';
|
|
4
|
+
import { computeWaeAvgDouble } from './wae-avg-double.js';
|
|
5
|
+
import { computeWaeEventCount } from './wae-event-count.js';
|
|
6
|
+
import { computeWaeEventCountByBlob } from './wae-event-count-by-blob.js';
|
|
7
|
+
import { computeWaeTopNByBlob } from './wae-top-n-by-blob.js';
|
|
8
|
+
import { computeWaeUniqueCount } from './wae-unique-count.js';
|
|
9
|
+
export const handlerRegistry = {
|
|
10
|
+
'd1.row-count': {
|
|
11
|
+
description: 'Counts D1 rows in the digest window using a created/processed timestamp column.',
|
|
12
|
+
},
|
|
13
|
+
'd1.row-count-by-event': {
|
|
14
|
+
description: 'Counts D1 rows whose event column matches one of the configured event names.',
|
|
15
|
+
},
|
|
16
|
+
'wae.event-count': {
|
|
17
|
+
description: 'Counts WAE rows for one event name and optional realm/site filter.',
|
|
18
|
+
},
|
|
19
|
+
'wae.event-count-by-blob': {
|
|
20
|
+
description: 'Counts one WAE event grouped by a blob field.',
|
|
21
|
+
},
|
|
22
|
+
'wae.unique-count': {
|
|
23
|
+
description: 'Counts distinct values in a WAE blob field.',
|
|
24
|
+
},
|
|
25
|
+
'wae.avg-double': {
|
|
26
|
+
description: 'Averages a WAE double field.',
|
|
27
|
+
},
|
|
28
|
+
'wae.top-n-by-blob': {
|
|
29
|
+
description: 'Returns the top N WAE blob values by event count.',
|
|
30
|
+
},
|
|
31
|
+
'composite.ratio': {
|
|
32
|
+
description: 'Computes numerator / denominator using nested metric definitions.',
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
export async function computeMetric(metric, window, context) {
|
|
36
|
+
switch (metric.handler) {
|
|
37
|
+
case 'd1.row-count':
|
|
38
|
+
return computeD1RowCount(metric, window, context);
|
|
39
|
+
case 'd1.row-count-by-event':
|
|
40
|
+
return computeD1RowCountByEvent(metric, window, context);
|
|
41
|
+
case 'wae.event-count':
|
|
42
|
+
return computeWaeEventCount(metric, window, context);
|
|
43
|
+
case 'wae.event-count-by-blob':
|
|
44
|
+
return computeWaeEventCountByBlob(metric, window, context);
|
|
45
|
+
case 'wae.unique-count':
|
|
46
|
+
return computeWaeUniqueCount(metric, window, context);
|
|
47
|
+
case 'wae.avg-double':
|
|
48
|
+
return computeWaeAvgDouble(metric, window, context);
|
|
49
|
+
case 'wae.top-n-by-blob':
|
|
50
|
+
return computeWaeTopNByBlob(metric, window, context);
|
|
51
|
+
case 'composite.ratio':
|
|
52
|
+
return computeCompositeRatio(metric, window, context, computeMetric);
|
|
53
|
+
default:
|
|
54
|
+
return assertNever(metric);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function assertNever(value) {
|
|
58
|
+
throw new Error(`ops-digest: unsupported metric handler "${String(value)}"`);
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/handlers/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAA;AACzE,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAE7D,MAAM,CAAC,MAAM,eAAe,GAAG;IAC9B,cAAc,EAAE;QACf,WAAW,EAAE,iFAAiF;KAC9F;IACD,uBAAuB,EAAE;QACxB,WAAW,EAAE,8EAA8E;KAC3F;IACD,iBAAiB,EAAE;QAClB,WAAW,EAAE,oEAAoE;KACjF;IACD,yBAAyB,EAAE;QAC1B,WAAW,EAAE,+CAA+C;KAC5D;IACD,kBAAkB,EAAE;QACnB,WAAW,EAAE,6CAA6C;KAC1D;IACD,gBAAgB,EAAE;QACjB,WAAW,EAAE,8BAA8B;KAC3C;IACD,mBAAmB,EAAE;QACpB,WAAW,EAAE,mDAAmD;KAChE;IACD,iBAAiB,EAAE;QAClB,WAAW,EAAE,mEAAmE;KAChF;CACQ,CAAA;AAEV,MAAM,CAAC,KAAK,UAAU,aAAa,CAClC,MAAwB,EACxB,MAA4B,EAC5B,OAAuB;IAEvB,QAAQ,MAAM,CAAC,OAAO,EAAE,CAAC;QACxB,KAAK,cAAc;YAClB,OAAO,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA;QAClD,KAAK,uBAAuB;YAC3B,OAAO,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA;QACzD,KAAK,iBAAiB;YACrB,OAAO,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA;QACrD,KAAK,yBAAyB;YAC7B,OAAO,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA;QAC3D,KAAK,kBAAkB;YACtB,OAAO,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA;QACtD,KAAK,gBAAgB;YACpB,OAAO,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA;QACpD,KAAK,mBAAmB;YACvB,OAAO,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA;QACrD,KAAK,iBAAiB;YACrB,OAAO,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC,CAAA;QACrE;YACC,OAAO,WAAW,CAAC,MAAM,CAAC,CAAA;IAC5B,CAAC;AACF,CAAC;AAED,SAAS,WAAW,CAAC,KAAY;IAChC,MAAM,IAAI,KAAK,CAAC,2CAA2C,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;AAC7E,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { HandlerContext, MetricDefinition, ResolvedDigestWindow } from '../types.js';
|
|
2
|
+
type WaeAvgDoubleMetric = Extract<MetricDefinition, {
|
|
3
|
+
handler: 'wae.avg-double';
|
|
4
|
+
}>;
|
|
5
|
+
export declare function computeWaeAvgDouble(metric: WaeAvgDoubleMetric, window: ResolvedDigestWindow, context: HandlerContext): Promise<number>;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=wae-avg-double.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wae-avg-double.d.ts","sourceRoot":"","sources":["../../src/handlers/wae-avg-double.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AASzF,KAAK,kBAAkB,GAAG,OAAO,CAAC,gBAAgB,EAAE;IAAE,OAAO,EAAE,gBAAgB,CAAA;CAAE,CAAC,CAAA;AAElF,wBAAsB,mBAAmB,CACxC,MAAM,EAAE,kBAAkB,EAC1B,MAAM,EAAE,oBAAoB,EAC5B,OAAO,EAAE,cAAc,GACrB,OAAO,CAAC,MAAM,CAAC,CAajB"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { assertSqlIdentifier, numericResult, queryWaeRows, resolveWaeSource, waeConditions, } from './common.js';
|
|
2
|
+
export async function computeWaeAvgDouble(metric, window, context) {
|
|
3
|
+
assertSqlIdentifier(metric.doubleField);
|
|
4
|
+
const source = resolveWaeSource(context, metric.source);
|
|
5
|
+
const rows = await queryWaeRows(source, context, [
|
|
6
|
+
`SELECT avg(${metric.doubleField}) AS c`,
|
|
7
|
+
`FROM ${source.dataset}`,
|
|
8
|
+
`WHERE ${waeConditions(source, window, metric.eventName).join(' AND ')}`,
|
|
9
|
+
].join(' '));
|
|
10
|
+
return numericResult(rows[0]);
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=wae-avg-double.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wae-avg-double.js","sourceRoot":"","sources":["../../src/handlers/wae-avg-double.ts"],"names":[],"mappings":"AACA,OAAO,EACN,mBAAmB,EACnB,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,aAAa,GACb,MAAM,aAAa,CAAA;AAIpB,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACxC,MAA0B,EAC1B,MAA4B,EAC5B,OAAuB;IAEvB,mBAAmB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;IACvC,MAAM,MAAM,GAAG,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;IACvD,MAAM,IAAI,GAAG,MAAM,YAAY,CAC9B,MAAM,EACN,OAAO,EACP;QACC,cAAc,MAAM,CAAC,WAAW,QAAQ;QACxC,QAAQ,MAAM,CAAC,OAAO,EAAE;QACxB,SAAS,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;KACxE,CAAC,IAAI,CAAC,GAAG,CAAC,CACX,CAAA;IACD,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;AAC9B,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { GroupedMetricResult, HandlerContext, MetricDefinition, ResolvedDigestWindow } from '../types.js';
|
|
2
|
+
type WaeEventCountByBlobMetric = Extract<MetricDefinition, {
|
|
3
|
+
handler: 'wae.event-count-by-blob';
|
|
4
|
+
}>;
|
|
5
|
+
export declare function computeWaeEventCountByBlob(metric: WaeEventCountByBlobMetric, window: ResolvedDigestWindow, context: HandlerContext): Promise<GroupedMetricResult>;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=wae-event-count-by-blob.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wae-event-count-by-blob.d.ts","sourceRoot":"","sources":["../../src/handlers/wae-event-count-by-blob.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,oBAAoB,EACpB,MAAM,aAAa,CAAA;AASpB,KAAK,yBAAyB,GAAG,OAAO,CAAC,gBAAgB,EAAE;IAAE,OAAO,EAAE,yBAAyB,CAAA;CAAE,CAAC,CAAA;AAElG,wBAAsB,0BAA0B,CAC/C,MAAM,EAAE,yBAAyB,EACjC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,EAAE,cAAc,GACrB,OAAO,CAAC,mBAAmB,CAAC,CAiB9B"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { assertSqlIdentifier, numericResult, queryWaeRows, resolveWaeSource, waeConditions, } from './common.js';
|
|
2
|
+
export async function computeWaeEventCountByBlob(metric, window, context) {
|
|
3
|
+
assertSqlIdentifier(metric.groupBy);
|
|
4
|
+
const source = resolveWaeSource(context, metric.source);
|
|
5
|
+
const rows = await queryWaeRows(source, context, [
|
|
6
|
+
`SELECT ${metric.groupBy} AS group, count() AS c`,
|
|
7
|
+
`FROM ${source.dataset}`,
|
|
8
|
+
`WHERE ${waeConditions(source, window, metric.eventName).join(' AND ')}`,
|
|
9
|
+
`GROUP BY ${metric.groupBy}`,
|
|
10
|
+
'ORDER BY c DESC',
|
|
11
|
+
].join(' '));
|
|
12
|
+
return Object.fromEntries(rows.map((row) => [String(row.group ?? ''), numericResult(row)]));
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=wae-event-count-by-blob.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wae-event-count-by-blob.js","sourceRoot":"","sources":["../../src/handlers/wae-event-count-by-blob.ts"],"names":[],"mappings":"AAMA,OAAO,EACN,mBAAmB,EACnB,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,aAAa,GACb,MAAM,aAAa,CAAA;AAIpB,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC/C,MAAiC,EACjC,MAA4B,EAC5B,OAAuB;IAEvB,mBAAmB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACnC,MAAM,MAAM,GAAG,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;IACvD,MAAM,IAAI,GAAG,MAAM,YAAY,CAC9B,MAAM,EACN,OAAO,EACP;QACC,UAAU,MAAM,CAAC,OAAO,yBAAyB;QACjD,QAAQ,MAAM,CAAC,OAAO,EAAE;QACxB,SAAS,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;QACxE,YAAY,MAAM,CAAC,OAAO,EAAE;QAC5B,iBAAiB;KACjB,CAAC,IAAI,CAAC,GAAG,CAAC,CACX,CAAA;IACD,OAAO,MAAM,CAAC,WAAW,CACxB,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,aAAa,CAAC,GAA8B,CAAC,CAAC,CAAC,CAC3F,CAAA;AACF,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { HandlerContext, MetricDefinition, ResolvedDigestWindow } from '../types.js';
|
|
2
|
+
type WaeEventCountMetric = Extract<MetricDefinition, {
|
|
3
|
+
handler: 'wae.event-count';
|
|
4
|
+
}>;
|
|
5
|
+
export declare function computeWaeEventCount(metric: WaeEventCountMetric, window: ResolvedDigestWindow, context: HandlerContext): Promise<number>;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=wae-event-count.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wae-event-count.d.ts","sourceRoot":"","sources":["../../src/handlers/wae-event-count.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAGzF,KAAK,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,EAAE;IAAE,OAAO,EAAE,iBAAiB,CAAA;CAAE,CAAC,CAAA;AAEpF,wBAAsB,oBAAoB,CACzC,MAAM,EAAE,mBAAmB,EAC3B,MAAM,EAAE,oBAAoB,EAC5B,OAAO,EAAE,cAAc,GACrB,OAAO,CAAC,MAAM,CAAC,CAYjB"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { numericResult, queryWaeRows, resolveWaeSource, waeConditions } from './common.js';
|
|
2
|
+
export async function computeWaeEventCount(metric, window, context) {
|
|
3
|
+
const source = resolveWaeSource(context, metric.source);
|
|
4
|
+
const rows = await queryWaeRows(source, context, [
|
|
5
|
+
'SELECT count() AS c',
|
|
6
|
+
`FROM ${source.dataset}`,
|
|
7
|
+
`WHERE ${waeConditions(source, window, metric.eventName).join(' AND ')}`,
|
|
8
|
+
].join(' '));
|
|
9
|
+
return numericResult(rows[0]);
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=wae-event-count.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wae-event-count.js","sourceRoot":"","sources":["../../src/handlers/wae-event-count.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAI1F,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACzC,MAA2B,EAC3B,MAA4B,EAC5B,OAAuB;IAEvB,MAAM,MAAM,GAAG,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;IACvD,MAAM,IAAI,GAAG,MAAM,YAAY,CAC9B,MAAM,EACN,OAAO,EACP;QACC,qBAAqB;QACrB,QAAQ,MAAM,CAAC,OAAO,EAAE;QACxB,SAAS,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;KACxE,CAAC,IAAI,CAAC,GAAG,CAAC,CACX,CAAA;IACD,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;AAC9B,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { HandlerContext, MetricDefinition, ResolvedDigestWindow, TopNResult } from '../types.js';
|
|
2
|
+
type WaeTopNByBlobMetric = Extract<MetricDefinition, {
|
|
3
|
+
handler: 'wae.top-n-by-blob';
|
|
4
|
+
}>;
|
|
5
|
+
export declare function computeWaeTopNByBlob(metric: WaeTopNByBlobMetric, window: ResolvedDigestWindow, context: HandlerContext): Promise<TopNResult>;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=wae-top-n-by-blob.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wae-top-n-by-blob.d.ts","sourceRoot":"","sources":["../../src/handlers/wae-top-n-by-blob.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,cAAc,EACd,gBAAgB,EAChB,oBAAoB,EACpB,UAAU,EACV,MAAM,aAAa,CAAA;AASpB,KAAK,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,EAAE;IAAE,OAAO,EAAE,mBAAmB,CAAA;CAAE,CAAC,CAAA;AAEtF,wBAAsB,oBAAoB,CACzC,MAAM,EAAE,mBAAmB,EAC3B,MAAM,EAAE,oBAAoB,EAC5B,OAAO,EAAE,cAAc,GACrB,OAAO,CAAC,UAAU,CAAC,CAmBrB"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { assertSqlIdentifier, numericResult, queryWaeRows, resolveWaeSource, waeConditions, } from './common.js';
|
|
2
|
+
export async function computeWaeTopNByBlob(metric, window, context) {
|
|
3
|
+
assertSqlIdentifier(metric.groupBy);
|
|
4
|
+
const source = resolveWaeSource(context, metric.source);
|
|
5
|
+
const rows = await queryWaeRows(source, context, [
|
|
6
|
+
`SELECT ${metric.groupBy} AS group, count() AS c`,
|
|
7
|
+
`FROM ${source.dataset}`,
|
|
8
|
+
`WHERE ${waeConditions(source, window, metric.eventName).join(' AND ')}`,
|
|
9
|
+
`GROUP BY ${metric.groupBy}`,
|
|
10
|
+
'ORDER BY c DESC',
|
|
11
|
+
`LIMIT ${metric.limit}`,
|
|
12
|
+
].join(' '));
|
|
13
|
+
return rows.map((row) => ({
|
|
14
|
+
group: String(row.group ?? ''),
|
|
15
|
+
count: numericResult(row),
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=wae-top-n-by-blob.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wae-top-n-by-blob.js","sourceRoot":"","sources":["../../src/handlers/wae-top-n-by-blob.ts"],"names":[],"mappings":"AAMA,OAAO,EACN,mBAAmB,EACnB,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,aAAa,GACb,MAAM,aAAa,CAAA;AAIpB,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACzC,MAA2B,EAC3B,MAA4B,EAC5B,OAAuB;IAEvB,mBAAmB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACnC,MAAM,MAAM,GAAG,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;IACvD,MAAM,IAAI,GAAG,MAAM,YAAY,CAC9B,MAAM,EACN,OAAO,EACP;QACC,UAAU,MAAM,CAAC,OAAO,yBAAyB;QACjD,QAAQ,MAAM,CAAC,OAAO,EAAE;QACxB,SAAS,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;QACxE,YAAY,MAAM,CAAC,OAAO,EAAE;QAC5B,iBAAiB;QACjB,SAAS,MAAM,CAAC,KAAK,EAAE;KACvB,CAAC,IAAI,CAAC,GAAG,CAAC,CACX,CAAA;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACzB,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;QAC9B,KAAK,EAAE,aAAa,CAAC,GAA8B,CAAC;KACpD,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { HandlerContext, MetricDefinition, ResolvedDigestWindow } from '../types.js';
|
|
2
|
+
type WaeUniqueCountMetric = Extract<MetricDefinition, {
|
|
3
|
+
handler: 'wae.unique-count';
|
|
4
|
+
}>;
|
|
5
|
+
export declare function computeWaeUniqueCount(metric: WaeUniqueCountMetric, window: ResolvedDigestWindow, context: HandlerContext): Promise<number>;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=wae-unique-count.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wae-unique-count.d.ts","sourceRoot":"","sources":["../../src/handlers/wae-unique-count.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AASzF,KAAK,oBAAoB,GAAG,OAAO,CAAC,gBAAgB,EAAE;IAAE,OAAO,EAAE,kBAAkB,CAAA;CAAE,CAAC,CAAA;AAEtF,wBAAsB,qBAAqB,CAC1C,MAAM,EAAE,oBAAoB,EAC5B,MAAM,EAAE,oBAAoB,EAC5B,OAAO,EAAE,cAAc,GACrB,OAAO,CAAC,MAAM,CAAC,CAajB"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { assertSqlIdentifier, numericResult, queryWaeRows, resolveWaeSource, waeConditions, } from './common.js';
|
|
2
|
+
export async function computeWaeUniqueCount(metric, window, context) {
|
|
3
|
+
assertSqlIdentifier(metric.uniqueBlob);
|
|
4
|
+
const source = resolveWaeSource(context, metric.source);
|
|
5
|
+
const rows = await queryWaeRows(source, context, [
|
|
6
|
+
`SELECT count(distinct ${metric.uniqueBlob}) AS c`,
|
|
7
|
+
`FROM ${source.dataset}`,
|
|
8
|
+
`WHERE ${waeConditions(source, window, metric.eventName).join(' AND ')}`,
|
|
9
|
+
].join(' '));
|
|
10
|
+
return numericResult(rows[0]);
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=wae-unique-count.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wae-unique-count.js","sourceRoot":"","sources":["../../src/handlers/wae-unique-count.ts"],"names":[],"mappings":"AACA,OAAO,EACN,mBAAmB,EACnB,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,aAAa,GACb,MAAM,aAAa,CAAA;AAIpB,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAC1C,MAA4B,EAC5B,MAA4B,EAC5B,OAAuB;IAEvB,mBAAmB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;IACtC,MAAM,MAAM,GAAG,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;IACvD,MAAM,IAAI,GAAG,MAAM,YAAY,CAC9B,MAAM,EACN,OAAO,EACP;QACC,yBAAyB,MAAM,CAAC,UAAU,QAAQ;QAClD,QAAQ,MAAM,CAAC,OAAO,EAAE;QACxB,SAAS,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;KACxE,CAAC,IAAI,CAAC,GAAG,CAAC,CACX,CAAA;IACD,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;AAC9B,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type IdempotencyState = 'first-run' | 'duplicate' | 'changed';
|
|
2
|
+
export declare function checkIdempotency(kv: KVNamespace, digestName: string, windowStart: number, computedHash: string): Promise<IdempotencyState>;
|
|
3
|
+
export declare function writeIdempotencyMarker(kv: KVNamespace, digestName: string, windowStart: number, hash: string): Promise<void>;
|
|
4
|
+
export declare function formatIdempotencyKey(digestName: string, windowStart: number): string;
|
|
5
|
+
export declare function hashRenderedDigest(rendered: string): Promise<string>;
|
|
6
|
+
//# sourceMappingURL=idempotency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.d.ts","sourceRoot":"","sources":["../src/idempotency.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,WAAW,GAAG,SAAS,CAAA;AAEpE,wBAAsB,gBAAgB,CACrC,EAAE,EAAE,WAAW,EACf,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GAClB,OAAO,CAAC,gBAAgB,CAAC,CAK3B;AAED,wBAAsB,sBAAsB,CAC3C,EAAE,EAAE,WAAW,EACf,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAEpF;AAED,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAI1E"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const TTL_SECONDS = 7 * 24 * 60 * 60;
|
|
2
|
+
export async function checkIdempotency(kv, digestName, windowStart, computedHash) {
|
|
3
|
+
const key = formatIdempotencyKey(digestName, windowStart);
|
|
4
|
+
const existing = await kv.get(key);
|
|
5
|
+
if (existing === null)
|
|
6
|
+
return 'first-run';
|
|
7
|
+
return existing === computedHash ? 'duplicate' : 'changed';
|
|
8
|
+
}
|
|
9
|
+
export async function writeIdempotencyMarker(kv, digestName, windowStart, hash) {
|
|
10
|
+
await kv.put(formatIdempotencyKey(digestName, windowStart), hash, {
|
|
11
|
+
expirationTtl: TTL_SECONDS,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export function formatIdempotencyKey(digestName, windowStart) {
|
|
15
|
+
return `digest:${digestName}:${formatYYYYMMDD(windowStart)}`;
|
|
16
|
+
}
|
|
17
|
+
export async function hashRenderedDigest(rendered) {
|
|
18
|
+
const encoded = new TextEncoder().encode(rendered);
|
|
19
|
+
const digest = await crypto.subtle.digest('SHA-256', encoded);
|
|
20
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
21
|
+
}
|
|
22
|
+
function formatYYYYMMDD(ms) {
|
|
23
|
+
return new Date(ms).toISOString().slice(0, 10);
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=idempotency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.js","sourceRoot":"","sources":["../src/idempotency.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAA;AAIpC,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACrC,EAAe,EACf,UAAkB,EAClB,WAAmB,EACnB,YAAoB;IAEpB,MAAM,GAAG,GAAG,oBAAoB,CAAC,UAAU,EAAE,WAAW,CAAC,CAAA;IACzD,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAClC,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,WAAW,CAAA;IACzC,OAAO,QAAQ,KAAK,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAA;AAC3D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC3C,EAAe,EACf,UAAkB,EAClB,WAAmB,EACnB,IAAY;IAEZ,MAAM,EAAE,CAAC,GAAG,CAAC,oBAAoB,CAAC,UAAU,EAAE,WAAW,CAAC,EAAE,IAAI,EAAE;QACjE,aAAa,EAAE,WAAW;KAC1B,CAAC,CAAA;AACH,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,UAAkB,EAAE,WAAmB;IAC3E,OAAO,UAAU,UAAU,IAAI,cAAc,CAAC,WAAW,CAAC,EAAE,CAAA;AAC7D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,QAAgB;IACxD,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IAClD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;IAC7D,OAAO,CAAC,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AAC9F,CAAC;AAED,SAAS,cAAc,CAAC,EAAU;IACjC,OAAO,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;AAC/C,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { OpsDigestConfig, OpsDigestHandle } from './types.js';
|
|
2
|
+
export declare function opsDigest(config: OpsDigestConfig): OpsDigestHandle;
|
|
3
|
+
export { handlerRegistry } from './handlers/index.js';
|
|
4
|
+
export type { AnomalyConfig, AnomalyHit, DigestDefinition, DigestRunResult, GroupedMetricResult, MetricDefinition, MetricResult, MetricValue, NotifyConfig, OpsDigestConfig, OpsDigestEnv, OpsDigestHandle, PercentChange, ResolvedDigestWindow, TopNResult, WaeSourceConfig, } from './types.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAmB,eAAe,EAAgB,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjG,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,CAiClE;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACrD,YAAY,EACX,aAAa,EACb,UAAU,EACV,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,UAAU,EACV,eAAe,GACf,MAAM,YAAY,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { runDigest as runDigestDefinition } from './engine.js';
|
|
2
|
+
import { opsDigestConfigSchema } from './schema.js';
|
|
3
|
+
export function opsDigest(config) {
|
|
4
|
+
const parsed = opsDigestConfigSchema.parse(config);
|
|
5
|
+
return {
|
|
6
|
+
async scheduledHandler(event, env, ctx) {
|
|
7
|
+
const cron = event.cron;
|
|
8
|
+
const matchingDigest = parsed.digests.find((digest) => digest.schedule === cron);
|
|
9
|
+
if (!matchingDigest) {
|
|
10
|
+
console.warn('ops-digest: scheduled event with no matching digest', { cron });
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
await runDigestDefinition(matchingDigest, parsed, env, ctx);
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
console.error('ops-digest: scheduled digest failed', { digest: matchingDigest.name, err });
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
async runDigest(digestName, env, ctx) {
|
|
21
|
+
const definition = parsed.digests.find((digest) => digest.name === digestName);
|
|
22
|
+
if (!definition)
|
|
23
|
+
throw new Error(`ops-digest: digest "${digestName}" is not configured`);
|
|
24
|
+
return runDigestDefinition(definition, parsed, env, ctx);
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export { handlerRegistry } from './handlers/index.js';
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,IAAI,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAGnD,MAAM,UAAU,SAAS,CAAC,MAAuB;IAChD,MAAM,MAAM,GAAG,qBAAqB,CAAC,KAAK,CAAC,MAAM,CAAoB,CAAA;IAErE,OAAO;QACN,KAAK,CAAC,gBAAgB,CACrB,KAAqB,EACrB,GAAiB,EACjB,GAAqB;YAErB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAA;YACvB,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAA;YAChF,IAAI,CAAC,cAAc,EAAE,CAAC;gBACrB,OAAO,CAAC,IAAI,CAAC,qDAAqD,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;gBAC7E,OAAM;YACP,CAAC;YAED,IAAI,CAAC;gBACJ,MAAM,mBAAmB,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;YAC5D,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACd,OAAO,CAAC,KAAK,CAAC,qCAAqC,EAAE,EAAE,MAAM,EAAE,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAA;YAC3F,CAAC;QACF,CAAC;QAED,KAAK,CAAC,SAAS,CACd,UAAkB,EAClB,GAAiB,EACjB,GAAqB;YAErB,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,UAAU,CAAC,CAAA;YAC9E,IAAI,CAAC,UAAU;gBAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,UAAU,qBAAqB,CAAC,CAAA;YACxF,OAAO,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;QACzD,CAAC;KACD,CAAA;AACF,CAAC;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA"}
|
package/dist/render.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { MetricResult, MetricValue, ResolvedDigestWindow } from './types.js';
|
|
2
|
+
export declare function renderDigest(template: string, metrics: MetricResult[], brandLabel: string, window: ResolvedDigestWindow): string;
|
|
3
|
+
export declare function metricValueTotal(value: MetricValue): number;
|
|
4
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAEjF,wBAAgB,YAAY,CAC3B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,YAAY,EAAE,EACvB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,oBAAoB,GAC1B,MAAM,CAgCR;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAI3D"}
|
package/dist/render.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export function renderDigest(template, metrics, brandLabel, window) {
|
|
2
|
+
if (template !== 'standard') {
|
|
3
|
+
throw new Error(`ops-digest: unsupported render template "${template}"`);
|
|
4
|
+
}
|
|
5
|
+
const lines = [`*${brandLabel} - Daily Ops Digest · ${formatDate(window.startMs)}*`, ''];
|
|
6
|
+
for (const metric of metrics) {
|
|
7
|
+
const currentTotal = metricValueTotal(metric.current);
|
|
8
|
+
lines.push(`• ${metricLabel(metric.name)} ${formatNumber(currentTotal)} (7d avg ${formatNumber(metric.baseline)} · ${formatDelta(metric.delta)})`);
|
|
9
|
+
for (const nested of nestedMetricLines(metric.current)) {
|
|
10
|
+
lines.push(` - ${nested}`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
lines.push('');
|
|
14
|
+
const anomalies = metrics.filter((metric) => metric.anomaly);
|
|
15
|
+
if (anomalies.length === 0) {
|
|
16
|
+
lines.push('_No anomalies._');
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
for (const metric of anomalies) {
|
|
20
|
+
const anomaly = metric.anomaly;
|
|
21
|
+
if (!anomaly)
|
|
22
|
+
continue;
|
|
23
|
+
lines.push(`⚠️ ${metricLabel(metric.name)} ${anomaly.direction} ${formatDelta(metric.delta)} vs 7d avg (${formatNumber(anomaly.current)} vs ${formatNumber(anomaly.median)} typical) - investigate`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return lines.join('\n');
|
|
27
|
+
}
|
|
28
|
+
export function metricValueTotal(value) {
|
|
29
|
+
if (typeof value === 'number')
|
|
30
|
+
return value;
|
|
31
|
+
if (Array.isArray(value))
|
|
32
|
+
return value.reduce((sum, item) => sum + item.count, 0);
|
|
33
|
+
return Object.values(value).reduce((sum, item) => sum + item, 0);
|
|
34
|
+
}
|
|
35
|
+
function nestedMetricLines(value) {
|
|
36
|
+
if (typeof value === 'number')
|
|
37
|
+
return [];
|
|
38
|
+
if (Array.isArray(value))
|
|
39
|
+
return value.map((item) => `${item.group}: ${formatNumber(item.count)}`);
|
|
40
|
+
return Object.entries(value).map(([group, count]) => `${group}: ${formatNumber(count)}`);
|
|
41
|
+
}
|
|
42
|
+
function metricLabel(name) {
|
|
43
|
+
const base = name.endsWith('-by-provider') ? name.slice(0, -'-by-provider'.length) : name;
|
|
44
|
+
const special = {
|
|
45
|
+
signins: 'Sign-ins',
|
|
46
|
+
'write-for-us': 'Write-for-us',
|
|
47
|
+
};
|
|
48
|
+
if (special[base])
|
|
49
|
+
return special[base];
|
|
50
|
+
const words = base.split('-');
|
|
51
|
+
return words.map((word, index) => (index === 0 ? capitalize(word) : word)).join(' ');
|
|
52
|
+
}
|
|
53
|
+
function capitalize(word) {
|
|
54
|
+
if (word.length === 0)
|
|
55
|
+
return word;
|
|
56
|
+
return `${word[0].toUpperCase()}${word.slice(1)}`;
|
|
57
|
+
}
|
|
58
|
+
function formatDelta(delta) {
|
|
59
|
+
if (delta.direction === 'up')
|
|
60
|
+
return `+${delta.value}`;
|
|
61
|
+
if (delta.direction === 'down')
|
|
62
|
+
return `-${delta.value}`;
|
|
63
|
+
return delta.value;
|
|
64
|
+
}
|
|
65
|
+
function formatNumber(value) {
|
|
66
|
+
if (Number.isInteger(value))
|
|
67
|
+
return String(value);
|
|
68
|
+
return value.toFixed(2).replace(/\.?0+$/, '');
|
|
69
|
+
}
|
|
70
|
+
function formatDate(ms) {
|
|
71
|
+
return new Date(ms).toISOString().slice(0, 10);
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
|