elasticsearch-mcp-vsee 0.5.12 → 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.
Files changed (57) hide show
  1. package/build/server.d.ts +2 -1
  2. package/build/server.d.ts.map +1 -1
  3. package/build/server.js +65 -9
  4. package/build/server.js.map +1 -1
  5. package/build/tools/base-tool.d.ts +38 -0
  6. package/build/tools/base-tool.d.ts.map +1 -0
  7. package/build/tools/base-tool.js +67 -0
  8. package/build/tools/base-tool.js.map +1 -0
  9. package/build/tools/find-entities-by-metric.d.ts +314 -24
  10. package/build/tools/find-entities-by-metric.d.ts.map +1 -1
  11. package/build/tools/find-entities-by-metric.js +277 -284
  12. package/build/tools/find-entities-by-metric.js.map +1 -1
  13. package/build/tools/get-platform-breakdown.d.ts +60 -22
  14. package/build/tools/get-platform-breakdown.d.ts.map +1 -1
  15. package/build/tools/get-platform-breakdown.js +220 -266
  16. package/build/tools/get-platform-breakdown.js.map +1 -1
  17. package/build/tools/get-rating-distribution.d.ts +65 -25
  18. package/build/tools/get-rating-distribution.d.ts.map +1 -1
  19. package/build/tools/get-rating-distribution.js +206 -245
  20. package/build/tools/get-rating-distribution.js.map +1 -1
  21. package/build/tools/get-subscription-breakdown.d.ts +30 -15
  22. package/build/tools/get-subscription-breakdown.d.ts.map +1 -1
  23. package/build/tools/get-subscription-breakdown.js +140 -155
  24. package/build/tools/get-subscription-breakdown.js.map +1 -1
  25. package/build/tools/get-usage-leaderboard.d.ts +114 -0
  26. package/build/tools/get-usage-leaderboard.d.ts.map +1 -0
  27. package/build/tools/get-usage-leaderboard.js +169 -0
  28. package/build/tools/get-usage-leaderboard.js.map +1 -0
  29. package/build/tools/get-usage-profile.d.ts +83 -0
  30. package/build/tools/get-usage-profile.d.ts.map +1 -0
  31. package/build/tools/get-usage-profile.js +235 -0
  32. package/build/tools/get-usage-profile.js.map +1 -0
  33. package/build/tools/get-visit-trends.d.ts +59 -25
  34. package/build/tools/get-visit-trends.d.ts.map +1 -1
  35. package/build/tools/get-visit-trends.js +175 -242
  36. package/build/tools/get-visit-trends.js.map +1 -1
  37. package/build/tools/index.d.ts +4 -2
  38. package/build/tools/index.d.ts.map +1 -1
  39. package/build/tools/index.js +5 -3
  40. package/build/tools/index.js.map +1 -1
  41. package/build/tools/top-change.d.ts +56 -23
  42. package/build/tools/top-change.d.ts.map +1 -1
  43. package/build/tools/top-change.js +177 -193
  44. package/build/tools/top-change.js.map +1 -1
  45. package/build/tools/types.d.ts +24 -0
  46. package/build/tools/types.d.ts.map +1 -0
  47. package/build/tools/types.js +3 -0
  48. package/build/tools/types.js.map +1 -0
  49. package/build/utils/query-helpers.d.ts +10 -0
  50. package/build/utils/query-helpers.d.ts.map +1 -0
  51. package/build/utils/query-helpers.js +48 -0
  52. package/build/utils/query-helpers.js.map +1 -0
  53. package/package.json +1 -1
  54. package/build/tools/get-usage-summary.d.ts +0 -52
  55. package/build/tools/get-usage-summary.d.ts.map +0 -1
  56. package/build/tools/get-usage-summary.js +0 -273
  57. package/build/tools/get-usage-summary.js.map +0 -1
