@butlr/butlr-mcp-server 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.
Files changed (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +195 -0
  3. package/dist/cache/occupancy-cache.d.ts +72 -0
  4. package/dist/cache/occupancy-cache.d.ts.map +1 -0
  5. package/dist/cache/occupancy-cache.js +166 -0
  6. package/dist/cache/occupancy-cache.js.map +1 -0
  7. package/dist/cache/topology-cache.d.ts +36 -0
  8. package/dist/cache/topology-cache.d.ts.map +1 -0
  9. package/dist/cache/topology-cache.js +74 -0
  10. package/dist/cache/topology-cache.js.map +1 -0
  11. package/dist/clients/auth-client.d.ts +22 -0
  12. package/dist/clients/auth-client.d.ts.map +1 -0
  13. package/dist/clients/auth-client.js +82 -0
  14. package/dist/clients/auth-client.js.map +1 -0
  15. package/dist/clients/graphql-client.d.ts +6 -0
  16. package/dist/clients/graphql-client.d.ts.map +1 -0
  17. package/dist/clients/graphql-client.js +106 -0
  18. package/dist/clients/graphql-client.js.map +1 -0
  19. package/dist/clients/queries/topology.d.ts +36 -0
  20. package/dist/clients/queries/topology.d.ts.map +1 -0
  21. package/dist/clients/queries/topology.js +252 -0
  22. package/dist/clients/queries/topology.js.map +1 -0
  23. package/dist/clients/reporting-client.d.ts +191 -0
  24. package/dist/clients/reporting-client.d.ts.map +1 -0
  25. package/dist/clients/reporting-client.js +353 -0
  26. package/dist/clients/reporting-client.js.map +1 -0
  27. package/dist/clients/stats-client.d.ts +119 -0
  28. package/dist/clients/stats-client.d.ts.map +1 -0
  29. package/dist/clients/stats-client.js +238 -0
  30. package/dist/clients/stats-client.js.map +1 -0
  31. package/dist/clients/types.d.ts +215 -0
  32. package/dist/clients/types.d.ts.map +1 -0
  33. package/dist/clients/types.js +6 -0
  34. package/dist/clients/types.js.map +1 -0
  35. package/dist/constants.d.ts +3 -0
  36. package/dist/constants.d.ts.map +1 -0
  37. package/dist/constants.js +3 -0
  38. package/dist/constants.js.map +1 -0
  39. package/dist/errors/mcp-errors.d.ts +63 -0
  40. package/dist/errors/mcp-errors.d.ts.map +1 -0
  41. package/dist/errors/mcp-errors.js +144 -0
  42. package/dist/errors/mcp-errors.js.map +1 -0
  43. package/dist/index.d.ts +3 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +43 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/tools/butlr-available-rooms.d.ts +49 -0
  48. package/dist/tools/butlr-available-rooms.d.ts.map +1 -0
  49. package/dist/tools/butlr-available-rooms.js +492 -0
  50. package/dist/tools/butlr-available-rooms.js.map +1 -0
  51. package/dist/tools/butlr-fetch-entity-details.d.ts +42 -0
  52. package/dist/tools/butlr-fetch-entity-details.d.ts.map +1 -0
  53. package/dist/tools/butlr-fetch-entity-details.js +276 -0
  54. package/dist/tools/butlr-fetch-entity-details.js.map +1 -0
  55. package/dist/tools/butlr-get-asset-details.d.ts +51 -0
  56. package/dist/tools/butlr-get-asset-details.d.ts.map +1 -0
  57. package/dist/tools/butlr-get-asset-details.js +391 -0
  58. package/dist/tools/butlr-get-asset-details.js.map +1 -0
  59. package/dist/tools/butlr-get-current-occupancy.d.ts +21 -0
  60. package/dist/tools/butlr-get-current-occupancy.d.ts.map +1 -0
  61. package/dist/tools/butlr-get-current-occupancy.js +126 -0
  62. package/dist/tools/butlr-get-current-occupancy.js.map +1 -0
  63. package/dist/tools/butlr-get-occupancy-timeseries.d.ts +31 -0
  64. package/dist/tools/butlr-get-occupancy-timeseries.d.ts.map +1 -0
  65. package/dist/tools/butlr-get-occupancy-timeseries.js +145 -0
  66. package/dist/tools/butlr-get-occupancy-timeseries.js.map +1 -0
  67. package/dist/tools/butlr-hardware-snapshot.d.ts +55 -0
  68. package/dist/tools/butlr-hardware-snapshot.d.ts.map +1 -0
  69. package/dist/tools/butlr-hardware-snapshot.js +556 -0
  70. package/dist/tools/butlr-hardware-snapshot.js.map +1 -0
  71. package/dist/tools/butlr-list-topology.d.ts +27 -0
  72. package/dist/tools/butlr-list-topology.d.ts.map +1 -0
  73. package/dist/tools/butlr-list-topology.js +241 -0
  74. package/dist/tools/butlr-list-topology.js.map +1 -0
  75. package/dist/tools/butlr-search-assets.d.ts +53 -0
  76. package/dist/tools/butlr-search-assets.d.ts.map +1 -0
  77. package/dist/tools/butlr-search-assets.js +206 -0
  78. package/dist/tools/butlr-search-assets.js.map +1 -0
  79. package/dist/tools/butlr-space-busyness.d.ts +23 -0
  80. package/dist/tools/butlr-space-busyness.d.ts.map +1 -0
  81. package/dist/tools/butlr-space-busyness.js +304 -0
  82. package/dist/tools/butlr-space-busyness.js.map +1 -0
  83. package/dist/tools/butlr-traffic-flow.d.ts +39 -0
  84. package/dist/tools/butlr-traffic-flow.d.ts.map +1 -0
  85. package/dist/tools/butlr-traffic-flow.js +369 -0
  86. package/dist/tools/butlr-traffic-flow.js.map +1 -0
  87. package/dist/types/responses.d.ts +253 -0
  88. package/dist/types/responses.d.ts.map +1 -0
  89. package/dist/types/responses.js +8 -0
  90. package/dist/types/responses.js.map +1 -0
  91. package/dist/utils/asset-flattener.d.ts +50 -0
  92. package/dist/utils/asset-flattener.d.ts.map +1 -0
  93. package/dist/utils/asset-flattener.js +131 -0
  94. package/dist/utils/asset-flattener.js.map +1 -0
  95. package/dist/utils/asset-helpers.d.ts +8 -0
  96. package/dist/utils/asset-helpers.d.ts.map +1 -0
  97. package/dist/utils/asset-helpers.js +24 -0
  98. package/dist/utils/asset-helpers.js.map +1 -0
  99. package/dist/utils/field-validator.d.ts +29 -0
  100. package/dist/utils/field-validator.d.ts.map +1 -0
  101. package/dist/utils/field-validator.js +197 -0
  102. package/dist/utils/field-validator.js.map +1 -0
  103. package/dist/utils/fuzzy-match.d.ts +29 -0
  104. package/dist/utils/fuzzy-match.d.ts.map +1 -0
  105. package/dist/utils/fuzzy-match.js +70 -0
  106. package/dist/utils/fuzzy-match.js.map +1 -0
  107. package/dist/utils/graphql-helpers.d.ts +29 -0
  108. package/dist/utils/graphql-helpers.d.ts.map +1 -0
  109. package/dist/utils/graphql-helpers.js +43 -0
  110. package/dist/utils/graphql-helpers.js.map +1 -0
  111. package/dist/utils/natural-language.d.ts +73 -0
  112. package/dist/utils/natural-language.d.ts.map +1 -0
  113. package/dist/utils/natural-language.js +172 -0
  114. package/dist/utils/natural-language.js.map +1 -0
  115. package/dist/utils/occupancy-helpers.d.ts +63 -0
  116. package/dist/utils/occupancy-helpers.d.ts.map +1 -0
  117. package/dist/utils/occupancy-helpers.js +203 -0
  118. package/dist/utils/occupancy-helpers.js.map +1 -0
  119. package/dist/utils/path-builder.d.ts +10 -0
  120. package/dist/utils/path-builder.d.ts.map +1 -0
  121. package/dist/utils/path-builder.js +34 -0
  122. package/dist/utils/path-builder.js.map +1 -0
  123. package/dist/utils/time-range-validator.d.ts +13 -0
  124. package/dist/utils/time-range-validator.d.ts.map +1 -0
  125. package/dist/utils/time-range-validator.js +65 -0
  126. package/dist/utils/time-range-validator.js.map +1 -0
  127. package/dist/utils/timezone-helpers.d.ts +49 -0
  128. package/dist/utils/timezone-helpers.d.ts.map +1 -0
  129. package/dist/utils/timezone-helpers.js +209 -0
  130. package/dist/utils/timezone-helpers.js.map +1 -0
  131. package/dist/utils/tree-formatter.d.ts +23 -0
  132. package/dist/utils/tree-formatter.d.ts.map +1 -0
  133. package/dist/utils/tree-formatter.js +258 -0
  134. package/dist/utils/tree-formatter.js.map +1 -0
  135. package/package.json +93 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graphql-helpers.js","sourceRoot":"","sources":["../../src/utils/graphql-helpers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAEhF;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAc;IAClD,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,eAAe,IAAI,KAAK,IAAI,cAAc,IAAI,KAAK,CAAC,EAAE,CAAC;QAChG,MAAM,QAAQ,GAAG,qBAAqB,CAAC,KAAoD,CAAC,CAAC;QAC7F,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC5C,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAC/C,OAAO,CACL,CAAC,CAAC,MAAM,CAAC,WAAW;QACpB,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE;QAChC,CAAC,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,UAAU,CAAC;QAC1C,CAAC,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,OAAO,CAAC,CACxC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAU;IACzC,OAAO,CACL,CAAC,CAAC,IAAI,CAAC,YAAY;QACnB,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE;QAC/B,CAAC,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CACpD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Natural Language Utilities for Conversational Tools
3
+ *
4
+ * Generates qualitative labels, trend descriptions, and formatted summaries
5
+ * to make API responses more conversational and actionable.
6
+ */
7
+ /**
8
+ * Get qualitative occupancy label
9
+ * @param utilizationPercent - Occupancy as % of capacity (0-100+)
10
+ * @returns "quiet" | "moderate" | "busy"
11
+ */
12
+ export declare function getOccupancyLabel(utilizationPercent: number): "quiet" | "moderate" | "busy";
13
+ /**
14
+ * Get trend label compared to typical
15
+ * @param deltaPercent - Difference from typical (positive = busier, negative = quieter)
16
+ * @returns "lighter" | "typical" | "busier"
17
+ */
18
+ export declare function getTrendLabel(deltaPercent: number): "lighter" | "typical" | "busier";
19
+ /**
20
+ * Build a busyness summary string
21
+ * @example "Café: Moderate (12 people, 45% capacity, typical for Thursday 2pm)"
22
+ */
23
+ export declare function buildBusynessSummary(params: {
24
+ spaceName: string;
25
+ occupancy: number;
26
+ capacity: number;
27
+ utilizationPercent: number;
28
+ trendLabel?: "lighter" | "typical" | "busier";
29
+ dayTime?: string;
30
+ }): string;
31
+ /**
32
+ * Build an available rooms summary
33
+ * @example "5 conference rooms available (capacity 4-12 people)"
34
+ */
35
+ export declare function buildAvailableRoomsSummary(params: {
36
+ count: number;
37
+ roomType?: string;
38
+ minCapacity?: number;
39
+ maxCapacity?: number;
40
+ }): string;
41
+ /**
42
+ * Build a hardware health summary
43
+ * @example "45 of 52 sensors online (87%), 8 of 8 hives online (100%). 3 batteries critical, 7 due within 30 days."
44
+ */
45
+ export declare function buildHardwareSummary(params: {
46
+ sensorsOnline: number;
47
+ sensorsTotal: number;
48
+ hivesOnline: number;
49
+ hivesTotal: number;
50
+ batteriesCritical: number;
51
+ batteriesDueSoon: number;
52
+ }): string;
53
+ /**
54
+ * Get recommendation based on busyness
55
+ */
56
+ export declare function getBusinessRecommendation(label: "quiet" | "moderate" | "busy"): string;
57
+ /**
58
+ * Format day and time for context
59
+ * @param date - Date to format (defaults to now)
60
+ * @param timezone - IANA timezone name (e.g. "America/Los_Angeles"). When provided,
61
+ * formats in the space's local time instead of the server's system time.
62
+ * @example "Thursday 2pm"
63
+ */
64
+ export declare function formatDayAndTime(date?: Date, timezone?: string): string;
65
+ /**
66
+ * Calculate days between dates
67
+ */
68
+ export declare function daysBetween(date1: Date, date2: Date): number;
69
+ /**
70
+ * Calculate hours between dates
71
+ */
72
+ export declare function hoursBetween(date1: Date, date2: Date): number;
73
+ //# sourceMappingURL=natural-language.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"natural-language.d.ts","sourceRoot":"","sources":["../../src/utils/natural-language.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAkBH;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,kBAAkB,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,CAQ3F;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAUpF;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,MAAM,CAkBT;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,MAAM,CAsBT;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE;IAC3C,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;CAC1B,GAAG,MAAM,CAwBT;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,CAStF;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,GAAE,IAAiB,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CA2BnF;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,GAAG,MAAM,CAG5D;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,GAAG,MAAM,CAG7D"}
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Natural Language Utilities for Conversational Tools
3
+ *
4
+ * Generates qualitative labels, trend descriptions, and formatted summaries
5
+ * to make API responses more conversational and actionable.
6
+ */
7
+ /**
8
+ * Occupancy labels based on utilization percentage
9
+ * Thresholds can be configured via environment variables
10
+ */
11
+ const DEFAULT_QUIET_THRESHOLD = 30; // < 30% = quiet
12
+ const DEFAULT_BUSY_THRESHOLD = 70; // > 70% = busy
13
+ const QUIET_THRESHOLD = parseInt(process.env.BUTLR_QUIET_THRESHOLD || String(DEFAULT_QUIET_THRESHOLD), 10);
14
+ const BUSY_THRESHOLD = parseInt(process.env.BUTLR_BUSY_THRESHOLD || String(DEFAULT_BUSY_THRESHOLD), 10);
15
+ /**
16
+ * Get qualitative occupancy label
17
+ * @param utilizationPercent - Occupancy as % of capacity (0-100+)
18
+ * @returns "quiet" | "moderate" | "busy"
19
+ */
20
+ export function getOccupancyLabel(utilizationPercent) {
21
+ if (utilizationPercent < QUIET_THRESHOLD) {
22
+ return "quiet";
23
+ }
24
+ if (utilizationPercent < BUSY_THRESHOLD) {
25
+ return "moderate";
26
+ }
27
+ return "busy";
28
+ }
29
+ /**
30
+ * Get trend label compared to typical
31
+ * @param deltaPercent - Difference from typical (positive = busier, negative = quieter)
32
+ * @returns "lighter" | "typical" | "busier"
33
+ */
34
+ export function getTrendLabel(deltaPercent) {
35
+ const TREND_THRESHOLD = 15; // >15% change = notable
36
+ if (deltaPercent < -TREND_THRESHOLD) {
37
+ return "lighter";
38
+ }
39
+ if (deltaPercent > TREND_THRESHOLD) {
40
+ return "busier";
41
+ }
42
+ return "typical";
43
+ }
44
+ /**
45
+ * Build a busyness summary string
46
+ * @example "Café: Moderate (12 people, 45% capacity, typical for Thursday 2pm)"
47
+ */
48
+ export function buildBusynessSummary(params) {
49
+ const label = getOccupancyLabel(params.utilizationPercent);
50
+ const labelCapitalized = label.charAt(0).toUpperCase() + label.slice(1);
51
+ const parts = [
52
+ `${params.spaceName}: ${labelCapitalized}`,
53
+ `(${params.occupancy} people, ${Math.round(params.utilizationPercent)}% capacity`,
54
+ ];
55
+ if (params.trendLabel && params.trendLabel !== "typical") {
56
+ parts.push(`${params.trendLabel} than typical`);
57
+ }
58
+ else if (params.trendLabel === "typical" && params.dayTime) {
59
+ parts.push(`typical for ${params.dayTime}`);
60
+ }
61
+ parts[parts.length - 1] += ")"; // Close parenthesis
62
+ return parts.join(" ");
63
+ }
64
+ /**
65
+ * Build an available rooms summary
66
+ * @example "5 conference rooms available (capacity 4-12 people)"
67
+ */
68
+ export function buildAvailableRoomsSummary(params) {
69
+ const roomTypeStr = params.roomType ? `${params.roomType} ` : "";
70
+ if (params.count === 0) {
71
+ return `No ${roomTypeStr}rooms currently available`;
72
+ }
73
+ const parts = [];
74
+ if (params.count === 1) {
75
+ parts.push(`1 ${roomTypeStr}room available`);
76
+ }
77
+ else {
78
+ parts.push(`${params.count} ${roomTypeStr}rooms available`);
79
+ }
80
+ if (params.minCapacity && params.maxCapacity) {
81
+ parts.push(`(capacity ${params.minCapacity}-${params.maxCapacity} people)`);
82
+ }
83
+ else if (params.minCapacity) {
84
+ parts.push(`(capacity ${params.minCapacity}+ people)`);
85
+ }
86
+ return parts.join(" ");
87
+ }
88
+ /**
89
+ * Build a hardware health summary
90
+ * @example "45 of 52 sensors online (87%), 8 of 8 hives online (100%). 3 batteries critical, 7 due within 30 days."
91
+ */
92
+ export function buildHardwareSummary(params) {
93
+ const sensorsPercent = params.sensorsTotal > 0 ? Math.round((params.sensorsOnline / params.sensorsTotal) * 100) : 0;
94
+ const hivesPercent = params.hivesTotal > 0 ? Math.round((params.hivesOnline / params.hivesTotal) * 100) : 0;
95
+ const parts = [
96
+ `${params.sensorsOnline} of ${params.sensorsTotal} sensors online (${sensorsPercent}%)`,
97
+ `${params.hivesOnline} of ${params.hivesTotal} hives online (${hivesPercent}%)`,
98
+ ];
99
+ // Add battery health if applicable
100
+ if (params.batteriesCritical > 0 || params.batteriesDueSoon > 0) {
101
+ const batteryParts = [];
102
+ if (params.batteriesCritical > 0) {
103
+ batteryParts.push(`${params.batteriesCritical} batteries critical`);
104
+ }
105
+ if (params.batteriesDueSoon > 0) {
106
+ batteryParts.push(`${params.batteriesDueSoon} due within 30 days`);
107
+ }
108
+ parts.push(batteryParts.join(", "));
109
+ }
110
+ return parts.join(", ") + ".";
111
+ }
112
+ /**
113
+ * Get recommendation based on busyness
114
+ */
115
+ export function getBusinessRecommendation(label) {
116
+ switch (label) {
117
+ case "quiet":
118
+ return "Great time to visit - not crowded";
119
+ case "moderate":
120
+ return "Good time to visit - moderately busy";
121
+ case "busy":
122
+ return "Consider waiting or visiting later - very busy";
123
+ }
124
+ }
125
+ /**
126
+ * Format day and time for context
127
+ * @param date - Date to format (defaults to now)
128
+ * @param timezone - IANA timezone name (e.g. "America/Los_Angeles"). When provided,
129
+ * formats in the space's local time instead of the server's system time.
130
+ * @example "Thursday 2pm"
131
+ */
132
+ export function formatDayAndTime(date = new Date(), timezone) {
133
+ if (timezone) {
134
+ try {
135
+ const formatter = new Intl.DateTimeFormat("en-US", {
136
+ timeZone: timezone,
137
+ weekday: "long",
138
+ hour: "numeric",
139
+ hour12: true,
140
+ });
141
+ const parts = formatter.formatToParts(date);
142
+ const dayName = parts.find((p) => p.type === "weekday")?.value || "";
143
+ const hour = parts.find((p) => p.type === "hour")?.value || "";
144
+ const dayPeriod = parts.find((p) => p.type === "dayPeriod")?.value || "";
145
+ return `${dayName} ${hour}${dayPeriod.toLowerCase()}`;
146
+ }
147
+ catch {
148
+ // Fall through to system-local fallback
149
+ }
150
+ }
151
+ const days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
152
+ const dayName = days[date.getDay()];
153
+ let hours = date.getHours();
154
+ const ampm = hours >= 12 ? "pm" : "am";
155
+ hours = hours % 12 || 12;
156
+ return `${dayName} ${hours}${ampm}`;
157
+ }
158
+ /**
159
+ * Calculate days between dates
160
+ */
161
+ export function daysBetween(date1, date2) {
162
+ const diffMs = date2.getTime() - date1.getTime();
163
+ return Math.floor(diffMs / (1000 * 60 * 60 * 24));
164
+ }
165
+ /**
166
+ * Calculate hours between dates
167
+ */
168
+ export function hoursBetween(date1, date2) {
169
+ const diffMs = date2.getTime() - date1.getTime();
170
+ return Math.floor(diffMs / (1000 * 60 * 60));
171
+ }
172
+ //# sourceMappingURL=natural-language.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"natural-language.js","sourceRoot":"","sources":["../../src/utils/natural-language.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Shared helpers for occupancy tools (butlr_get_current_occupancy, butlr_get_occupancy_timeseries)
3
+ *
4
+ * Extracted from the two occupancy tools to eliminate ~200 lines of duplication.
5
+ * Both tools share: topology fetching, sensor filtering, asset resolution,
6
+ * measurement selection, and recommendation logic.
7
+ */
8
+ import type { Sensor, Site, Floor, Building } from "../clients/types.js";
9
+ import type { TimezoneMetadata } from "./timezone-helpers.js";
10
+ import type { MeasurementRecommendation, BaseMeasurementData } from "../types/responses.js";
11
+ /**
12
+ * Topology and sensor data fetched in parallel for occupancy tools.
13
+ */
14
+ export interface TopologyContext {
15
+ sites: Site[];
16
+ buildings: Building[];
17
+ floors: Floor[];
18
+ productionSensors: Sensor[];
19
+ }
20
+ /**
21
+ * Fetch topology and production sensors in a single parallel call.
22
+ * Shared by both current-occupancy and timeseries tools.
23
+ */
24
+ export declare function fetchTopologyAndSensors(): Promise<TopologyContext>;
25
+ /**
26
+ * Resolved context for a single asset, including timezone, name, and sensor partitioning.
27
+ */
28
+ export interface AssetContext {
29
+ assetType: "floor" | "room" | "zone";
30
+ assetName: string | undefined;
31
+ timezone: string;
32
+ tzMetadata: TimezoneMetadata;
33
+ presenceSensors: Sensor[];
34
+ trafficSensors: Sensor[];
35
+ }
36
+ /**
37
+ * Resolve full context for an asset ID: type validation, name, timezone, sensor partitioning.
38
+ */
39
+ export declare function resolveAssetContext(assetId: string, ctx: TopologyContext): AssetContext;
40
+ /**
41
+ * Get the measurement name for presence data based on asset type.
42
+ */
43
+ export declare function getPresenceMeasurement(assetType: "floor" | "room" | "zone"): string;
44
+ /**
45
+ * Get the measurement name for traffic data based on asset type.
46
+ */
47
+ export declare function getTrafficMeasurement(assetType: "floor" | "room"): string;
48
+ /**
49
+ * Build coverage note for presence measurement.
50
+ */
51
+ export declare function getPresenceCoverageNote(assetType: "floor" | "room" | "zone", sensorCount: number): string;
52
+ /**
53
+ * Build coverage note for traffic measurement.
54
+ */
55
+ export declare function getTrafficCoverageNote(assetType: "floor" | "room" | "zone", sensorCount: number): string;
56
+ /**
57
+ * Build measurement recommendation based on data availability AND query success.
58
+ *
59
+ * Unlike the previous implementation, this checks whether data was actually retrieved,
60
+ * not just whether sensors exist.
61
+ */
62
+ export declare function buildRecommendation(presence: BaseMeasurementData, traffic: BaseMeasurementData, presenceHasData: boolean, trafficHasData: boolean): MeasurementRecommendation;
63
+ //# sourceMappingURL=occupancy-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"occupancy-helpers.d.ts","sourceRoot":"","sources":["../../src/utils/occupancy-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,KAAK,EAAE,yBAAyB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAM5F;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,eAAe,CAAC,CA0BxE;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACrC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,eAAe,GAAG,YAAY,CAiEvF;AA4BD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CASnF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAOzE;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,EACpC,WAAW,EAAE,MAAM,GAClB,MAAM,CASR;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,EACpC,WAAW,EAAE,MAAM,GAClB,MAAM,CASR;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE,mBAAmB,EAC5B,eAAe,EAAE,OAAO,EACxB,cAAc,EAAE,OAAO,GACtB,yBAAyB,CA6B3B"}
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Shared helpers for occupancy tools (butlr_get_current_occupancy, butlr_get_occupancy_timeseries)
3
+ *
4
+ * Extracted from the two occupancy tools to eliminate ~200 lines of duplication.
5
+ * Both tools share: topology fetching, sensor filtering, asset resolution,
6
+ * measurement selection, and recommendation logic.
7
+ */
8
+ import { apolloClient } from "../clients/graphql-client.js";
9
+ import { GET_ALL_SENSORS, GET_FULL_TOPOLOGY } from "../clients/queries/topology.js";
10
+ import { detectAssetType } from "./asset-helpers.js";
11
+ import { isProductionSensor } from "./graphql-helpers.js";
12
+ import { getTimezoneForAsset, buildTimezoneMetadata } from "./timezone-helpers.js";
13
+ import { rethrowIfGraphQLError } from "./graphql-helpers.js";
14
+ /**
15
+ * Fetch topology and production sensors in a single parallel call.
16
+ * Shared by both current-occupancy and timeseries tools.
17
+ */
18
+ export async function fetchTopologyAndSensors() {
19
+ let topoResult, sensorsResult;
20
+ try {
21
+ [topoResult, sensorsResult] = await Promise.all([
22
+ apolloClient.query({
23
+ query: GET_FULL_TOPOLOGY,
24
+ fetchPolicy: "network-only",
25
+ }),
26
+ apolloClient.query({
27
+ query: GET_ALL_SENSORS,
28
+ fetchPolicy: "network-only",
29
+ }),
30
+ ]);
31
+ }
32
+ catch (error) {
33
+ rethrowIfGraphQLError(error);
34
+ throw error;
35
+ }
36
+ const sites = topoResult.data?.sites?.data || [];
37
+ const buildings = sites.flatMap((s) => s.buildings || []);
38
+ const floors = buildings.flatMap((b) => b.floors || []);
39
+ const allSensors = sensorsResult.data?.sensors?.data || [];
40
+ const productionSensors = allSensors.filter(isProductionSensor);
41
+ return { sites, buildings, floors, productionSensors };
42
+ }
43
+ /**
44
+ * Resolve full context for an asset ID: type validation, name, timezone, sensor partitioning.
45
+ */
46
+ export function resolveAssetContext(assetId, ctx) {
47
+ const assetType = detectAssetType(assetId);
48
+ if (!["floor", "room", "zone"].includes(assetType)) {
49
+ throw new Error(`Asset ${assetId} must be a floor, room, or zone. Got: ${assetType}`);
50
+ }
51
+ const typedAssetType = assetType;
52
+ // Resolve timezone
53
+ const timezone = getTimezoneForAsset(assetId, typedAssetType, ctx.floors, ctx.buildings, ctx.sites);
54
+ if (!timezone) {
55
+ throw new Error(`Could not determine timezone for asset ${assetId}`);
56
+ }
57
+ const tzMetadata = buildTimezoneMetadata(timezone);
58
+ // Resolve asset name
59
+ const assetName = findAssetName(assetId, typedAssetType, ctx.floors);
60
+ // Filter sensors for this asset
61
+ const assetSensors = ctx.productionSensors.filter((s) => {
62
+ const sensorFloorId = s.floor_id || s.floorID;
63
+ const sensorRoomId = s.room_id || s.roomID;
64
+ switch (typedAssetType) {
65
+ case "floor":
66
+ return sensorFloorId === assetId;
67
+ case "room":
68
+ return sensorRoomId === assetId;
69
+ case "zone":
70
+ return false; // Zones don't have direct sensor assignments
71
+ }
72
+ });
73
+ // Partition sensors by mode
74
+ const presenceSensors = assetSensors.filter((s) => s.mode === "presence");
75
+ let trafficSensors;
76
+ switch (typedAssetType) {
77
+ case "floor":
78
+ trafficSensors = assetSensors.filter((s) => s.mode === "traffic" && s.is_entrance === true);
79
+ break;
80
+ case "room":
81
+ trafficSensors = assetSensors.filter((s) => s.mode === "traffic" && s.is_entrance === false);
82
+ break;
83
+ default:
84
+ trafficSensors = [];
85
+ }
86
+ return {
87
+ assetType: typedAssetType,
88
+ assetName,
89
+ timezone,
90
+ tzMetadata,
91
+ presenceSensors,
92
+ trafficSensors,
93
+ };
94
+ }
95
+ /**
96
+ * Find the display name for an asset by searching the topology.
97
+ */
98
+ function findAssetName(assetId, assetType, floors) {
99
+ switch (assetType) {
100
+ case "floor":
101
+ return floors.find((f) => f.id === assetId)?.name;
102
+ case "room":
103
+ for (const floor of floors) {
104
+ const room = floor.rooms?.find((r) => r.id === assetId);
105
+ if (room)
106
+ return room.name;
107
+ }
108
+ return undefined;
109
+ case "zone":
110
+ for (const floor of floors) {
111
+ const zone = floor.zones?.find((z) => z.id === assetId);
112
+ if (zone)
113
+ return zone.name;
114
+ }
115
+ return undefined;
116
+ }
117
+ }
118
+ /**
119
+ * Get the measurement name for presence data based on asset type.
120
+ */
121
+ export function getPresenceMeasurement(assetType) {
122
+ switch (assetType) {
123
+ case "floor":
124
+ return "floor_occupancy";
125
+ case "room":
126
+ return "room_occupancy";
127
+ case "zone":
128
+ return "zone_occupancy";
129
+ }
130
+ }
131
+ /**
132
+ * Get the measurement name for traffic data based on asset type.
133
+ */
134
+ export function getTrafficMeasurement(assetType) {
135
+ switch (assetType) {
136
+ case "floor":
137
+ return "traffic_floor_occupancy";
138
+ case "room":
139
+ return "traffic_room_occupancy";
140
+ }
141
+ }
142
+ /**
143
+ * Build coverage note for presence measurement.
144
+ */
145
+ export function getPresenceCoverageNote(assetType, sensorCount) {
146
+ if (sensorCount === 0) {
147
+ return assetType === "zone"
148
+ ? "Zones support presence measurement only."
149
+ : `No presence sensors on this ${assetType}.`;
150
+ }
151
+ return assetType === "floor"
152
+ ? `Presence from ${sensorCount} sensors (may not cover entire floor).`
153
+ : `Presence from ${sensorCount} sensors.`;
154
+ }
155
+ /**
156
+ * Build coverage note for traffic measurement.
157
+ */
158
+ export function getTrafficCoverageNote(assetType, sensorCount) {
159
+ if (sensorCount === 0) {
160
+ if (assetType === "zone")
161
+ return "Zones do not support traffic.";
162
+ if (assetType === "floor")
163
+ return "No main entrance sensors.";
164
+ return "No traffic sensors.";
165
+ }
166
+ return assetType === "floor"
167
+ ? `Traffic from ${sensorCount} main entrance sensors.`
168
+ : `Traffic from ${sensorCount} sensors.`;
169
+ }
170
+ /**
171
+ * Build measurement recommendation based on data availability AND query success.
172
+ *
173
+ * Unlike the previous implementation, this checks whether data was actually retrieved,
174
+ * not just whether sensors exist.
175
+ */
176
+ export function buildRecommendation(presence, traffic, presenceHasData, trafficHasData) {
177
+ const presenceSucceeded = presence.available && presenceHasData && !presence.warning;
178
+ const trafficSucceeded = traffic.available && trafficHasData && !traffic.warning;
179
+ if (presenceSucceeded && trafficSucceeded) {
180
+ return {
181
+ recommended_measurement: "presence",
182
+ recommendation_reason: "Both available. Presence shows current occupants; traffic shows flow.",
183
+ };
184
+ }
185
+ if (presenceSucceeded) {
186
+ return {
187
+ recommended_measurement: "presence",
188
+ recommendation_reason: "Presence available (direct occupant count).",
189
+ };
190
+ }
191
+ if (trafficSucceeded) {
192
+ return {
193
+ recommended_measurement: "traffic",
194
+ recommendation_reason: "Traffic available (entry/exit counts).",
195
+ };
196
+ }
197
+ const failureReason = presence.warning || traffic.warning || "No occupancy data available.";
198
+ return {
199
+ recommended_measurement: "none",
200
+ recommendation_reason: failureReason,
201
+ };
202
+ }
203
+ //# sourceMappingURL=occupancy-helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"occupancy-helpers.js","sourceRoot":"","sources":["../../src/utils/occupancy-helpers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Utilities for building readable breadcrumb paths for assets
3
+ */
4
+ import type { FlattenedAsset } from "./asset-flattener.js";
5
+ /**
6
+ * Build a human-readable path for an asset
7
+ * Example: "Acme Corp / Main Building / Floor 1 / Conference Room A"
8
+ */
9
+ export declare function buildAssetPath(asset: FlattenedAsset): string;
10
+ //# sourceMappingURL=path-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-builder.d.ts","sourceRoot":"","sources":["../../src/utils/path-builder.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CA+B5D"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Utilities for building readable breadcrumb paths for assets
3
+ */
4
+ /**
5
+ * Build a human-readable path for an asset
6
+ * Example: "Acme Corp / Main Building / Floor 1 / Conference Room A"
7
+ */
8
+ export function buildAssetPath(asset) {
9
+ const parts = [];
10
+ // Add site
11
+ if (asset.site_name) {
12
+ parts.push(asset.site_name);
13
+ }
14
+ // Add building
15
+ if (asset.building_name) {
16
+ parts.push(asset.building_name);
17
+ }
18
+ // Add floor
19
+ if (asset.floor_name) {
20
+ parts.push(asset.floor_name);
21
+ }
22
+ // Add room (only if this asset is a zone or device)
23
+ if (asset.type === "zone" || asset.type === "sensor" || asset.type === "hive") {
24
+ if (asset.room_name) {
25
+ parts.push(asset.room_name);
26
+ }
27
+ }
28
+ // Add the asset itself (unless it's already the last part)
29
+ if (asset.type !== "site" || (asset.type === "site" && parts.length === 0)) {
30
+ parts.push(asset.name);
31
+ }
32
+ return parts.join(" / ");
33
+ }
34
+ //# sourceMappingURL=path-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-builder.js","sourceRoot":"","sources":["../../src/utils/path-builder.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,KAAqB;IAClD,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,WAAW;IACX,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QACpB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC9B,CAAC;IAED,eAAe;IACf,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC;QACxB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IAClC,CAAC;IAED,YAAY;IACZ,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;QACrB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC/B,CAAC;IAED,oDAAoD;IACpD,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC9E,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;YACpB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IAED,2DAA2D;IAC3D,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC;QAC3E,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Time range validation for timeseries queries
3
+ * Prevents excessive data queries that could timeout or overwhelm the API
4
+ */
5
+ /**
6
+ * Validate time range based on interval
7
+ * @param interval Aggregation interval ('1m', '1h', '1d')
8
+ * @param start Start time (ISO-8601 or relative like '-24h')
9
+ * @param stop Stop time (ISO-8601 or relative like 'now')
10
+ * @throws Error if time range exceeds limits for the given interval
11
+ */
12
+ export declare function validateTimeRange(interval: string, start: string, stop: string): void;
13
+ //# sourceMappingURL=time-range-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-range-validator.d.ts","sourceRoot":"","sources":["../../src/utils/time-range-validator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAiCrF"}