@tummycrypt/acuity-middleware 0.1.0 → 0.1.1

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 (148) hide show
  1. package/dist/adapters/acuity-scraper.d.ts +8 -0
  2. package/dist/adapters/acuity-scraper.d.ts.map +1 -0
  3. package/dist/adapters/acuity-scraper.js +8 -0
  4. package/dist/adapters/acuity-scraper.js.map +1 -0
  5. package/dist/adapters/types.d.ts +8 -0
  6. package/dist/adapters/types.d.ts.map +1 -0
  7. package/dist/adapters/types.js +8 -0
  8. package/dist/adapters/types.js.map +1 -0
  9. package/dist/core/types.d.ts +10 -0
  10. package/dist/core/types.d.ts.map +1 -0
  11. package/dist/core/types.js +2 -0
  12. package/dist/core/types.js.map +1 -0
  13. package/dist/index.d.ts +17 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +18 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/middleware/acuity-wizard.d.ts +49 -0
  18. package/dist/middleware/acuity-wizard.d.ts.map +1 -0
  19. package/dist/middleware/acuity-wizard.js +265 -0
  20. package/dist/middleware/acuity-wizard.js.map +1 -0
  21. package/dist/middleware/browser-service.d.ts +53 -0
  22. package/dist/middleware/browser-service.d.ts.map +1 -0
  23. package/dist/middleware/browser-service.js +105 -0
  24. package/dist/middleware/browser-service.js.map +1 -0
  25. package/dist/middleware/errors.d.ts +58 -0
  26. package/dist/middleware/errors.d.ts.map +1 -0
  27. package/dist/middleware/errors.js +43 -0
  28. package/dist/middleware/errors.js.map +1 -0
  29. package/{src/middleware/index.ts → dist/middleware/index.d.ts} +5 -52
  30. package/dist/middleware/index.d.ts.map +1 -0
  31. package/dist/middleware/index.js +38 -0
  32. package/dist/middleware/index.js.map +1 -0
  33. package/dist/middleware/logger.d.ts +26 -0
  34. package/dist/middleware/logger.d.ts.map +1 -0
  35. package/dist/middleware/logger.js +65 -0
  36. package/dist/middleware/logger.js.map +1 -0
  37. package/dist/middleware/remote-adapter.d.ts +45 -0
  38. package/dist/middleware/remote-adapter.d.ts.map +1 -0
  39. package/dist/middleware/remote-adapter.js +178 -0
  40. package/dist/middleware/remote-adapter.js.map +1 -0
  41. package/dist/middleware/selector-health.d.ts +44 -0
  42. package/dist/middleware/selector-health.d.ts.map +1 -0
  43. package/dist/middleware/selector-health.js +144 -0
  44. package/dist/middleware/selector-health.js.map +1 -0
  45. package/dist/middleware/selectors.d.ts +108 -0
  46. package/dist/middleware/selectors.d.ts.map +1 -0
  47. package/dist/middleware/selectors.js +249 -0
  48. package/dist/middleware/selectors.js.map +1 -0
  49. package/dist/middleware/server.d.ts +34 -0
  50. package/dist/middleware/server.d.ts.map +1 -0
  51. package/dist/middleware/server.js +377 -0
  52. package/dist/middleware/server.js.map +1 -0
  53. package/dist/middleware/service-resolver.d.ts +46 -0
  54. package/dist/middleware/service-resolver.d.ts.map +1 -0
  55. package/dist/middleware/service-resolver.js +274 -0
  56. package/dist/middleware/service-resolver.js.map +1 -0
  57. package/dist/middleware/slot-parser.d.ts +29 -0
  58. package/dist/middleware/slot-parser.d.ts.map +1 -0
  59. package/dist/middleware/slot-parser.js +50 -0
  60. package/dist/middleware/slot-parser.js.map +1 -0
  61. package/dist/middleware/steps/__tests__/fixtures.d.ts +14 -0
  62. package/dist/middleware/steps/__tests__/fixtures.d.ts.map +1 -0
  63. package/dist/middleware/steps/__tests__/fixtures.js +204 -0
  64. package/dist/middleware/steps/__tests__/fixtures.js.map +1 -0
  65. package/dist/middleware/steps/bypass-payment.d.ts +54 -0
  66. package/dist/middleware/steps/bypass-payment.d.ts.map +1 -0
  67. package/dist/middleware/steps/bypass-payment.js +164 -0
  68. package/dist/middleware/steps/bypass-payment.js.map +1 -0
  69. package/dist/middleware/steps/extract-business.d.ts +93 -0
  70. package/dist/middleware/steps/extract-business.d.ts.map +1 -0
  71. package/dist/middleware/steps/extract-business.js +170 -0
  72. package/dist/middleware/steps/extract-business.js.map +1 -0
  73. package/dist/middleware/steps/extract.d.ts +41 -0
  74. package/dist/middleware/steps/extract.d.ts.map +1 -0
  75. package/dist/middleware/steps/extract.js +128 -0
  76. package/dist/middleware/steps/extract.js.map +1 -0
  77. package/dist/middleware/steps/fill-form.d.ts +45 -0
  78. package/dist/middleware/steps/fill-form.d.ts.map +1 -0
  79. package/dist/middleware/steps/fill-form.js +262 -0
  80. package/dist/middleware/steps/fill-form.js.map +1 -0
  81. package/dist/middleware/steps/index.d.ts +12 -0
  82. package/dist/middleware/steps/index.d.ts.map +1 -0
  83. package/dist/middleware/steps/index.js +12 -0
  84. package/dist/middleware/steps/index.js.map +1 -0
  85. package/dist/middleware/steps/navigate.d.ts +51 -0
  86. package/dist/middleware/steps/navigate.d.ts.map +1 -0
  87. package/dist/middleware/steps/navigate.js +391 -0
  88. package/dist/middleware/steps/navigate.js.map +1 -0
  89. package/dist/middleware/steps/read-availability.d.ts +37 -0
  90. package/dist/middleware/steps/read-availability.d.ts.map +1 -0
  91. package/dist/middleware/steps/read-availability.js +298 -0
  92. package/dist/middleware/steps/read-availability.js.map +1 -0
  93. package/dist/middleware/steps/read-slots.d.ts +33 -0
  94. package/dist/middleware/steps/read-slots.d.ts.map +1 -0
  95. package/dist/middleware/steps/read-slots.js +295 -0
  96. package/dist/middleware/steps/read-slots.js.map +1 -0
  97. package/dist/middleware/steps/read-via-url.d.ts +39 -0
  98. package/dist/middleware/steps/read-via-url.d.ts.map +1 -0
  99. package/dist/middleware/steps/read-via-url.js +141 -0
  100. package/dist/middleware/steps/read-via-url.js.map +1 -0
  101. package/dist/middleware/steps/submit.d.ts +22 -0
  102. package/dist/middleware/steps/submit.d.ts.map +1 -0
  103. package/dist/middleware/steps/submit.js +112 -0
  104. package/dist/middleware/steps/submit.js.map +1 -0
  105. package/dist/middleware/wizard-calendar.d.ts +37 -0
  106. package/dist/middleware/wizard-calendar.d.ts.map +1 -0
  107. package/dist/middleware/wizard-calendar.js +177 -0
  108. package/dist/middleware/wizard-calendar.js.map +1 -0
  109. package/dist/middleware/wizard-service.d.ts +30 -0
  110. package/dist/middleware/wizard-service.d.ts.map +1 -0
  111. package/dist/middleware/wizard-service.js +89 -0
  112. package/dist/middleware/wizard-service.js.map +1 -0
  113. package/dist/server.d.ts +6 -0
  114. package/dist/server.d.ts.map +1 -0
  115. package/{src/server.ts → dist/server.js} +1 -0
  116. package/dist/server.js.map +1 -0
  117. package/package.json +16 -4
  118. package/.github/workflows/build-paper.yml +0 -39
  119. package/.github/workflows/ci.yml +0 -37
  120. package/Dockerfile +0 -53
  121. package/docs/blog-post.mdx +0 -240
  122. package/docs/paper/IEEEtran.bst +0 -2409
  123. package/docs/paper/IEEEtran.cls +0 -6347
  124. package/docs/paper/acuity-middleware-paper.tex +0 -375
  125. package/docs/paper/balance.sty +0 -87
  126. package/docs/paper/references.bib +0 -231
  127. package/docs/paper.md +0 -400
  128. package/flake.nix +0 -32
  129. package/modal-app.py +0 -82
  130. package/src/adapters/acuity-scraper.ts +0 -543
  131. package/src/adapters/types.ts +0 -193
  132. package/src/core/types.ts +0 -325
  133. package/src/index.ts +0 -75
  134. package/src/middleware/acuity-wizard.ts +0 -456
  135. package/src/middleware/browser-service.ts +0 -183
  136. package/src/middleware/errors.ts +0 -70
  137. package/src/middleware/remote-adapter.ts +0 -246
  138. package/src/middleware/selectors.ts +0 -308
  139. package/src/middleware/server.ts +0 -372
  140. package/src/middleware/steps/bypass-payment.ts +0 -226
  141. package/src/middleware/steps/extract.ts +0 -174
  142. package/src/middleware/steps/fill-form.ts +0 -359
  143. package/src/middleware/steps/index.ts +0 -27
  144. package/src/middleware/steps/navigate.ts +0 -537
  145. package/src/middleware/steps/read-availability.ts +0 -399
  146. package/src/middleware/steps/read-slots.ts +0 -405
  147. package/src/middleware/steps/submit.ts +0 -168
  148. package/tsconfig.json +0 -25