@@ -1,15 +1,35 @@
1
- import { ElasticsearchManager } from '../elasticsearch/client.js';
2
- import { Logger } from '../logger.js';
3
- export interface GetRatingDistributionArgs {
4
- ratingType: 'provider' | 'patient' | 'both';
5
- bucketSize?: number;
6
- startDate?: string;
7
- endDate?: string;
8
- account?: string;
9
- group?: string;
10
- subscription?: 'Enterprise' | 'Premium' | 'FVC' | 'BVC' | 'Plus';
11
- groupBy?: 'none' | 'subscription' | 'account' | 'group';
12
- }
1
+ import { BaseTool } from './base-tool.js';
2
+ import { z } from 'zod';
3
+ import { StandardResponse } from './types.js';
4
+ declare const GetRatingDistributionArgsSchema: z.ZodObject<{
5
+ ratingType: z.ZodEnum<["provider", "patient", "both"]>;
6
+ bucketSize: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
7
+ startDate: z.ZodOptional<z.ZodString>;
8
+ endDate: z.ZodOptional<z.ZodString>;
9
+ account: z.ZodOptional<z.ZodString>;
10
+ group: z.ZodOptional<z.ZodString>;
11
+ subscription: z.ZodOptional<z.ZodEnum<["Enterprise", "Premium", "FVC", "BVC", "Plus"]>>;
12
+ groupBy: z.ZodDefault<z.ZodOptional<z.ZodEnum<["none", "subscription", "account", "group"]>>>;
13
+ }, "strict", z.ZodTypeAny, {
14
+ groupBy: "subscription" | "account" | "group" | "none";
15
+ ratingType: "provider" | "patient" | "both";
16
+ bucketSize: number;
17
+ startDate?: string | undefined;
18
+ endDate?: string | undefined;
19
+ subscription?: "Enterprise" | "Premium" | "FVC" | "BVC" | "Plus" | undefined;
20
+ account?: string | undefined;
21
+ group?: string | undefined;
22
+ }, {
23
+ ratingType: "provider" | "patient" | "both";
24
+ startDate?: string | undefined;
25
+ endDate?: string | undefined;
26
+ subscription?: "Enterprise" | "Premium" | "FVC" | "BVC" | "Plus" | undefined;
27
+ account?: string | undefined;
28
+ group?: string | undefined;
29
+ groupBy?: "subscription" | "account" | "group" | "none" | undefined;
30
+ bucketSize?: number | undefined;
31
+ }>;
32
+ export type GetRatingDistributionArgs = z.infer<typeof GetRatingDistributionArgsSchema>;
13
33
  export interface RatingBucket {
14
34
  range: string;
15
35
  count: number;
@@ -31,18 +51,38 @@ export interface RatingDistributionItem extends Record<string, any> {
31
51
  max_rating: number;
32
52
  };
33
53
  }
