elasticsearch-mcp-vsee-stage 0.5.13
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/LICENSE +21 -0
- package/README.md +304 -0
- package/build/config.d.ts +76 -0
- package/build/config.d.ts.map +1 -0
- package/build/config.js +63 -0
- package/build/config.js.map +1 -0
- package/build/elasticsearch/client.d.ts +26 -0
- package/build/elasticsearch/client.d.ts.map +1 -0
- package/build/elasticsearch/client.js +131 -0
- package/build/elasticsearch/client.js.map +1 -0
- package/build/errors/handlers.d.ts +44 -0
- package/build/errors/handlers.d.ts.map +1 -0
- package/build/errors/handlers.js +231 -0
- package/build/errors/handlers.js.map +1 -0
- package/build/index.d.ts +9 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +28 -0
- package/build/index.js.map +1 -0
- package/build/logger.d.ts +31 -0
- package/build/logger.d.ts.map +1 -0
- package/build/logger.js +81 -0
- package/build/logger.js.map +1 -0
- package/build/server.d.ts +23 -0
- package/build/server.d.ts.map +1 -0
- package/build/server.js +571 -0
- package/build/server.js.map +1 -0
- package/build/tools/find-entities-by-metric.d.ts +51 -0
- package/build/tools/find-entities-by-metric.d.ts.map +1 -0
- package/build/tools/find-entities-by-metric.js +375 -0
- package/build/tools/find-entities-by-metric.js.map +1 -0
- package/build/tools/get-index-fields.d.ts +26 -0
- package/build/tools/get-index-fields.d.ts.map +1 -0
- package/build/tools/get-index-fields.js +94 -0
- package/build/tools/get-index-fields.js.map +1 -0
- package/build/tools/get-platform-breakdown.d.ts +42 -0
- package/build/tools/get-platform-breakdown.d.ts.map +1 -0
- package/build/tools/get-platform-breakdown.js +305 -0
- package/build/tools/get-platform-breakdown.js.map +1 -0
- package/build/tools/get-rating-distribution.d.ts +48 -0
- package/build/tools/get-rating-distribution.d.ts.map +1 -0
- package/build/tools/get-rating-distribution.js +283 -0
- package/build/tools/get-rating-distribution.js.map +1 -0
- package/build/tools/get-subscription-breakdown.d.ts +34 -0
- package/build/tools/get-subscription-breakdown.d.ts.map +1 -0
- package/build/tools/get-subscription-breakdown.js +179 -0
- package/build/tools/get-subscription-breakdown.js.map +1 -0
- package/build/tools/get-usage-leaderboard.d.ts +38 -0
- package/build/tools/get-usage-leaderboard.d.ts.map +1 -0
- package/build/tools/get-usage-leaderboard.js +177 -0
- package/build/tools/get-usage-leaderboard.js.map +1 -0
- package/build/tools/get-usage-profile.d.ts +52 -0
- package/build/tools/get-usage-profile.d.ts.map +1 -0
- package/build/tools/get-usage-profile.js +273 -0
- package/build/tools/get-usage-profile.js.map +1 -0
- package/build/tools/get-visit-trends.d.ts +37 -0
- package/build/tools/get-visit-trends.d.ts.map +1 -0
- package/build/tools/get-visit-trends.js +289 -0
- package/build/tools/get-visit-trends.js.map +1 -0
- package/build/tools/index.d.ts +19 -0
- package/build/tools/index.d.ts.map +1 -0
- package/build/tools/index.js +22 -0
- package/build/tools/index.js.map +1 -0
- package/build/tools/top-change.d.ts +34 -0
- package/build/tools/top-change.d.ts.map +1 -0
- package/build/tools/top-change.js +262 -0
- package/build/tools/top-change.js.map +1 -0
- package/build/utils/aggregation-limits.d.ts +23 -0
- package/build/utils/aggregation-limits.d.ts.map +1 -0
- package/build/utils/aggregation-limits.js +32 -0
- package/build/utils/aggregation-limits.js.map +1 -0
- package/build/utils/date-math.d.ts +31 -0
- package/build/utils/date-math.d.ts.map +1 -0
- package/build/utils/date-math.js +116 -0
- package/build/utils/date-math.js.map +1 -0
- package/build/utils/field-constants.d.ts +23 -0
- package/build/utils/field-constants.d.ts.map +1 -0
- package/build/utils/field-constants.js +26 -0
- package/build/utils/field-constants.js.map +1 -0
- package/build/validation/schemas.d.ts +243 -0
- package/build/validation/schemas.d.ts.map +1 -0
- package/build/validation/schemas.js +151 -0
- package/build/validation/schemas.js.map +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AACzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAErE,YAAY,EACV,kBAAkB,EAClB,oBAAoB,EACpB,SAAS,GACV,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EACV,aAAa,EACb,eAAe,EACf,UAAU,GACX,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EACV,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,iCAAiC,CAAC;AAEzC,YAAY,EACV,wBAAwB,EACxB,uBAAuB,EACvB,eAAe,GAChB,MAAM,6BAA6B,CAAC;AAErC,YAAY,EACV,yBAAyB,EACzB,wBAAwB,EACxB,YAAY,GACb,MAAM,8BAA8B,CAAC;AAEtC,YAAY,EACV,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,EACd,gBAAgB,GACjB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EACV,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,wBAAwB,CAAC;AAEhC,YAAY,EACV,wBAAwB,EACxB,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,8BAA8B,CAAC;AAEtC,YAAY,EACV,uBAAuB,EACvB,yBAAyB,EACzB,kBAAkB,GACnB,MAAM,4BAA4B,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GetUsageLeaderboardTool = exports.FindEntitiesByMetricTool = exports.GetUsageProfileTool = exports.GetVisitTrendsTool = exports.GetRatingDistributionTool = exports.GetPlatformBreakdownTool = exports.PeriodSummaryTool = exports.TopChangeTool = exports.GetIndexFieldsTool = void 0;
|
|
4
|
+
var get_index_fields_js_1 = require("./get-index-fields.js");
|
|
5
|
+
Object.defineProperty(exports, "GetIndexFieldsTool", { enumerable: true, get: function () { return get_index_fields_js_1.GetIndexFieldsTool; } });
|
|
6
|
+
var top_change_js_1 = require("./top-change.js");
|
|
7
|
+
Object.defineProperty(exports, "TopChangeTool", { enumerable: true, get: function () { return top_change_js_1.TopChangeTool; } });
|
|
8
|
+
var get_subscription_breakdown_js_1 = require("./get-subscription-breakdown.js");
|
|
9
|
+
Object.defineProperty(exports, "PeriodSummaryTool", { enumerable: true, get: function () { return get_subscription_breakdown_js_1.PeriodSummaryTool; } });
|
|
10
|
+
var get_platform_breakdown_js_1 = require("./get-platform-breakdown.js");
|
|
11
|
+
Object.defineProperty(exports, "GetPlatformBreakdownTool", { enumerable: true, get: function () { return get_platform_breakdown_js_1.GetPlatformBreakdownTool; } });
|
|
12
|
+
var get_rating_distribution_js_1 = require("./get-rating-distribution.js");
|
|
13
|
+
Object.defineProperty(exports, "GetRatingDistributionTool", { enumerable: true, get: function () { return get_rating_distribution_js_1.GetRatingDistributionTool; } });
|
|
14
|
+
var get_visit_trends_js_1 = require("./get-visit-trends.js");
|
|
15
|
+
Object.defineProperty(exports, "GetVisitTrendsTool", { enumerable: true, get: function () { return get_visit_trends_js_1.GetVisitTrendsTool; } });
|
|
16
|
+
var get_usage_profile_js_1 = require("./get-usage-profile.js");
|
|
17
|
+
Object.defineProperty(exports, "GetUsageProfileTool", { enumerable: true, get: function () { return get_usage_profile_js_1.GetUsageProfileTool; } });
|
|
18
|
+
var find_entities_by_metric_js_1 = require("./find-entities-by-metric.js");
|
|
19
|
+
Object.defineProperty(exports, "FindEntitiesByMetricTool", { enumerable: true, get: function () { return find_entities_by_metric_js_1.FindEntitiesByMetricTool; } });
|
|
20
|
+
var get_usage_leaderboard_js_1 = require("./get-usage-leaderboard.js");
|
|
21
|
+
Object.defineProperty(exports, "GetUsageLeaderboardTool", { enumerable: true, get: function () { return get_usage_leaderboard_js_1.GetUsageLeaderboardTool; } });
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":";;;AAAA,6DAA2D;AAAlD,yHAAA,kBAAkB,OAAA;AAC3B,iDAAgD;AAAvC,8GAAA,aAAa,OAAA;AACtB,iFAAoE;AAA3D,kIAAA,iBAAiB,OAAA;AAC1B,yEAAuE;AAA9D,qIAAA,wBAAwB,OAAA;AACjC,2EAAyE;AAAhE,uIAAA,yBAAyB,OAAA;AAClC,6DAA2D;AAAlD,yHAAA,kBAAkB,OAAA;AAC3B,+DAA6D;AAApD,2HAAA,mBAAmB,OAAA;AAC5B,2EAAwE;AAA/D,sIAAA,wBAAwB,OAAA;AACjC,uEAAqE;AAA5D,mIAAA,uBAAuB,OAAA"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ElasticsearchManager } from '../elasticsearch/client.js';
|
|
2
|
+
import { Logger } from '../logger.js';
|
|
3
|
+
export interface TopChangeArgs {
|
|
4
|
+
groupBy: 'account' | 'group' | 'provider_platform' | 'patient_platform' | 'provider_platform_version' | 'patient_platform_version';
|
|
5
|
+
direction: 'increase' | 'decrease';
|
|
6
|
+
topN?: number;
|
|
7
|
+
startDate?: string;
|
|
8
|
+
endDate?: string;
|
|
9
|
+
subscription?: 'Enterprise' | 'Premium' | 'FVC' | 'BVC' | 'Plus';
|
|
10
|
+
}
|
|
11
|
+
export interface ChangeInfo {
|
|
12
|
+
item: string;
|
|
13
|
+
current_period_count: number;
|
|
14
|
+
previous_period_count: number;
|
|
15
|
+
change: number;
|
|
16
|
+
change_percent: number;
|
|
17
|
+
}
|
|
18
|
+
export interface TopChangeResult {
|
|
19
|
+
currentPeriodStart: string;
|
|
20
|
+
currentPeriodEnd: string;
|
|
21
|
+
previousPeriodStart: string;
|
|
22
|
+
previousPeriodEnd: string;
|
|
23
|
+
groupBy: 'account' | 'group' | 'provider_platform' | 'patient_platform' | 'provider_platform_version' | 'patient_platform_version';
|
|
24
|
+
direction: 'increase' | 'decrease';
|
|
25
|
+
items: ChangeInfo[];
|
|
26
|
+
total: number;
|
|
27
|
+
}
|
|
28
|
+
export declare class TopChangeTool {
|
|
29
|
+
private elasticsearch;
|
|
30
|
+
private logger;
|
|
31
|
+
constructor(elasticsearch: ElasticsearchManager, logger: Logger);
|
|
32
|
+
execute(args: unknown): Promise<TopChangeResult>;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=top-change.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"top-change.d.ts","sourceRoot":"","sources":["../../src/tools/top-change.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAsBtC,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,SAAS,GAAG,OAAO,GAAG,mBAAmB,GAAG,kBAAkB,GAAG,2BAA2B,GAAG,0BAA0B,CAAC;IACnI,SAAS,EAAE,UAAU,GAAG,UAAU,CAAC;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,YAAY,GAAG,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;CAClE;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,EAAE,MAAM,CAAC;IAC7B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,SAAS,GAAG,OAAO,GAAG,mBAAmB,GAAG,kBAAkB,GAAG,2BAA2B,GAAG,0BAA0B,CAAC;IACnI,SAAS,EAAE,UAAU,GAAG,UAAU,CAAC;IACnC,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,MAAM,CAAS;gBAEX,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM;IAKzD,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC;CAqQvD"}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TopChangeTool = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const handlers_js_1 = require("../errors/handlers.js");
|
|
6
|
+
const field_constants_js_1 = require("../utils/field-constants.js");
|
|
7
|
+
const date_math_js_1 = require("../utils/date-math.js");
|
|
8
|
+
const TopChangeArgsSchema = zod_1.z.object({
|
|
9
|
+
groupBy: zod_1.z.enum([
|
|
10
|
+
'account',
|
|
11
|
+
'group',
|
|
12
|
+
'provider_platform',
|
|
13
|
+
'patient_platform',
|
|
14
|
+
'provider_platform_version',
|
|
15
|
+
'patient_platform_version'
|
|
16
|
+
]).describe('Group by: "account", "group", "provider_platform", "patient_platform", "provider_platform_version", or "patient_platform_version"'),
|
|
17
|
+
direction: zod_1.z.enum(['increase', 'decrease']).describe('Direction: "increase" for highest growth, "decrease" for highest decline'),
|
|
18
|
+
topN: zod_1.z.number().int().min(1).max(50).default(5).describe('Number of top items to return (default: 5, max: 50)'),
|
|
19
|
+
startDate: zod_1.z.string().optional().describe('Start date for current period in ISO format (YYYY-MM-DD) or date math (e.g., "now-30d", "now-1y"). Defaults to "now-30d"'),
|
|
20
|
+
endDate: zod_1.z.string().optional().describe('End date for current period in ISO format (YYYY-MM-DD) or date math (e.g., "now"). Defaults to "now"'),
|
|
21
|
+
subscription: zod_1.z.enum(['Enterprise', 'Premium', 'FVC', 'BVC', 'Plus']).optional().describe('Optional subscription tier to filter by'),
|
|
22
|
+
}).strict();
|
|
23
|
+
class TopChangeTool {
|
|
24
|
+
elasticsearch;
|
|
25
|
+
logger;
|
|
26
|
+
constructor(elasticsearch, logger) {
|
|
27
|
+
this.elasticsearch = elasticsearch;
|
|
28
|
+
this.logger = logger.child({ tool: 'top-change' });
|
|
29
|
+
}
|
|
30
|
+
async execute(args) {
|
|
31
|
+
try {
|
|
32
|
+
const validatedArgs = TopChangeArgsSchema.parse(args);
|
|
33
|
+
const topN = validatedArgs.topN || 5;
|
|
34
|
+
let currentPeriodStart = validatedArgs.startDate || 'now-30d';
|
|
35
|
+
let currentPeriodEnd = validatedArgs.endDate || 'now';
|
|
36
|
+
// This tool aggregates ALL accounts/groups before sorting, making it memory-intensive.
|
|
37
|
+
// Cap the current period to prevent data limit errors.
|
|
38
|
+
//const { startDate: cappedCurrentStart, endDate: cappedCurrentEnd } = capTimePeriod(
|
|
39
|
+
// currentPeriodStart,
|
|
40
|
+
// currentPeriodEnd,
|
|
41
|
+
// 60, // Max 60 days per period
|
|
42
|
+
// this.logger
|
|
43
|
+
//);
|
|
44
|
+
//currentPeriodStart = cappedCurrentStart;
|
|
45
|
+
//currentPeriodEnd = cappedCurrentEnd;
|
|
46
|
+
// Calculate absolute dates for the current period
|
|
47
|
+
const currentStart = (0, date_math_js_1.resolveDate)(currentPeriodStart);
|
|
48
|
+
const currentEnd = (0, date_math_js_1.resolveDate)(currentPeriodEnd);
|
|
49
|
+
// Calculate duration in milliseconds
|
|
50
|
+
const durationMs = currentEnd.getTime() - currentStart.getTime();
|
|
51
|
+
// Calculate previous period: strictly distinct, immediately preceding current period
|
|
52
|
+
// Previous End = Current Start
|
|
53
|
+
const previousEnd = new Date(currentStart.getTime());
|
|
54
|
+
// Previous Start = Previous End - Duration
|
|
55
|
+
const previousStart = new Date(previousEnd.getTime() - durationMs);
|
|
56
|
+
// Format as ISO strings for Elasticsearch
|
|
57
|
+
const currentPeriodStartIso = currentStart.toISOString();
|
|
58
|
+
const currentPeriodEndIso = currentEnd.toISOString();
|
|
59
|
+
const previousPeriodStartIso = previousStart.toISOString();
|
|
60
|
+
const previousPeriodEndIso = previousEnd.toISOString();
|
|
61
|
+
this.logger.info('Getting top items by visit change', {
|
|
62
|
+
groupBy: validatedArgs.groupBy,
|
|
63
|
+
direction: validatedArgs.direction,
|
|
64
|
+
topN,
|
|
65
|
+
currentPeriodStart: currentPeriodStartIso,
|
|
66
|
+
currentPeriodEnd: currentPeriodEndIso,
|
|
67
|
+
previousPeriodStart: previousPeriodStartIso,
|
|
68
|
+
previousPeriodEnd: previousPeriodEndIso,
|
|
69
|
+
durationMs,
|
|
70
|
+
subscription: validatedArgs.subscription,
|
|
71
|
+
});
|
|
72
|
+
const client = this.elasticsearch.getClient();
|
|
73
|
+
const index = field_constants_js_1.FIELD_CONSTANTS.index;
|
|
74
|
+
const timeField = field_constants_js_1.FIELD_CONSTANTS.timeField;
|
|
75
|
+
const testVisitField = field_constants_js_1.FIELD_CONSTANTS.testVisitField;
|
|
76
|
+
const subscriptionField = field_constants_js_1.FIELD_CONSTANTS.subscriptionField;
|
|
77
|
+
let groupingField;
|
|
78
|
+
let aggregationName;
|
|
79
|
+
switch (validatedArgs.groupBy) {
|
|
80
|
+
case 'account':
|
|
81
|
+
groupingField = field_constants_js_1.FIELD_CONSTANTS.accountField;
|
|
82
|
+
aggregationName = 'by_account';
|
|
83
|
+
break;
|
|
84
|
+
case 'group':
|
|
85
|
+
groupingField = field_constants_js_1.FIELD_CONSTANTS.groupField;
|
|
86
|
+
aggregationName = 'by_group';
|
|
87
|
+
break;
|
|
88
|
+
case 'provider_platform':
|
|
89
|
+
groupingField = field_constants_js_1.FIELD_CONSTANTS.providerPlatformField;
|
|
90
|
+
aggregationName = 'by_provider_platform';
|
|
91
|
+
break;
|
|
92
|
+
case 'patient_platform':
|
|
93
|
+
groupingField = field_constants_js_1.FIELD_CONSTANTS.patientPlatformField;
|
|
94
|
+
aggregationName = 'by_patient_platform';
|
|
95
|
+
break;
|
|
96
|
+
case 'provider_platform_version':
|
|
97
|
+
groupingField = field_constants_js_1.FIELD_CONSTANTS.providerPlatformVersionField;
|
|
98
|
+
aggregationName = 'by_provider_platform_version';
|
|
99
|
+
break;
|
|
100
|
+
case 'patient_platform_version':
|
|
101
|
+
groupingField = field_constants_js_1.FIELD_CONSTANTS.patientPlatformVersionField;
|
|
102
|
+
aggregationName = 'by_patient_platform_version';
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
const sortOrder = validatedArgs.direction === 'increase' ? 'desc' : 'asc';
|
|
106
|
+
const filters = [
|
|
107
|
+
{
|
|
108
|
+
range: {
|
|
109
|
+
[timeField]: {
|
|
110
|
+
gte: previousPeriodStartIso,
|
|
111
|
+
lt: currentPeriodEndIso,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
exists: {
|
|
117
|
+
field: groupingField,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
];
|
|
121
|
+
if (validatedArgs.subscription) {
|
|
122
|
+
filters.push({
|
|
123
|
+
term: {
|
|
124
|
+
[subscriptionField]: validatedArgs.subscription,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
// aggregationName is already set above
|
|
129
|
+
const query = {
|
|
130
|
+
index,
|
|
131
|
+
size: 0,
|
|
132
|
+
body: {
|
|
133
|
+
track_total_hits: false,
|
|
134
|
+
query: {
|
|
135
|
+
bool: {
|
|
136
|
+
filter: filters,
|
|
137
|
+
must_not: [
|
|
138
|
+
{
|
|
139
|
+
term: {
|
|
140
|
+
[testVisitField]: 'Yes',
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
// Exclude empty group/account names
|
|
144
|
+
{
|
|
145
|
+
term: {
|
|
146
|
+
[groupingField]: '',
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
aggs: {
|
|
153
|
+
[aggregationName]: {
|
|
154
|
+
terms: {
|
|
155
|
+
field: groupingField,
|
|
156
|
+
size: topN * 2,
|
|
157
|
+
exclude: '',
|
|
158
|
+
},
|
|
159
|
+
aggs: {
|
|
160
|
+
current_period: {
|
|
161
|
+
filter: {
|
|
162
|
+
range: {
|
|
163
|
+
[timeField]: {
|
|
164
|
+
gte: currentPeriodStartIso,
|
|
165
|
+
lt: currentPeriodEndIso,
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
previous_period: {
|
|
171
|
+
filter: {
|
|
172
|
+
range: {
|
|
173
|
+
[timeField]: {
|
|
174
|
+
gte: previousPeriodStartIso,
|
|
175
|
+
lt: previousPeriodEndIso,
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
visit_delta: {
|
|
181
|
+
bucket_script: {
|
|
182
|
+
buckets_path: {
|
|
183
|
+
current: 'current_period._count',
|
|
184
|
+
previous: 'previous_period._count',
|
|
185
|
+
},
|
|
186
|
+
script: 'params.current - params.previous',
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
top_results: {
|
|
190
|
+
bucket_sort: {
|
|
191
|
+
sort: [
|
|
192
|
+
{
|
|
193
|
+
visit_delta: {
|
|
194
|
+
order: sortOrder,
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
size: topN,
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
this.logger.debug('Executing query', { query: JSON.stringify(query, null, 2) });
|
|
207
|
+
const response = await client.search(query);
|
|
208
|
+
const items = [];
|
|
209
|
+
const aggregation = response.aggregations?.[aggregationName];
|
|
210
|
+
const buckets = aggregation?.buckets || [];
|
|
211
|
+
for (const bucket of buckets) {
|
|
212
|
+
const item = bucket.key;
|
|
213
|
+
if (!item || item.trim() === '') {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
const currentPeriod = bucket.current_period;
|
|
217
|
+
const previousPeriod = bucket.previous_period;
|
|
218
|
+
const visitDelta = bucket.visit_delta;
|
|
219
|
+
const currentCount = currentPeriod?.doc_count || 0;
|
|
220
|
+
const previousCount = previousPeriod?.doc_count || 0;
|
|
221
|
+
const change = visitDelta?.value || 0;
|
|
222
|
+
const changePercent = previousCount > 0 ? ((change / previousCount) * 100).toFixed(2) : (currentCount > 0 ? 100 : 0);
|
|
223
|
+
items.push({
|
|
224
|
+
item,
|
|
225
|
+
current_period_count: currentCount,
|
|
226
|
+
previous_period_count: previousCount,
|
|
227
|
+
change,
|
|
228
|
+
change_percent: parseFloat(changePercent.toString()),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
this.logger.info('Successfully retrieved top items', {
|
|
232
|
+
groupBy: validatedArgs.groupBy,
|
|
233
|
+
direction: validatedArgs.direction,
|
|
234
|
+
count: items.length,
|
|
235
|
+
});
|
|
236
|
+
return {
|
|
237
|
+
currentPeriodStart: currentPeriodStartIso,
|
|
238
|
+
currentPeriodEnd: currentPeriodEndIso,
|
|
239
|
+
previousPeriodStart: previousPeriodStartIso,
|
|
240
|
+
previousPeriodEnd: previousPeriodEndIso,
|
|
241
|
+
groupBy: validatedArgs.groupBy,
|
|
242
|
+
direction: validatedArgs.direction,
|
|
243
|
+
items,
|
|
244
|
+
total: items.length,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
if (error instanceof Error && error.name === 'ZodError') {
|
|
249
|
+
throw new handlers_js_1.ValidationError('Invalid arguments for top_change', {
|
|
250
|
+
details: error.message,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
if (error instanceof handlers_js_1.ValidationError) {
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
this.logger.error('Failed to get top items by change', {}, error);
|
|
257
|
+
throw new handlers_js_1.ElasticsearchError('Failed to get top items by visit change from Elasticsearch', error, { args });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
exports.TopChangeTool = TopChangeTool;
|
|
262
|
+
//# sourceMappingURL=top-change.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"top-change.js","sourceRoot":"","sources":["../../src/tools/top-change.ts"],"names":[],"mappings":";;;AAEA,6BAAwB;AACxB,uDAA4E;AAC5E,oEAA8D;AAC9D,wDAAoD;AAEpD,MAAM,mBAAmB,GAAG,OAAC,CAAC,MAAM,CAAC;IACnC,OAAO,EAAE,OAAC,CAAC,IAAI,CAAC;QACd,SAAS;QACT,OAAO;QACP,mBAAmB;QACnB,kBAAkB;QAClB,2BAA2B;QAC3B,0BAA0B;KAC3B,CAAC,CAAC,QAAQ,CAAC,mIAAmI,CAAC;IAChJ,SAAS,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,0EAA0E,CAAC;IAChI,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,qDAAqD,CAAC;IAChH,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,0HAA0H,CAAC;IACrK,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sGAAsG,CAAC;IAC/I,YAAY,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,YAAY,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,yCAAyC,CAAC;CACrI,CAAC,CAAC,MAAM,EAAE,CAAC;AA8BZ,MAAa,aAAa;IAChB,aAAa,CAAuB;IACpC,MAAM,CAAS;IAEvB,YAAY,aAAmC,EAAE,MAAc;QAC7D,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,IAAa;QACzB,IAAI,CAAC;YACH,MAAM,aAAa,GAAG,mBAAmB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACtD,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,IAAI,CAAC,CAAC;YACrC,IAAI,kBAAkB,GAAG,aAAa,CAAC,SAAS,IAAI,SAAS,CAAC;YAC9D,IAAI,gBAAgB,GAAG,aAAa,CAAC,OAAO,IAAI,KAAK,CAAC;YAEtD,uFAAuF;YACvF,uDAAuD;YACvD,qFAAqF;YACrF,uBAAuB;YACvB,qBAAqB;YACrB,iCAAiC;YACjC,eAAe;YACf,IAAI;YACJ,0CAA0C;YAC1C,sCAAsC;YAEtC,kDAAkD;YAClD,MAAM,YAAY,GAAG,IAAA,0BAAW,EAAC,kBAAkB,CAAC,CAAC;YACrD,MAAM,UAAU,GAAG,IAAA,0BAAW,EAAC,gBAAgB,CAAC,CAAC;YAEjD,qCAAqC;YACrC,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,CAAC;YAEjE,qFAAqF;YACrF,+BAA+B;YAC/B,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC,CAAC;YACrD,2CAA2C;YAC3C,MAAM,aAAa,GAAG,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,GAAG,UAAU,CAAC,CAAC;YAEnE,0CAA0C;YAC1C,MAAM,qBAAqB,GAAG,YAAY,CAAC,WAAW,EAAE,CAAC;YACzD,MAAM,mBAAmB,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC;YACrD,MAAM,sBAAsB,GAAG,aAAa,CAAC,WAAW,EAAE,CAAC;YAC3D,MAAM,oBAAoB,GAAG,WAAW,CAAC,WAAW,EAAE,CAAC;YAEvD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,mCAAmC,EAAE;gBACpD,OAAO,EAAE,aAAa,CAAC,OAAO;gBAC9B,SAAS,EAAE,aAAa,CAAC,SAAS;gBAClC,IAAI;gBACJ,kBAAkB,EAAE,qBAAqB;gBACzC,gBAAgB,EAAE,mBAAmB;gBACrC,mBAAmB,EAAE,sBAAsB;gBAC3C,iBAAiB,EAAE,oBAAoB;gBACvC,UAAU;gBACV,YAAY,EAAE,aAAa,CAAC,YAAY;aACzC,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,SAAS,EAAE,CAAC;YAE9C,MAAM,KAAK,GAAG,oCAAe,CAAC,KAAK,CAAC;YACpC,MAAM,SAAS,GAAG,oCAAe,CAAC,SAAS,CAAC;YAC5C,MAAM,cAAc,GAAG,oCAAe,CAAC,cAAc,CAAC;YACtD,MAAM,iBAAiB,GAAG,oCAAe,CAAC,iBAAiB,CAAC;YAE5D,IAAI,aAAqB,CAAC;YAC1B,IAAI,eAAuB,CAAC;YAE5B,QAAQ,aAAa,CAAC,OAAO,EAAE,CAAC;gBAC9B,KAAK,SAAS;oBACZ,aAAa,GAAG,oCAAe,CAAC,YAAY,CAAC;oBAC7C,eAAe,GAAG,YAAY,CAAC;oBAC/B,MAAM;gBACR,KAAK,OAAO;oBACV,aAAa,GAAG,oCAAe,CAAC,UAAU,CAAC;oBAC3C,eAAe,GAAG,UAAU,CAAC;oBAC7B,MAAM;gBACR,KAAK,mBAAmB;oBACtB,aAAa,GAAG,oCAAe,CAAC,qBAAqB,CAAC;oBACtD,eAAe,GAAG,sBAAsB,CAAC;oBACzC,MAAM;gBACR,KAAK,kBAAkB;oBACrB,aAAa,GAAG,oCAAe,CAAC,oBAAoB,CAAC;oBACrD,eAAe,GAAG,qBAAqB,CAAC;oBACxC,MAAM;gBACR,KAAK,2BAA2B;oBAC9B,aAAa,GAAG,oCAAe,CAAC,4BAA4B,CAAC;oBAC7D,eAAe,GAAG,8BAA8B,CAAC;oBACjD,MAAM;gBACR,KAAK,0BAA0B;oBAC7B,aAAa,GAAG,oCAAe,CAAC,2BAA2B,CAAC;oBAC5D,eAAe,GAAG,6BAA6B,CAAC;oBAChD,MAAM;YACV,CAAC;YAED,MAAM,SAAS,GAAG,aAAa,CAAC,SAAS,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;YAE1E,MAAM,OAAO,GAAU;gBACrB;oBACE,KAAK,EAAE;wBACL,CAAC,SAAS,CAAC,EAAE;4BACX,GAAG,EAAE,sBAAsB;4BAC3B,EAAE,EAAE,mBAAmB;yBACxB;qBACF;iBACF;gBACD;oBACE,MAAM,EAAE;wBACN,KAAK,EAAE,aAAa;qBACrB;iBACF;aACF,CAAC;YAEF,IAAI,aAAa,CAAC,YAAY,EAAE,CAAC;gBAC/B,OAAO,CAAC,IAAI,CAAC;oBACX,IAAI,EAAE;wBACJ,CAAC,iBAAiB,CAAC,EAAE,aAAa,CAAC,YAAY;qBAChD;iBACF,CAAC,CAAC;YACL,CAAC;YAED,uCAAuC;YAEvC,MAAM,KAAK,GAAG;gBACZ,KAAK;gBACL,IAAI,EAAE,CAAC;gBACP,IAAI,EAAE;oBACJ,gBAAgB,EAAE,KAAK;oBACvB,KAAK,EAAE;wBACL,IAAI,EAAE;4BACJ,MAAM,EAAE,OAAO;4BACf,QAAQ,EAAE;gCACR;oCACE,IAAI,EAAE;wCACJ,CAAC,cAAc,CAAC,EAAE,KAAK;qCACxB;iCACF;gCACD,oCAAoC;gCACpC;oCACE,IAAI,EAAE;wCACJ,CAAC,aAAa,CAAC,EAAE,EAAE;qCACpB;iCACF;6BACF;yBACF;qBACF;oBACD,IAAI,EAAE;wBACJ,CAAC,eAAe,CAAC,EAAE;4BACjB,KAAK,EAAE;gCACL,KAAK,EAAE,aAAa;gCACpB,IAAI,EAAE,IAAI,GAAG,CAAC;gCACd,OAAO,EAAE,EAAE;6BACZ;4BACD,IAAI,EAAE;gCACJ,cAAc,EAAE;oCACd,MAAM,EAAE;wCACN,KAAK,EAAE;4CACL,CAAC,SAAS,CAAC,EAAE;gDACX,GAAG,EAAE,qBAAqB;gDAC1B,EAAE,EAAE,mBAAmB;6CACxB;yCACF;qCACF;iCACF;gCACD,eAAe,EAAE;oCACf,MAAM,EAAE;wCACN,KAAK,EAAE;4CACL,CAAC,SAAS,CAAC,EAAE;gDACX,GAAG,EAAE,sBAAsB;gDAC3B,EAAE,EAAE,oBAAoB;6CACzB;yCACF;qCACF;iCACF;gCACD,WAAW,EAAE;oCACX,aAAa,EAAE;wCACb,YAAY,EAAE;4CACZ,OAAO,EAAE,uBAAuB;4CAChC,QAAQ,EAAE,wBAAwB;yCACnC;wCACD,MAAM,EAAE,kCAAkC;qCAC3C;iCACF;gCACD,WAAW,EAAE;oCACX,WAAW,EAAE;wCACX,IAAI,EAAE;4CACJ;gDACE,WAAW,EAAE;oDACX,KAAK,EAAE,SAAS;iDACjB;6CACF;yCACF;wCACD,IAAI,EAAE,IAAI;qCACX;iCACF;6BACF;yBACF;qBACF;iBACF;aACF,CAAC;YAEF,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;YAEhF,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAE5C,MAAM,KAAK,GAAiB,EAAE,CAAC;YAC/B,MAAM,WAAW,GAAG,QAAQ,CAAC,YAAY,EAAE,CAAC,eAAe,CAAQ,CAAC;YACpE,MAAM,OAAO,GAAG,WAAW,EAAE,OAAO,IAAI,EAAE,CAAC;YAE3C,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,GAAa,CAAC;gBAElC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;oBAChC,SAAS;gBACX,CAAC;gBAED,MAAM,aAAa,GAAG,MAAM,CAAC,cAAqB,CAAC;gBACnD,MAAM,cAAc,GAAG,MAAM,CAAC,eAAsB,CAAC;gBACrD,MAAM,UAAU,GAAG,MAAM,CAAC,WAAkB,CAAC;gBAE7C,MAAM,YAAY,GAAG,aAAa,EAAE,SAAS,IAAI,CAAC,CAAC;gBACnD,MAAM,aAAa,GAAG,cAAc,EAAE,SAAS,IAAI,CAAC,CAAC;gBACrD,MAAM,MAAM,GAAG,UAAU,EAAE,KAAK,IAAI,CAAC,CAAC;gBACtC,MAAM,aAAa,GACjB,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,aAAa,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAEjG,KAAK,CAAC,IAAI,CAAC;oBACT,IAAI;oBACJ,oBAAoB,EAAE,YAAY;oBAClC,qBAAqB,EAAE,aAAa;oBACpC,MAAM;oBACN,cAAc,EAAE,UAAU,CAAC,aAAa,CAAC,QAAQ,EAAE,CAAC;iBACrD,CAAC,CAAC;YACL,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,kCAAkC,EAAE;gBACnD,OAAO,EAAE,aAAa,CAAC,OAAO;gBAC9B,SAAS,EAAE,aAAa,CAAC,SAAS;gBAClC,KAAK,EAAE,KAAK,CAAC,MAAM;aACpB,CAAC,CAAC;YAEH,OAAO;gBACL,kBAAkB,EAAE,qBAAqB;gBACzC,gBAAgB,EAAE,mBAAmB;gBACrC,mBAAmB,EAAE,sBAAsB;gBAC3C,iBAAiB,EAAE,oBAAoB;gBACvC,OAAO,EAAE,aAAa,CAAC,OAAO;gBAC9B,SAAS,EAAE,aAAa,CAAC,SAAS;gBAClC,KAAK;gBACL,KAAK,EAAE,KAAK,CAAC,MAAM;aACpB,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;gBACxD,MAAM,IAAI,6BAAe,CAAC,kCAAkC,EAAE;oBAC5D,OAAO,EAAE,KAAK,CAAC,OAAO;iBACvB,CAAC,CAAC;YACL,CAAC;YAED,IAAI,KAAK,YAAY,6BAAe,EAAE,CAAC;gBACrC,MAAM,KAAK,CAAC;YACd,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,mCAAmC,EAAE,EAAE,EAAE,KAAc,CAAC,CAAC;YAC3E,MAAM,IAAI,gCAAkB,CAC1B,4DAA4D,EAC5D,KAAc,EACd,EAAE,IAAI,EAAE,CACT,CAAC;QACJ,CAAC;IACH,CAAC;CACF;AA9QD,sCA8QC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standardized aggregation size limits to prevent data limit errors
|
|
3
|
+
* All tools should use these constants instead of magic numbers
|
|
4
|
+
*/
|
|
5
|
+
export declare const AGGREGATION_LIMITS: {
|
|
6
|
+
readonly SMALL: 10;
|
|
7
|
+
readonly MEDIUM: 50;
|
|
8
|
+
readonly LARGE: 100;
|
|
9
|
+
readonly MAX: 200;
|
|
10
|
+
readonly MAX_BUCKETS: 12;
|
|
11
|
+
readonly MAX_TOP_N: 50;
|
|
12
|
+
readonly MAX_LIMIT: 50;
|
|
13
|
+
readonly MAX_SIZE: 50;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Calculate safe terms aggregation size
|
|
17
|
+
* @param requested - Requested number of items
|
|
18
|
+
* @param multiplier - Multiplier for fetching extra items (default: 2)
|
|
19
|
+
* @param max - Maximum allowed (default: LARGE)
|
|
20
|
+
* @returns Safe aggregation size
|
|
21
|
+
*/
|
|
22
|
+
export declare function calculateTermsSize(requested: number, multiplier?: number, max?: number): number;
|
|
23
|
+
//# sourceMappingURL=aggregation-limits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aggregation-limits.d.ts","sourceRoot":"","sources":["../../src/utils/aggregation-limits.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;CAcrB,CAAC;AAEX;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,UAAU,GAAE,MAAU,EACtB,GAAG,GAAE,MAAiC,GACrC,MAAM,CAER"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AGGREGATION_LIMITS = void 0;
|
|
4
|
+
exports.calculateTermsSize = calculateTermsSize;
|
|
5
|
+
/**
|
|
6
|
+
* Standardized aggregation size limits to prevent data limit errors
|
|
7
|
+
* All tools should use these constants instead of magic numbers
|
|
8
|
+
*/
|
|
9
|
+
exports.AGGREGATION_LIMITS = {
|
|
10
|
+
// Terms aggregations
|
|
11
|
+
SMALL: 10, // For single-entity summaries (e.g., top 5 accounts in a group)
|
|
12
|
+
MEDIUM: 50, // For distributions, subscriptions, ratings
|
|
13
|
+
LARGE: 100, // For platform breakdowns, top N queries
|
|
14
|
+
MAX: 200, // Absolute maximum (rarely used)
|
|
15
|
+
// Date histogram buckets
|
|
16
|
+
MAX_BUCKETS: 12, // For time series (daily/weekly/monthly trends)
|
|
17
|
+
// Safeguards
|
|
18
|
+
MAX_TOP_N: 50, // Cap on topN parameters
|
|
19
|
+
MAX_LIMIT: 50, // Cap on limit parameters
|
|
20
|
+
MAX_SIZE: 50, // Cap on search size parameter
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Calculate safe terms aggregation size
|
|
24
|
+
* @param requested - Requested number of items
|
|
25
|
+
* @param multiplier - Multiplier for fetching extra items (default: 2)
|
|
26
|
+
* @param max - Maximum allowed (default: LARGE)
|
|
27
|
+
* @returns Safe aggregation size
|
|
28
|
+
*/
|
|
29
|
+
function calculateTermsSize(requested, multiplier = 2, max = exports.AGGREGATION_LIMITS.LARGE) {
|
|
30
|
+
return Math.min(requested * multiplier, max);
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=aggregation-limits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aggregation-limits.js","sourceRoot":"","sources":["../../src/utils/aggregation-limits.ts"],"names":[],"mappings":";;;AA2BA,gDAMC;AAjCD;;;GAGG;AACU,QAAA,kBAAkB,GAAG;IAChC,qBAAqB;IACrB,KAAK,EAAE,EAAE,EAAO,gEAAgE;IAChF,MAAM,EAAE,EAAE,EAAM,4CAA4C;IAC5D,KAAK,EAAE,GAAG,EAAM,yCAAyC;IACzD,GAAG,EAAE,GAAG,EAAQ,iCAAiC;IAEjD,yBAAyB;IACzB,WAAW,EAAE,EAAE,EAAE,gDAAgD;IAEjE,aAAa;IACb,SAAS,EAAE,EAAE,EAAK,yBAAyB;IAC3C,SAAS,EAAE,EAAE,EAAK,0BAA0B;IAC5C,QAAQ,EAAE,EAAE,EAAM,+BAA+B;CACzC,CAAC;AAEX;;;;;;GAMG;AACH,SAAgB,kBAAkB,CAChC,SAAiB,EACjB,aAAqB,CAAC,EACtB,MAAc,0BAAkB,CAAC,KAAK;IAEtC,OAAO,IAAI,CAAC,GAAG,CAAC,SAAS,GAAG,UAAU,EAAE,GAAG,CAAC,CAAC;AAC/C,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for parsing and working with Elasticsearch date math expressions
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Resolve a date string (ISO or relative math) to a Javascript Date object
|
|
6
|
+
* @param dateStr - Date string (e.g., "now", "now-30d", "2023-01-01")
|
|
7
|
+
* @returns Date object
|
|
8
|
+
* @throws Error if date format is invalid
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveDate(dateStr: string): Date;
|
|
11
|
+
/**
|
|
12
|
+
* Legacy wrapper for backward compatibility, though discouraged.
|
|
13
|
+
* returns number of days relative to now.
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseDateMath(dateStr: string): number;
|
|
16
|
+
/**
|
|
17
|
+
* Cap a time period to a maximum number of days
|
|
18
|
+
* @param startDate - Start date string
|
|
19
|
+
* @param endDate - End date string
|
|
20
|
+
* @param maxDays - Maximum number of days allowed
|
|
21
|
+
* @param logger - Logger instance for warnings
|
|
22
|
+
* @returns Adjusted dates and whether adjustment was made
|
|
23
|
+
*/
|
|
24
|
+
export declare function capTimePeriod(startDate: string, endDate: string, maxDays: number, logger: {
|
|
25
|
+
warn: (message: string, meta: any) => void;
|
|
26
|
+
}): {
|
|
27
|
+
startDate: string;
|
|
28
|
+
endDate: string;
|
|
29
|
+
wasAdjusted: boolean;
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=date-math.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"date-math.d.ts","sourceRoot":"","sources":["../../src/utils/date-math.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CA8CjD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAsBrD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,MAAM,EAAE;IAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,CAAA;CAAE,GACrD;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAkB9D"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Utilities for parsing and working with Elasticsearch date math expressions
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveDate = resolveDate;
|
|
7
|
+
exports.parseDateMath = parseDateMath;
|
|
8
|
+
exports.capTimePeriod = capTimePeriod;
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a date string (ISO or relative math) to a Javascript Date object
|
|
11
|
+
* @param dateStr - Date string (e.g., "now", "now-30d", "2023-01-01")
|
|
12
|
+
* @returns Date object
|
|
13
|
+
* @throws Error if date format is invalid
|
|
14
|
+
*/
|
|
15
|
+
function resolveDate(dateStr) {
|
|
16
|
+
// Handle "now"
|
|
17
|
+
if (dateStr === 'now') {
|
|
18
|
+
return new Date();
|
|
19
|
+
}
|
|
20
|
+
// Handle relative math "now-..."
|
|
21
|
+
if (dateStr.startsWith('now-')) {
|
|
22
|
+
const match = dateStr.match(/now-(\d+)([dwMy]|\+)?/);
|
|
23
|
+
if (!match) {
|
|
24
|
+
throw new Error(`Invalid relative date format: ${dateStr}`);
|
|
25
|
+
}
|
|
26
|
+
const value = parseInt(match[1], 10);
|
|
27
|
+
const unit = match[2];
|
|
28
|
+
const now = new Date();
|
|
29
|
+
switch (unit) {
|
|
30
|
+
case 'd':
|
|
31
|
+
case 'D':
|
|
32
|
+
now.setDate(now.getDate() - value);
|
|
33
|
+
break;
|
|
34
|
+
case 'w':
|
|
35
|
+
case 'W':
|
|
36
|
+
now.setDate(now.getDate() - (value * 7));
|
|
37
|
+
break;
|
|
38
|
+
case 'M':
|
|
39
|
+
now.setMonth(now.getMonth() - value);
|
|
40
|
+
break;
|
|
41
|
+
case 'y':
|
|
42
|
+
case 'Y':
|
|
43
|
+
now.setFullYear(now.getFullYear() - value);
|
|
44
|
+
break;
|
|
45
|
+
default:
|
|
46
|
+
// Default to days if unit is missing or unknown (safe fallback for simple cases)
|
|
47
|
+
now.setDate(now.getDate() - value);
|
|
48
|
+
}
|
|
49
|
+
return now;
|
|
50
|
+
}
|
|
51
|
+
// Handle ISO strings
|
|
52
|
+
const parsed = new Date(dateStr);
|
|
53
|
+
if (isNaN(parsed.getTime())) {
|
|
54
|
+
throw new Error(`Invalid date format: ${dateStr}`);
|
|
55
|
+
}
|
|
56
|
+
return parsed;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Legacy wrapper for backward compatibility, though discouraged.
|
|
60
|
+
* returns number of days relative to now.
|
|
61
|
+
*/
|
|
62
|
+
function parseDateMath(dateStr) {
|
|
63
|
+
if (dateStr === 'now')
|
|
64
|
+
return 0;
|
|
65
|
+
try {
|
|
66
|
+
const date = resolveDate(dateStr);
|
|
67
|
+
const now = new Date();
|
|
68
|
+
const diffTime = Math.abs(now.getTime() - date.getTime());
|
|
69
|
+
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
// Fallback for very simple day parsing if resolveDate fails or strictly for "now-Nd" extraction
|
|
73
|
+
const match = dateStr.match(/now-(\d+)([dwMy])/);
|
|
74
|
+
if (match) {
|
|
75
|
+
const value = parseInt(match[1], 10);
|
|
76
|
+
const unit = match[2];
|
|
77
|
+
if (unit === 'd' || unit === 'D')
|
|
78
|
+
return value;
|
|
79
|
+
if (unit === 'w' || unit === 'W')
|
|
80
|
+
return value * 7;
|
|
81
|
+
if (unit === 'M')
|
|
82
|
+
return value * 30;
|
|
83
|
+
if (unit === 'y' || unit === 'Y')
|
|
84
|
+
return value * 365;
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
return 30;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Cap a time period to a maximum number of days
|
|
92
|
+
* @param startDate - Start date string
|
|
93
|
+
* @param endDate - End date string
|
|
94
|
+
* @param maxDays - Maximum number of days allowed
|
|
95
|
+
* @param logger - Logger instance for warnings
|
|
96
|
+
* @returns Adjusted dates and whether adjustment was made
|
|
97
|
+
*/
|
|
98
|
+
function capTimePeriod(startDate, endDate, maxDays, logger) {
|
|
99
|
+
const startDays = parseDateMath(startDate);
|
|
100
|
+
const endDays = parseDateMath(endDate);
|
|
101
|
+
const periodDays = Math.abs(startDays - endDays);
|
|
102
|
+
if (periodDays > maxDays) {
|
|
103
|
+
const adjustedStart = startDate.startsWith('now-') ? `now-${maxDays}d` : startDate;
|
|
104
|
+
logger.warn('Time period capped to prevent data limit errors', {
|
|
105
|
+
originalStartDate: startDate,
|
|
106
|
+
originalEndDate: endDate,
|
|
107
|
+
originalPeriodDays: periodDays,
|
|
108
|
+
adjustedStartDate: adjustedStart,
|
|
109
|
+
adjustedEndDate: endDate,
|
|
110
|
+
adjustedPeriodDays: maxDays,
|
|
111
|
+
});
|
|
112
|
+
return { startDate: adjustedStart, endDate, wasAdjusted: true };
|
|
113
|
+
}
|
|
114
|
+
return { startDate, endDate, wasAdjusted: false };
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=date-math.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"date-math.js","sourceRoot":"","sources":["../../src/utils/date-math.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared field name constants for Elasticsearch queries
|
|
3
|
+
* All tools should use these constants instead of hardcoding field names
|
|
4
|
+
*/
|
|
5
|
+
export declare const FIELD_CONSTANTS: {
|
|
6
|
+
readonly index: "stats-*";
|
|
7
|
+
readonly timeField: "createdtime";
|
|
8
|
+
readonly testVisitField: "test_visit.keyword";
|
|
9
|
+
readonly subscriptionField: "subscription.keyword";
|
|
10
|
+
readonly accountField: "account.keyword";
|
|
11
|
+
readonly groupField: "group.keyword";
|
|
12
|
+
readonly providerField: "provider0.keyword";
|
|
13
|
+
readonly patientField: "patient0.keyword";
|
|
14
|
+
readonly callDurationField: "call_duration";
|
|
15
|
+
readonly providerRatingField: "provider_rating";
|
|
16
|
+
readonly patientRatingField: "patient_rating";
|
|
17
|
+
readonly meetingBasedField: "meeting_based";
|
|
18
|
+
readonly providerPlatformField: "provider0_platform.keyword";
|
|
19
|
+
readonly patientPlatformField: "patient0_platform.keyword";
|
|
20
|
+
readonly providerPlatformVersionField: "provider0_platform_version.keyword";
|
|
21
|
+
readonly patientPlatformVersionField: "patient0_platform_version.keyword";
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=field-constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field-constants.d.ts","sourceRoot":"","sources":["../../src/utils/field-constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;CAiBlB,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FIELD_CONSTANTS = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Shared field name constants for Elasticsearch queries
|
|
6
|
+
* All tools should use these constants instead of hardcoding field names
|
|
7
|
+
*/
|
|
8
|
+
exports.FIELD_CONSTANTS = {
|
|
9
|
+
index: 'stats-*',
|
|
10
|
+
timeField: 'createdtime',
|
|
11
|
+
testVisitField: 'test_visit.keyword',
|
|
12
|
+
subscriptionField: 'subscription.keyword',
|
|
13
|
+
accountField: 'account.keyword',
|
|
14
|
+
groupField: 'group.keyword',
|
|
15
|
+
providerField: 'provider0.keyword',
|
|
16
|
+
patientField: 'patient0.keyword',
|
|
17
|
+
callDurationField: 'call_duration',
|
|
18
|
+
providerRatingField: 'provider_rating',
|
|
19
|
+
patientRatingField: 'patient_rating',
|
|
20
|
+
meetingBasedField: 'meeting_based',
|
|
21
|
+
providerPlatformField: 'provider0_platform.keyword',
|
|
22
|
+
patientPlatformField: 'patient0_platform.keyword',
|
|
23
|
+
providerPlatformVersionField: 'provider0_platform_version.keyword',
|
|
24
|
+
patientPlatformVersionField: 'patient0_platform_version.keyword',
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=field-constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field-constants.js","sourceRoot":"","sources":["../../src/utils/field-constants.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACU,QAAA,eAAe,GAAG;IAC7B,KAAK,EAAE,SAAS;IAChB,SAAS,EAAE,aAAa;IACxB,cAAc,EAAE,oBAAoB;IACpC,iBAAiB,EAAE,sBAAsB;IACzC,YAAY,EAAE,iBAAiB;IAC/B,UAAU,EAAE,eAAe;IAC3B,aAAa,EAAE,mBAAmB;IAClC,YAAY,EAAE,kBAAkB;IAChC,iBAAiB,EAAE,eAAe;IAClC,mBAAmB,EAAE,iBAAiB;IACtC,kBAAkB,EAAE,gBAAgB;IACpC,iBAAiB,EAAE,eAAe;IAClC,qBAAqB,EAAE,4BAA4B;IACnD,oBAAoB,EAAE,2BAA2B;IACjD,4BAA4B,EAAE,oCAAoC;IAClE,2BAA2B,EAAE,mCAAmC;CACxD,CAAC"}
|