@@ -0,0 +1,274 @@
1
+ /**
2
+ * ServiceResolver — Multi-Strategy Service Name Matching
3
+ *
4
+ * Effect Context.Tag providing resilient service resolution with
5
+ * cascading fallback strategies and confidence scoring.
6
+ *
7
+ * Strategies (tried in order via Effect.orElse):
8
+ * 1. ID match (confidence 1.0) — match by Acuity numeric ID in BUSINESS object
9
+ * 2. Normalized (confidence 0.95) — strip punctuation, collapse whitespace, exact match
10
+ * 3. Token overlap (0.5-0.9) — word-level intersection scoring
11
+ * 4. Fuzzy/Levenshtein (0.3-0.7) — edit-distance based matching
12
+ */
13
+ import { Context, Effect, Layer } from 'effect';
14
+ import { ServiceResolverError } from './errors.js';
15
+ import { Selectors } from './selectors.js';
16
+ // =============================================================================
17
+ // CONTEXT TAG
18
+ // =============================================================================
19
+ export class ServiceResolver extends Context.Tag('scheduling-kit/ServiceResolver')() {
20
+ }
21
+ // =============================================================================
22
+ // STRING MATCHING UTILITIES
23
+ // =============================================================================
24
+ /** Normalize a string: lowercase, strip non-alphanumeric (keep spaces), collapse whitespace. */
25
+ export const normalize = (s) => s.toLowerCase().replace(/[^\w\s]/g, '').replace(/\s+/g, ' ').trim();
26
+ /** Tokenize: split on whitespace into lowercase words. */
27
+ const tokenize = (s) => new Set(normalize(s).split(' ').filter(Boolean));
28
+ /** Token overlap score: |intersection| / max(|a|, |b|). */
29
+ export const tokenOverlap = (a, b) => {
30
+ const setA = tokenize(a);
31
+ const setB = tokenize(b);
32
+ if (setA.size === 0 || setB.size === 0)
33
+ return 0;
34
+ let intersection = 0;
35
+ for (const token of setA) {
36
+ if (setB.has(token))
37
+ intersection++;
38
+ }
39
+ return intersection / Math.max(setA.size, setB.size);
40
+ };
41
+ /** Levenshtein edit distance between two strings. */
42
+ export const levenshtein = (a, b) => {
43
+ const m = a.length;
44
+ const n = b.length;
45
+ // Optimize for empty strings
46
+ if (m === 0)
47
+ return n;
48
+ if (n === 0)
49
+ return m;
50
+ // Single-row DP
51
+ const row = Array.from({ length: n + 1 }, (_, i) => i);
52
+ for (let i = 1; i <= m; i++) {
53
+ let prev = i;
54
+ for (let j = 1; j <= n; j++) {
55
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
56
+ const val = Math.min(row[j] + 1, // deletion
57
+ prev + 1, // insertion
58
+ row[j - 1] + cost // substitution
59
+ );
60
+ row[j - 1] = prev;
61
+ prev = val;
62
+ }
63
+ row[n] = prev;
64
+ }
65
+ return row[n];
66
+ };
67
+ /** Fuzzy match confidence: 1 - (distance / maxLen). */
68
+ export const fuzzyConfidence = (a, b) => {
69
+ const na = normalize(a);
70
+ const nb = normalize(b);
71
+ const maxLen = Math.max(na.length, nb.length);
72
+ if (maxLen === 0)
73
+ return 0;
74
+ const dist = levenshtein(na, nb);
75
+ return Math.max(0, 1 - dist / maxLen);
76
+ };
77
+ /** Extract all service items and their names from the page DOM. */
78
+ const extractPageServices = (page) => Effect.tryPromise({
79
+ try: async () => {
80
+ const items = await page.$$(Selectors.serviceList[0]);
81
+ const services = [];
82
+ for (const item of items) {
83
+ const nameEl = await item.$(Selectors.serviceName[0]);
84
+ const name = await nameEl?.textContent();
85
+ if (name?.trim()) {
86
+ services.push({ name: name.trim(), element: item });
87
+ }
88
+ }
89
+ return services;
90
+ },
91
+ catch: () => [],
92
+ }).pipe(Effect.orElseSucceed(() => []));
93
+ // =============================================================================
94
+ // STRATEGY IMPLEMENTATIONS
95
+ // =============================================================================
96
+ /** Strategy 1: Match by Acuity numeric ID via BUSINESS object. */
97
+ const tryIdMatch = (page, pageServices, appointmentTypeId) => Effect.gen(function* () {
98
+ // Try to get BUSINESS object from the page
99
+ const business = yield* Effect.tryPromise({
100
+ try: () => page.evaluate(() => window.BUSINESS ?? null),
101
+ catch: () => null,
102
+ }).pipe(Effect.orElseSucceed(() => null));
103
+ if (!business) {
104
+ return yield* Effect.fail(new ServiceResolverError({
105
+ serviceName: appointmentTypeId,
106
+ strategies: ['id-match'],
107
+ message: 'BUSINESS object not available on page',
108
+ }));
109
+ }
110
+ // Find the appointment type by ID
111
+ let targetName = null;
112
+ for (const types of Object.values(business.appointmentTypes ?? {})) {
113
+ for (const apt of types) {
114
+ if (String(apt.id) === appointmentTypeId) {
115
+ targetName = apt.name;
116
+ break;
117
+ }
118
+ }
119
+ if (targetName)
120
+ break;
121
+ }
122
+ if (!targetName) {
123
+ return yield* Effect.fail(new ServiceResolverError({
124
+ serviceName: appointmentTypeId,
125
+ strategies: ['id-match'],
126
+ message: `Acuity ID ${appointmentTypeId} not found in BUSINESS object`,
127
+ }));
128
+ }
129
+ // Now match the BUSINESS name to a DOM element
130
+ const normalizedTarget = normalize(targetName);
131
+ for (const svc of pageServices) {
132
+ if (normalize(svc.name) === normalizedTarget) {
133
+ return {
134
+ element: svc.element,
135
+ confidence: 1.0,
136
+ strategy: 'id-match',
137
+ matchedName: svc.name,
138
+ };
139
+ }
140
+ }
141
+ return yield* Effect.fail(new ServiceResolverError({
142
+ serviceName: appointmentTypeId,
143
+ strategies: ['id-match'],
144
+ message: `BUSINESS name "${targetName}" not found in DOM`,
145
+ }));
146
+ });
147
+ /** Strategy 2: Normalized exact match. */
148
+ const tryNormalizedMatch = (pageServices, serviceName) => {
149
+ const normalizedTarget = normalize(serviceName);
150
+ for (const svc of pageServices) {
151
+ if (normalize(svc.name) === normalizedTarget) {
152
+ return Effect.succeed({
153
+ element: svc.element,
154
+ confidence: 0.95,
155
+ strategy: 'normalized-exact',
156
+ matchedName: svc.name,
157
+ });
158
+ }
159
+ }
160
+ return Effect.fail(new ServiceResolverError({
161
+ serviceName,
162
+ strategies: ['normalized-exact'],
163
+ message: `No normalized match for "${serviceName}"`,
164
+ }));
165
+ };
166
+ /** Strategy 3: Token overlap. */
167
+ const tryTokenOverlap = (pageServices, serviceName) => {
168
+ const TOKEN_THRESHOLD = 0.6;
169
+ let bestMatch = null;
170
+ let bestScore = 0;
171
+ for (const svc of pageServices) {
172
+ const score = tokenOverlap(serviceName, svc.name);
173
+ if (score > bestScore) {
174
+ bestScore = score;
175
+ bestMatch = svc;
176
+ }
177
+ }
178
+ if (bestMatch && bestScore >= TOKEN_THRESHOLD) {
179
+ // Scale confidence: threshold maps to 0.5, perfect match maps to 0.9
180
+ const confidence = 0.5 + (bestScore - TOKEN_THRESHOLD) / (1 - TOKEN_THRESHOLD) * 0.4;
181
+ return Effect.succeed({
182
+ element: bestMatch.element,
183
+ confidence,
184
+ strategy: 'token-overlap',
185
+ matchedName: bestMatch.name,
186
+ });
187
+ }
188
+ return Effect.fail(new ServiceResolverError({
189
+ serviceName,
190
+ strategies: ['token-overlap'],
191
+ message: `Best token overlap score ${bestScore.toFixed(2)} below threshold ${TOKEN_THRESHOLD}`,
192
+ }));
193
+ };
194
+ /** Strategy 4: Fuzzy/Levenshtein. */
195
+ const tryFuzzyMatch = (pageServices, serviceName) => {
196
+ const FUZZY_THRESHOLD = 0.6; // distance/maxLen < 0.4 means confidence > 0.6
197
+ let bestMatch = null;
198
+ let bestConfidence = 0;
199
+ for (const svc of pageServices) {
200
+ const conf = fuzzyConfidence(serviceName, svc.name);
201
+ if (conf > bestConfidence) {
202
+ bestConfidence = conf;
203
+ bestMatch = svc;
204
+ }
205
+ }
206
+ if (bestMatch && bestConfidence >= FUZZY_THRESHOLD) {
207
+ // Scale: 0.6 threshold -> 0.3 confidence, 1.0 -> 0.7
208
+ const confidence = 0.3 + (bestConfidence - FUZZY_THRESHOLD) / (1 - FUZZY_THRESHOLD) * 0.4;
209
+ return Effect.succeed({
210
+ element: bestMatch.element,
211
+ confidence,
212
+ strategy: 'fuzzy',
213
+ matchedName: bestMatch.name,
214
+ });
215
+ }
216
+ return Effect.fail(new ServiceResolverError({
217
+ serviceName,
218
+ strategies: ['fuzzy'],
219
+ message: `Best fuzzy confidence ${bestConfidence.toFixed(2)} below threshold ${FUZZY_THRESHOLD}`,
220
+ }));
221
+ };
222
+ // =============================================================================
223
+ // LIVE LAYER
224
+ // =============================================================================
225
+ export const ServiceResolverLive = Layer.succeed(ServiceResolver, {
226
+ resolve: (page, serviceName, appointmentTypeId) => Effect.gen(function* () {
227
+ const pageServices = yield* extractPageServices(page);
228
+ if (pageServices.length === 0) {
229
+ return yield* Effect.fail(new ServiceResolverError({
230
+ serviceName,
231
+ strategies: [],
232
+ message: 'No services found on page',
233
+ }));
234
+ }
235
+ const strategies = [];
236
+ // Strategy 1: ID match (only if appointmentTypeId provided)
237
+ if (appointmentTypeId) {
238
+ const idResult = yield* tryIdMatch(page, pageServices, appointmentTypeId).pipe(Effect.tap(() => Effect.sync(() => strategies.push('id-match:success'))), Effect.tapError(() => Effect.sync(() => strategies.push('id-match:failed'))), Effect.orElse(() => {
239
+ // Strategy 2: Normalized exact match
240
+ return tryNormalizedMatch(pageServices, serviceName).pipe(Effect.tap(() => Effect.sync(() => strategies.push('normalized-exact:success'))), Effect.tapError(() => Effect.sync(() => strategies.push('normalized-exact:failed'))));
241
+ }), Effect.orElse(() => {
242
+ // Strategy 3: Token overlap
243
+ return tryTokenOverlap(pageServices, serviceName).pipe(Effect.tap(() => Effect.sync(() => strategies.push('token-overlap:success'))), Effect.tapError(() => Effect.sync(() => strategies.push('token-overlap:failed'))));
244
+ }), Effect.orElse(() => {
245
+ // Strategy 4: Fuzzy
246
+ return tryFuzzyMatch(pageServices, serviceName).pipe(Effect.tap(() => Effect.sync(() => strategies.push('fuzzy:success'))), Effect.tapError(() => Effect.sync(() => strategies.push('fuzzy:failed'))));
247
+ }));
248
+ return idResult;
249
+ }
250
+ // No ID — start from strategy 2
251
+ const result = yield* tryNormalizedMatch(pageServices, serviceName).pipe(Effect.tap(() => Effect.sync(() => strategies.push('normalized-exact:success'))), Effect.tapError(() => Effect.sync(() => strategies.push('normalized-exact:failed'))), Effect.orElse(() => tryTokenOverlap(pageServices, serviceName).pipe(Effect.tap(() => Effect.sync(() => strategies.push('token-overlap:success'))), Effect.tapError(() => Effect.sync(() => strategies.push('token-overlap:failed'))))), Effect.orElse(() => tryFuzzyMatch(pageServices, serviceName).pipe(Effect.tap(() => Effect.sync(() => strategies.push('fuzzy:success'))), Effect.tapError(() => Effect.sync(() => strategies.push('fuzzy:failed'))))), Effect.mapError(() => new ServiceResolverError({
252
+ serviceName,
253
+ strategies,
254
+ message: `No match found for "${serviceName}" across ${pageServices.length} services (tried: ${strategies.join(', ')})`,
255
+ })));
256
+ return result;
257
+ }),
258
+ });
259
+ // =============================================================================
260
+ // TEST LAYER
261
+ // =============================================================================
262
+ /**
263
+ * A static ServiceResolver for tests that always returns a mock resolution.
264
+ */
265
+ export const ServiceResolverTest = (mockResolution) => Layer.succeed(ServiceResolver, {
266
+ resolve: () => Effect.succeed({
267
+ element: null,
268
+ confidence: 1.0,
269
+ strategy: 'normalized-exact',
270
+ matchedName: 'Test Service',
271
+ ...mockResolution,
272
+ }),
273
+ });
274
+ //# sourceMappingURL=service-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-resolver.js","sourceRoot":"","sources":["../../src/middleware/service-resolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Slot Text Parser
3
+ *
4
+ * Parses Acuity time slot button text like "4:00 PM1 spot left"
5
+ * into structured data. The button textContent concatenates the
6
+ * time with the availability indicator without any separator.
7
+ */
8
+ /**
9
+ * Parse raw slot button text into time and spots-left.
10
+ *
11
+ * Examples:
12
+ * "4:00 PM1 spot left" => { time: "4:00 PM", spotsLeft: 1 }
13
+ * "10:00 AM" => { time: "10:00 AM", spotsLeft: null }
14
+ * "2:30 PM3 spots left" => { time: "2:30 PM", spotsLeft: 3 }
15
+ * "" => null
16
+ */
17
+ export declare const parseSlotText: (text: string) => {
18
+ time: string;
19
+ spotsLeft: number | null;
20
+ } | null;
21
+ /**
22
+ * Build an ISO datetime string from a date string and a 12-hour time string.
23
+ *
24
+ * @param dateStr - "2026-03-15" format
25
+ * @param timeStr - "10:00 AM" or "4:00 PM" format
26
+ * @returns "2026-03-15T10:00:00" or "2026-03-15T16:00:00"
27
+ */
28
+ export declare const buildIsoDatetime: (dateStr: string, timeStr: string) => string;
29
+ //# sourceMappingURL=slot-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slot-parser.d.ts","sourceRoot":"","sources":["../../src/middleware/slot-parser.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,GACzB,MAAM,MAAM,KACV;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAc/C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,GAAI,SAAS,MAAM,EAAE,SAAS,MAAM,KAAG,MAYnE,CAAC"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Slot Text Parser
3
+ *
4
+ * Parses Acuity time slot button text like "4:00 PM1 spot left"
5
+ * into structured data. The button textContent concatenates the
6
+ * time with the availability indicator without any separator.
7
+ */
8
+ /**
9
+ * Parse raw slot button text into time and spots-left.
10
+ *
11
+ * Examples:
12
+ * "4:00 PM1 spot left" => { time: "4:00 PM", spotsLeft: 1 }
13
+ * "10:00 AM" => { time: "10:00 AM", spotsLeft: null }
14
+ * "2:30 PM3 spots left" => { time: "2:30 PM", spotsLeft: 3 }
15
+ * "" => null
16
+ */
17
+ export const parseSlotText = (text) => {
18
+ const trimmed = text.trim();
19
+ if (!trimmed)
20
+ return null;
21
+ const timeMatch = trimmed.match(/^(\d{1,2}:\d{2}\s*[AP]M)/i);
22
+ if (!timeMatch)
23
+ return null;
24
+ const time = timeMatch[1];
25
+ const rest = trimmed.slice(timeMatch.index + time.length).trim();
26
+ const spotsMatch = rest.match(/^(\d+)\s*spot/i);
27
+ const spotsLeft = spotsMatch ? parseInt(spotsMatch[1], 10) : null;
28
+ return { time, spotsLeft };
29
+ };
30
+ /**
31
+ * Build an ISO datetime string from a date string and a 12-hour time string.
32
+ *
33
+ * @param dateStr - "2026-03-15" format
34
+ * @param timeStr - "10:00 AM" or "4:00 PM" format
35
+ * @returns "2026-03-15T10:00:00" or "2026-03-15T16:00:00"
36
+ */
37
+ export const buildIsoDatetime = (dateStr, timeStr) => {
38
+ const match = timeStr.match(/^(\d{1,2}):(\d{2})\s*([AP]M)/i);
39
+ if (!match)
40
+ return `${dateStr}T00:00:00`;
41
+ let hours = parseInt(match[1], 10);
42
+ const minutes = match[2];
43
+ const period = match[3].toUpperCase();
44
+ if (period === 'AM' && hours === 12)
45
+ hours = 0;
46
+ if (period === 'PM' && hours !== 12)
47
+ hours += 12;
48
+ return `${dateStr}T${String(hours).padStart(2, '0')}:${minutes}:00`;
49
+ };
50
+ //# sourceMappingURL=slot-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slot-parser.js","sourceRoot":"","sources":["../../src/middleware/slot-parser.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAC5B,IAAY,EACwC,EAAE;IACtD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAE1B,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAC7D,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAE5B,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;IAC1B,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,KAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IAElE,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAChD,MAAM,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAElE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AAC5B,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,OAAe,EAAE,OAAe,EAAU,EAAE;IAC5E,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;IAC7D,IAAI,CAAC,KAAK;QAAE,OAAO,GAAG,OAAO,WAAW,CAAC;IAEzC,IAAI,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACzB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAEtC,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE;QAAE,KAAK,GAAG,CAAC,CAAC;IAC/C,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE;QAAE,KAAK,IAAI,EAAE,CAAC;IAEjD,OAAO,GAAG,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,OAAO,KAAK,CAAC;AACrE,CAAC,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Test fixtures for extract-business.ts
3
+ *
4
+ * Based on real data from https://MassageIthaca.as.me/ (captured 2026-03-27)
5
+ */
6
+ import type { AcuityBusinessData, AcuityAppointmentType } from '../extract-business.js';
7
+ export declare const mockAppointmentTypes: Record<string, AcuityAppointmentType[]>;
8
+ export declare const mockInactiveType: AcuityAppointmentType;
9
+ export declare const mockPrivateType: AcuityAppointmentType;
10
+ export declare const mockBusinessData: AcuityBusinessData;
11
+ export declare const mockBusinessHtml: string;
12
+ export declare const mockHtmlNoBusiness = "\n<!DOCTYPE html>\n<html>\n<head><title>Error</title></head>\n<body><p>Page not found</p></body>\n</html>\n";
13
+ export declare const mockHtmlMalformedBusiness = "\n<!DOCTYPE html>\n<script>\nvar BUSINESS = {this is not valid json at all};\nvar FEATURE_FLAGS = [];\n</script>\n";
14
+ //# sourceMappingURL=fixtures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["../../../../src/middleware/steps/__tests__/fixtures.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAMxF,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,EAAE,CAsGxE,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,qBAkB9B,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,qBAkB7B,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,kBAsB9B,CAAC;AAMF,eAAO,MAAM,gBAAgB,QAa5B,CAAC;AAEF,eAAO,MAAM,kBAAkB,gHAM9B,CAAC;AAEF,eAAO,MAAM,yBAAyB,uHAMrC,CAAC"}
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Test fixtures for extract-business.ts
3
+ *
4
+ * Based on real data from https://MassageIthaca.as.me/ (captured 2026-03-27)
5
+ */
6
+ // =============================================================================
7
+ // MOCK BUSINESS OBJECT
8
+ // =============================================================================
9
+ export const mockAppointmentTypes = {
10
+ '1 Urgent Care Massage': [
11
+ {
12
+ id: 53178494,
13
+ name: 'URGENT Care Massage (Same or Next day care)',
14
+ active: true,
15
+ description: 'Same-day or priority care for pain, flare-ups...',
16
+ duration: 45,
17
+ price: '155.00',
18
+ category: '1 Urgent Care Massage',
19
+ color: '#E45A26',
20
+ private: false,
21
+ type: 'service',
22
+ calendarIDs: [8973181],
23
+ formIDs: [2480264],
24
+ addonIDs: [],
25
+ paddingAfter: 13,
26
+ paddingBefore: 0,
27
+ paymentRequired: true,
28
+ classSize: null,
29
+ },
30
+ ],
31
+ '2 TMD': [
32
+ {
33
+ id: 82429463,
34
+ name: 'TMD 1st Consultation & Session',
35
+ active: true,
36
+ description: 'Initial TMD consultation',
37
+ duration: 30,
38
+ price: '155.00',
39
+ category: '2 TMD',
40
+ color: '#5b9366',
41
+ private: false,
42
+ type: 'service',
43
+ calendarIDs: [8973181],
44
+ formIDs: [],
45
+ addonIDs: [],
46
+ paddingAfter: 0,
47
+ paddingBefore: 0,
48
+ paymentRequired: true,
49
+ classSize: null,
50
+ },
51
+ {
52
+ id: 82429361,
53
+ name: 'TMD: single session (30 min)',
54
+ active: true,
55
+ description: 'Follow-up TMD session',
56
+ duration: 30,
57
+ price: '105.00',
58
+ category: '2 TMD',
59
+ color: '#5b9366',
60
+ private: false,
61
+ type: 'service',
62
+ calendarIDs: [8973181],
63
+ formIDs: [],
64
+ addonIDs: [],
65
+ paddingAfter: 0,
66
+ paddingBefore: 0,
67
+ paymentRequired: true,
68
+ classSize: null,
69
+ },
70
+ {
71
+ id: 91149479,
72
+ name: 'TMD Tune up',
73
+ active: true,
74
+ description: 'TMD tune-up session',
75
+ duration: 75,
76
+ price: '255.00',
77
+ category: '2 TMD',
78
+ color: '#5b9366',
79
+ private: false,
80
+ type: 'service',
81
+ calendarIDs: [8973181],
82
+ formIDs: [],
83
+ addonIDs: [],
84
+ paddingAfter: 0,
85
+ paddingBefore: 0,
86
+ paymentRequired: true,
87
+ classSize: null,
88
+ },
89
+ ],
90
+ '3 Cervical': [
91
+ {
92
+ id: 82429246,
93
+ name: 'Cervical Medical Massage 30 minutes',
94
+ active: true,
95
+ description: 'Targeted neck area massage',
96
+ duration: 30,
97
+ price: '85.00',
98
+ category: '3 Cervical',
99
+ color: '#5b9366',
100
+ private: false,
101
+ type: 'service',
102
+ calendarIDs: [8973181],
103
+ formIDs: [],
104
+ addonIDs: [],
105
+ paddingAfter: 0,
106
+ paddingBefore: 0,
107
+ paymentRequired: true,
108
+ classSize: null,
109
+ },
110
+ ],
111
+ };
112
+ export const mockInactiveType = {
113
+ id: 99999999,
114
+ name: 'Inactive service',
115
+ active: false,
116
+ description: '',
117
+ duration: 30,
118
+ price: '50.00',
119
+ category: 'Test',
120
+ color: '#000',
121
+ private: false,
122
+ type: 'service',
123
+ calendarIDs: [],
124
+ formIDs: [],
125
+ addonIDs: [],
126
+ paddingAfter: 0,
127
+ paddingBefore: 0,
128
+ paymentRequired: false,
129
+ classSize: null,
130
+ };
131
+ export const mockPrivateType = {
132
+ id: 88888888,
133
+ name: 'Private admin-only service',
134
+ active: true,
135
+ description: '',
136
+ duration: 60,
137
+ price: '200.00',
138
+ category: 'Admin',
139
+ color: '#000',
140
+ private: true,
141
+ type: 'service',
142
+ calendarIDs: [],
143
+ formIDs: [],
144
+ addonIDs: [],
145
+ paddingAfter: 0,
146
+ paddingBefore: 0,
147
+ paymentRequired: false,
148
+ classSize: null,
149
+ };
150
+ export const mockBusinessData = {
151
+ id: 30262130,
152
+ ownerKey: '4671d709',
153
+ name: 'Massage Ithaca',
154
+ timezone: 'America/New_York',
155
+ appointmentTypes: mockAppointmentTypes,
156
+ calendars: {
157
+ 'South Hill Business Campus': [
158
+ {
159
+ id: 8973181,
160
+ name: '1. Jennifer Whitaker',
161
+ description: 'Licensed massage therapist',
162
+ location: 'South Hill Business Campus 950 Danby Rd Suite 202-U Ithaca, NY 14850',
163
+ timezone: 'America/New_York',
164
+ thumbnail: '//cdn-s.acuityscheduling.com/calendar-thumb-8973181.png',
165
+ image: '//cdn-s.acuityscheduling.com/calendar-8973181.png',
166
+ },
167
+ ],
168
+ },
169
+ products: {},
170
+ forms: [],
171
+ addons: [],
172
+ };
173
+ // =============================================================================
174
+ // MOCK HTML
175
+ // =============================================================================
176
+ export const mockBusinessHtml = `
177
+ <!DOCTYPE html>
178
+ <html>
179
+ <head><title>Massage Ithaca</title></head>
180
+ <body>
181
+ <script>
182
+ var OWNER_KEY = '4671d709';
183
+ var BUSINESS = ${JSON.stringify(mockBusinessData)};
184
+ var FEATURE_FLAGS = [];
185
+ </script>
186
+ <div id="secondo-container"></div>
187
+ </body>
188
+ </html>
189
+ `;
190
+ export const mockHtmlNoBusiness = `
191
+ <!DOCTYPE html>
192
+ <html>
193
+ <head><title>Error</title></head>
194
+ <body><p>Page not found</p></body>
195
+ </html>
196
+ `;
197
+ export const mockHtmlMalformedBusiness = `
198
+ <!DOCTYPE html>
199
+ <script>
200
+ var BUSINESS = {this is not valid json at all};
201
+ var FEATURE_FLAGS = [];
202
+ </script>
203
+ `;
204
+ //# sourceMappingURL=fixtures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixtures.js","sourceRoot":"","sources":["../../../../src/middleware/steps/__tests__/fixtures.ts"],"names":[],"mappings":"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"}