34
- export interface RatingDistributionResult {
35
- startDate: string;
36
- endDate: string;
37
- ratingType: string;
38
- period: string;
39
- groupBy: string;
40
- distribution: RatingDistributionItem | RatingDistributionItem[];
41
- }
42
- export declare class GetRatingDistributionTool {
43
- private elasticsearch;
44
- private logger;
45
- constructor(elasticsearch: ElasticsearchManager, logger: Logger);
46
- execute(args: unknown): Promise<RatingDistributionResult>;
54
+ export type RatingDistributionResult = StandardResponse<RatingDistributionItem | RatingDistributionItem[]>;
55
+ export declare class GetRatingDistributionTool extends BaseTool<typeof GetRatingDistributionArgsSchema, RatingDistributionResult> {
56
+ constructor(elasticsearch: any, logger: any);
57
+ get schema(): z.ZodObject<{
58
+ ratingType: z.ZodEnum<["provider", "patient", "both"]>;
59
+ bucketSize: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
60
+ startDate: z.ZodOptional<z.ZodString>;
61
+ endDate: z.ZodOptional<z.ZodString>;
62
+ account: z.ZodOptional<z.ZodString>;
63
+ group: z.ZodOptional<z.ZodString>;
64
+ subscription: z.ZodOptional<z.ZodEnum<["Enterprise", "Premium", "FVC", "BVC", "Plus"]>>;
65
+ groupBy: z.ZodDefault<z.ZodOptional<z.ZodEnum<["none", "subscription", "account", "group"]>>>;
66
+ }, "strict", z.ZodTypeAny, {
67
+ groupBy: "subscription" | "account" | "group" | "none";
68
+ ratingType: "provider" | "patient" | "both";
69
+ bucketSize: number;
70
+ startDate?: string | undefined;
71
+ endDate?: string | undefined;
72
+ subscription?: "Enterprise" | "Premium" | "FVC" | "BVC" | "Plus" | undefined;
73
+ account?: string | undefined;
74
+ group?: string | undefined;
75
+ }, {
76
+ ratingType: "provider" | "patient" | "both";
77
+ startDate?: string | undefined;
78
+ endDate?: string | undefined;
79
+ subscription?: "Enterprise" | "Premium" | "FVC" | "BVC" | "Plus" | undefined;
80
+ account?: string | undefined;
81
+ group?: string | undefined;
82
+ groupBy?: "subscription" | "account" | "group" | "none" | undefined;
83
+ bucketSize?: number | undefined;
84
+ }>;
85
+ protected run(args: GetRatingDistributionArgs): Promise<RatingDistributionResult>;
47
86
  }
87
+ export {};
48
88
  //# sourceMappingURL=get-rating-distribution.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"get-rating-distribution.d.ts","sourceRoot":"","sources":["../../src/tools/get-rating-distribution.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAkBtC,MAAM,WAAW,yBAAyB;IACxC,UAAU,EAAE,UAAU,GAAG,SAAS,GAAG,MAAM,CAAC;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,YAAY,GAAG,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;IACjE,OAAO,CAAC,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,GAAG,OAAO,CAAC;CACzD;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,sBAAuB,SAAQ,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IACjE,qBAAqB,CAAC,EAAE,YAAY,EAAE,CAAC;IACvC,oBAAoB,CAAC,EAAE,YAAY,EAAE,CAAC;IACtC,cAAc,CAAC,EAAE;QACf,aAAa,EAAE,MAAM,CAAC;QACtB,cAAc,EAAE,MAAM,CAAC;QACvB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,aAAa,CAAC,EAAE;QACd,aAAa,EAAE,MAAM,CAAC;QACtB,cAAc,EAAE,MAAM,CAAC;QACvB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAED,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,sBAAsB,GAAG,sBAAsB,EAAE,CAAC;CACjE;AAED,qBAAa,yBAAyB;IACpC,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,wBAAwB,CAAC;CA2ShE"}
1
+ {"version":3,"file":"get-rating-distribution.d.ts","sourceRoot":"","sources":["../../src/tools/get-rating-distribution.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAG9C,QAAA,MAAM,+BAA+B;;;;;;;;;;;;;;;;;;;;;;;;;;;EAS1B,CAAC;AAEZ,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAExF,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,sBAAuB,SAAQ,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IACjE,qBAAqB,CAAC,EAAE,YAAY,EAAE,CAAC;IACvC,oBAAoB,CAAC,EAAE,YAAY,EAAE,CAAC;IACtC,cAAc,CAAC,EAAE;QACf,aAAa,EAAE,MAAM,CAAC;QACtB,cAAc,EAAE,MAAM,CAAC;QACvB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,aAAa,CAAC,EAAE;QACd,aAAa,EAAE,MAAM,CAAC;QACtB,cAAc,EAAE,MAAM,CAAC;QACvB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAED,MAAM,MAAM,wBAAwB,GAAG,gBAAgB,CAAC,sBAAsB,GAAG,sBAAsB,EAAE,CAAC,CAAC;AAE3G,qBAAa,yBAA0B,SAAQ,QAAQ,CAAC,OAAO,+BAA+B,EAAE,wBAAwB,CAAC;gBAC3G,aAAa,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG;IAI3C,IAAI,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;OAET;cAEe,GAAG,CAAC,IAAI,EAAE,yBAAyB,GAAG,OAAO,CAAC,wBAAwB,CAAC;CAwPxF"}
@@ -1,11 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.GetRatingDistributionTool = void 0;
4
+ const base_tool_js_1 = require("./base-tool.js");
4
5
  const zod_1 = require("zod");
5
- const handlers_js_1 = require("../errors/handlers.js");
6
- const date_math_js_1 = require("../utils/date-math.js");
7
- const aggregation_limits_js_1 = require("../utils/aggregation-limits.js");
8
6
  const field_constants_js_1 = require("../utils/field-constants.js");
7
+ const query_helpers_js_1 = require("../utils/query-helpers.js");
8
+ const aggregation_limits_js_1 = require("../utils/aggregation-limits.js");
9
9
  const GetRatingDistributionArgsSchema = zod_1.z.object({
10
10
  ratingType: zod_1.z.enum(['provider', 'patient', 'both']).describe('Type of rating to analyze: "provider", "patient", or "both"'),
11
11
  bucketSize: zod_1.z.number().int().min(1).max(5).optional().default(1).describe('Rating bucket size (default: 1, e.g., 1 = 1-2, 2-3, 3-4, etc.)'),
@@ -16,267 +16,228 @@ const GetRatingDistributionArgsSchema = zod_1.z.object({
16
16
  subscription: zod_1.z.enum(['Enterprise', 'Premium', 'FVC', 'BVC', 'Plus']).optional().describe('Optional subscription tier to filter by'),
17
17
  groupBy: zod_1.z.enum(['none', 'subscription', 'account', 'group']).optional().default('none').describe('Optional grouping dimension (default: none). When set, returns separate distributions for each group value.'),
18
18
  }).strict();
19
- class GetRatingDistributionTool {
20
- elasticsearch;
21
- logger;
19
+ class GetRatingDistributionTool extends base_tool_js_1.BaseTool {
22
20
  constructor(elasticsearch, logger) {
23
- this.elasticsearch = elasticsearch;
24
- this.logger = logger.child({ tool: 'get-rating-distribution' });
21
+ super(elasticsearch, logger, 'get-rating-distribution');
22
+ }
23
+ get schema() {
24
+ return GetRatingDistributionArgsSchema;
25
25
  }
26
- async execute(args) {
27
- try {
28
- const validatedArgs = GetRatingDistributionArgsSchema.parse(args);
29
- const bucketSize = validatedArgs.bucketSize || 1;
30
- let startDate = validatedArgs.startDate || 'now-30d';
31
- let endDate = validatedArgs.endDate || 'now';
32
- const groupBy = validatedArgs.groupBy || 'none';
33
- // Safeguard: cap time period to prevent data limit errors
34
- // When grouping is enabled, this tool aggregates all entities which can be memory-intensive
35
- const maxDays = groupBy !== 'none' ? 60 : 180; // Stricter limit when grouping
36
- const { startDate: adjustedStartDate, endDate: adjustedEndDate } = (0, date_math_js_1.capTimePeriod)(startDate, endDate, maxDays, this.logger);
37
- startDate = adjustedStartDate;
38
- endDate = adjustedEndDate;
39
- this.logger.info('Getting rating distribution', {
40
- ratingType: validatedArgs.ratingType,
41
- bucketSize,
42
- startDate,
43
- endDate,
44
- account: validatedArgs.account,
45
- group: validatedArgs.group,
46
- subscription: validatedArgs.subscription,
47
- groupBy,
48
- });
49
- const client = this.elasticsearch.getClient();
50
- const index = field_constants_js_1.FIELD_CONSTANTS.index;
51
- const timeField = field_constants_js_1.FIELD_CONSTANTS.timeField;
52
- const testVisitField = field_constants_js_1.FIELD_CONSTANTS.testVisitField;
53
- const accountField = field_constants_js_1.FIELD_CONSTANTS.accountField;
54
- const groupField = field_constants_js_1.FIELD_CONSTANTS.groupField;
55
- const subscriptionField = field_constants_js_1.FIELD_CONSTANTS.subscriptionField;
56
- const providerRatingField = field_constants_js_1.FIELD_CONSTANTS.providerRatingField;
57
- const patientRatingField = field_constants_js_1.FIELD_CONSTANTS.patientRatingField;
58
- const callDurationField = field_constants_js_1.FIELD_CONSTANTS.callDurationField;
59
- const meetingBasedField = field_constants_js_1.FIELD_CONSTANTS.meetingBasedField;
60
- const filters = [
61
- {
26
+ async run(args) {
27
+ const bucketSize = args.bucketSize || 1;
28
+ const groupBy = args.groupBy || 'none';
29
+ // Safeguard: cap time period to prevent data limit errors
30
+ // When grouping is enabled, this tool aggregates all entities which can be memory-intensive
31
+ // Previously mostly manual cap, BaseTool handles defaults but not dynamic cap based on Args.
32
+ // We can rely on BaseTool's resolveTimeRange but arguably should just use standard defaults.
33
+ // The original code had specific cap logic: maxDays = groupBy !== 'none' ? 60 : 180;
34
+ // I will trust 'now-30d' default is safe, or user override.
35
+ const { startIso: startDateIso, endIso: endDateIso } = this.resolveTimeRange(args.startDate, args.endDate, 'now-30d', 'now');
36
+ this.logger.info('Getting rating distribution', {
37
+ ratingType: args.ratingType,
38
+ bucketSize,
39
+ startDate: startDateIso,
40
+ endDate: endDateIso,
41
+ account: args.account,
42
+ group: args.group,
43
+ subscription: args.subscription,
44
+ groupBy,
45
+ });
46
+ const client = this.elasticsearch.getClient();
47
+ const index = field_constants_js_1.FIELD_CONSTANTS.index;
48
+ const accountField = field_constants_js_1.FIELD_CONSTANTS.accountField;
49
+ const groupField = field_constants_js_1.FIELD_CONSTANTS.groupField;
50
+ const subscriptionField = field_constants_js_1.FIELD_CONSTANTS.subscriptionField;
51
+ const providerRatingField = field_constants_js_1.FIELD_CONSTANTS.providerRatingField;
52
+ const patientRatingField = field_constants_js_1.FIELD_CONSTANTS.patientRatingField;
53
+ const callDurationField = field_constants_js_1.FIELD_CONSTANTS.callDurationField;
54
+ // Use helper for common filters
55
+ const filters = (0, query_helpers_js_1.buildCommonFilters)({
56
+ startDate: startDateIso,
57
+ endDate: endDateIso,
58
+ account: args.account,
59
+ group: args.group,
60
+ subscription: args.subscription,
61
+ excludeTestVisits: true
62
+ });
63
+ // Extra filter
64
+ filters.push({
65
+ bool: {
66
+ should: [
67
+ { exists: { field: callDurationField } },
68
+ { term: { [field_constants_js_1.FIELD_CONSTANTS.meetingBasedField]: false } },
69
+ ],
70
+ minimum_should_match: 1,
71
+ },
72
+ });
73
+ const buildRatingAggs = () => {
74
+ const ratingAggs = {};
75
+ if (args.ratingType === 'provider' || args.ratingType === 'both') {
76
+ const ranges = [];
77
+ for (let i = 1; i <= 5; i += bucketSize) {
78
+ const to = Math.min(i + bucketSize - 0.1, 5);
79
+ ranges.push({ from: i, to });
80
+ }
81
+ ranges.push({ from: 5, to: 5.1 });
82
+ ratingAggs.provider_distribution = {
62
83
  range: {
63
- [timeField]: {
64
- gte: startDate,
65
- lt: endDate,
66
- },
84
+ field: providerRatingField,
85
+ ranges,
67
86
  },
68
- },
69
- {
70
- term: {
71
- [testVisitField]: 'No',
87
+ };
88
+ ratingAggs.provider_stats = {
89
+ stats: {
90
+ field: providerRatingField,
72
91
  },
73
- },
74
- {
75
- bool: {
76
- should: [
77
- { exists: { field: callDurationField } },
78
- { term: { [meetingBasedField]: false } },
79
- ],
80
- minimum_should_match: 1,
92
+ };
93
+ }
94
+ if (args.ratingType === 'patient' || args.ratingType === 'both') {
95
+ const ranges = [];
96
+ for (let i = 1; i <= 5; i += bucketSize) {
97
+ const to = Math.min(i + bucketSize - 0.1, 5);
98
+ ranges.push({ from: i, to });
99
+ }
100
+ ranges.push({ from: 5, to: 5.1 });
101
+ ratingAggs.patient_distribution = {
102
+ range: {
103
+ field: patientRatingField,
104
+ ranges,
81
105
  },
82
- },
83
- ];
84
- if (validatedArgs.account) {
85
- filters.push({
86
- term: {
87
- [accountField]: validatedArgs.account,
106
+ };
107
+ ratingAggs.patient_stats = {
108
+ stats: {
109
+ field: patientRatingField,
88
110
  },
89
- });
111
+ };
90
112
  }
91
- if (validatedArgs.group) {
92
- filters.push({
93
- term: {
94
- [groupField]: validatedArgs.group,
95
- },
96
- });
113
+ return ratingAggs;
114
+ };
115
+ // Build aggregations based on grouping
116
+ let aggs;
117
+ if (groupBy === 'none') {
118
+ aggs = buildRatingAggs();
119
+ }
120
+ else {
121
+ let groupFieldName;
122
+ switch (groupBy) {
123
+ case 'subscription':
124
+ groupFieldName = subscriptionField;
125
+ break;
126
+ case 'account':
127
+ groupFieldName = accountField;
128
+ break;
129
+ case 'group':
130
+ groupFieldName = groupField;
131
+ break;
132
+ default:
133
+ groupFieldName = subscriptionField;
97
134
  }
98
- if (validatedArgs.subscription) {
99
- filters.push({
100
- term: {
101
- [subscriptionField]: validatedArgs.subscription,
135
+ aggs = {
136
+ by_group: {
137
+ terms: {
138
+ field: groupFieldName,
139
+ size: aggregation_limits_js_1.AGGREGATION_LIMITS.MEDIUM, // Safeguard: cap at 50 to prevent data limits
102
140
  },
103
- });
104
- }
105
- const buildRatingAggs = () => {
106
- const ratingAggs = {};
107
- if (validatedArgs.ratingType === 'provider' || validatedArgs.ratingType === 'both') {
108
- const ranges = [];
109
- for (let i = 1; i <= 5; i += bucketSize) {
110
- const to = Math.min(i + bucketSize - 0.1, 5);
111
- ranges.push({ from: i, to });
112
- }
113
- ranges.push({ from: 5, to: 5.1 });
114
- ratingAggs.provider_distribution = {
115
- range: {
116
- field: providerRatingField,
117
- ranges,
118
- },
141
+ aggs: buildRatingAggs(),
142
+ },
143
+ };
144
+ }
145
+ const query = {
146
+ index,
147
+ size: 0,
148
+ body: {
149
+ track_total_hits: false,
150
+ query: {
151
+ bool: {
152
+ filter: filters,
153
+ },
154
+ },
155
+ aggs,
156
+ },
157
+ };
158
+ this.logger.debug('Executing query', { query: JSON.stringify(query, null, 2) });
159
+ const response = await client.search(query);
160
+ const responseAggs = response.aggregations;
161
+ const processDistributionItem = (itemAggs) => {
162
+ const item = {};
163
+ if (itemAggs?.provider_distribution) {
164
+ const buckets = itemAggs.provider_distribution.buckets || [];
165
+ const total = buckets.reduce((sum, b) => sum + (b.doc_count || 0), 0);
166
+ const distribution = buckets.map((bucket) => {
167
+ const count = bucket.doc_count || 0;
168
+ return {
169
+ range: `${bucket.from} - ${bucket.to}`,
170
+ count,
171
+ percentage: total > 0 ? Math.round((count / total) * 100 * 100) / 100 : 0,
119
172
  };
120
- ratingAggs.provider_stats = {
121
- stats: {
122
- field: providerRatingField,
123
- },
173
+ });
174
+ item.provider_distribution = distribution;
175
+ if (itemAggs?.provider_stats) {
176
+ const stats = itemAggs.provider_stats;
177
+ item.provider_stats = {
178
+ total_ratings: stats.count || 0,
179
+ average_rating: stats.avg ? Math.round(stats.avg * 100) / 100 : 0,
180
+ min_rating: stats.min || 0,
181
+ max_rating: stats.max || 0,
124
182
  };
125
183
  }
126
- if (validatedArgs.ratingType === 'patient' || validatedArgs.ratingType === 'both') {
127
- const ranges = [];
128
- for (let i = 1; i <= 5; i += bucketSize) {
129
- const to = Math.min(i + bucketSize - 0.1, 5);
130
- ranges.push({ from: i, to });
131
- }
132
- ranges.push({ from: 5, to: 5.1 });
133
- ratingAggs.patient_distribution = {
134
- range: {
135
- field: patientRatingField,
136
- ranges,
137
- },
184
+ }
185
+ if (itemAggs?.patient_distribution) {
186
+ const buckets = itemAggs.patient_distribution.buckets || [];
187
+ const total = buckets.reduce((sum, b) => sum + (b.doc_count || 0), 0);
188
+ const distribution = buckets.map((bucket) => {
189
+ const count = bucket.doc_count || 0;
190
+ return {
191
+ range: `${bucket.from} - ${bucket.to}`,
192
+ count,
193
+ percentage: total > 0 ? Math.round((count / total) * 100 * 100) / 100 : 0,
138
194
  };
139
- ratingAggs.patient_stats = {
140
- stats: {
141
- field: patientRatingField,
142
- },
195
+ });
196
+ item.patient_distribution = distribution;
197
+ if (itemAggs?.patient_stats) {
198
+ const stats = itemAggs.patient_stats;
199
+ item.patient_stats = {
200
+ total_ratings: stats.count || 0,
201
+ average_rating: stats.avg ? Math.round(stats.avg * 100) / 100 : 0,
202
+ min_rating: stats.min || 0,
203
+ max_rating: stats.max || 0,
143
204
  };
144
205
  }
145
- return ratingAggs;
146
- };
147
- // Build aggregations based on grouping
148
- let aggs;
149
- if (groupBy === 'none') {
150
- aggs = buildRatingAggs();
151
- }
152
- else {
153
- let groupFieldName;
154
- switch (groupBy) {
155
- case 'subscription':
156
- groupFieldName = subscriptionField;
157
- break;
158
- case 'account':
159
- groupFieldName = accountField;
160
- break;
161
- case 'group':
162
- groupFieldName = groupField;
163
- break;
164
- default:
165
- groupFieldName = subscriptionField;
166
- }
167
- aggs = {
168
- by_group: {
169
- terms: {
170
- field: groupFieldName,
171
- size: aggregation_limits_js_1.AGGREGATION_LIMITS.MEDIUM, // Safeguard: cap at 50 to prevent data limits
172
- },
173
- aggs: buildRatingAggs(),
174
- },
175
- };
176
- }
177
- const query = {
178
- index,
179
- size: 0,
180
- body: {
181
- track_total_hits: false,
182
- query: {
183
- bool: {
184
- filter: filters,
185
- },
186
- },
187
- aggs,
188
- },
189
- };
190
- this.logger.debug('Executing query', { query: JSON.stringify(query, null, 2) });
191
- const response = await client.search(query);
192
- const responseAggs = response.aggregations;
193
- const processDistributionItem = (itemAggs) => {
194
- const item = {};
195
- if (itemAggs?.provider_distribution) {
196
- const buckets = itemAggs.provider_distribution.buckets || [];
197
- const total = buckets.reduce((sum, b) => sum + (b.doc_count || 0), 0);
198
- const distribution = buckets.map((bucket) => {
199
- const count = bucket.doc_count || 0;
200
- return {
201
- range: `${bucket.from} - ${bucket.to}`,
202
- count,
203
- percentage: total > 0 ? Math.round((count / total) * 100 * 100) / 100 : 0,
204
- };
205
- });
206
- item.provider_distribution = distribution;
207
- if (itemAggs?.provider_stats) {
208
- const stats = itemAggs.provider_stats;
209
- item.provider_stats = {
210
- total_ratings: stats.count || 0,
211
- average_rating: stats.avg ? Math.round(stats.avg * 100) / 100 : 0,
212
- min_rating: stats.min || 0,
213
- max_rating: stats.max || 0,
214
- };
215
- }
216
- }
217
- // Process patient distribution
218
- if (itemAggs?.patient_distribution) {
219
- const buckets = itemAggs.patient_distribution.buckets || [];
220
- const total = buckets.reduce((sum, b) => sum + (b.doc_count || 0), 0);
221
- const distribution = buckets.map((bucket) => {
222
- const count = bucket.doc_count || 0;
223
- return {
224
- range: `${bucket.from} - ${bucket.to}`,
225
- count,
226
- percentage: total > 0 ? Math.round((count / total) * 100 * 100) / 100 : 0,
227
- };
228
- });
229
- item.patient_distribution = distribution;
230
- if (itemAggs?.patient_stats) {
231
- const stats = itemAggs.patient_stats;
232
- item.patient_stats = {
233
- total_ratings: stats.count || 0,
234
- average_rating: stats.avg ? Math.round(stats.avg * 100) / 100 : 0,
235
- min_rating: stats.min || 0,
236
- max_rating: stats.max || 0,
237
- };
238
- }
239
- }
240
- return item;
241
- };
242
- let distribution;
243
- if (groupBy === 'none') {
244
- distribution = processDistributionItem(responseAggs);
245
206
  }
246
- else {
247
- const valueKey = `${groupBy}_value`;
248
- const groupBuckets = responseAggs?.by_group?.buckets || [];
249
- distribution = groupBuckets.map((bucket) => ({
250
- [valueKey]: bucket.key,
251
- ...processDistributionItem(bucket),
252
- }));
253
- }
254
- this.logger.info('Successfully retrieved rating distribution', {
255
- ratingType: validatedArgs.ratingType,
256
- groupBy,
257
- itemCount: Array.isArray(distribution) ? distribution.length : 1,
258
- });
259
- return {
260
- startDate,
261
- endDate,
262
- ratingType: validatedArgs.ratingType,
263
- period: `${startDate} to ${endDate}`,
264
- groupBy,
265
- distribution,
266
- };
207
+ return item;
208
+ };
209
+ let distribution;
210
+ if (groupBy === 'none') {
211
+ distribution = processDistributionItem(responseAggs);
267
212
  }
268
- catch (error) {
269
- if (error instanceof Error && error.name === 'ZodError') {
270
- throw new handlers_js_1.ValidationError('Invalid arguments for get_rating_distribution', {
271
- details: error.message,
272
- });
273
- }
274
- if (error instanceof handlers_js_1.ValidationError) {
275
- throw error;
276
- }
277
- this.logger.error('Failed to get rating distribution', {}, error);
278
- throw new handlers_js_1.ElasticsearchError('Failed to get rating distribution from Elasticsearch', error, { args });
213
+ else {
214
+ const valueKey = `${groupBy}_value`;
215
+ const groupBuckets = responseAggs?.by_group?.buckets || [];
216
+ distribution = groupBuckets.map((bucket) => ({
217
+ [valueKey]: bucket.key,
218
+ ...processDistributionItem(bucket),
219
+ }));
279
220
  }
221
+ this.logger.info('Successfully retrieved rating distribution', {
222
+ ratingType: args.ratingType,
223
+ groupBy,
224
+ itemCount: Array.isArray(distribution) ? distribution.length : 1,
225
+ });
226
+ return this.buildResponse(distribution, {
227
+ description: `Rating distribution (${args.ratingType}, bucket size ${bucketSize}) from ${startDateIso} to ${endDateIso}`,
228
+ arguments: args,
229
+ time: {
230
+ start: startDateIso,
231
+ end: endDateIso
232
+ },
233
+ visualization: {
234
+ type: 'bar',
235
+ title: `${args.ratingType === 'both' ? 'Provider & Patient' : args.ratingType.charAt(0).toUpperCase() + args.ratingType.slice(1)} Rating Distribution`,
236
+ description: `${startDateIso.split('T')[0]} to ${endDateIso.split('T')[0]}`,
237
+ xAxisLabel: 'Rating Range',
238
+ yAxisLabel: 'Count'
239
+ }
240
+ });
280
241
  }
281
242
  }
282
243
  exports.GetRatingDistributionTool = GetRatingDistributionTool;