@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.
- package/dist/adapters/acuity-scraper.d.ts +8 -0
- package/dist/adapters/acuity-scraper.d.ts.map +1 -0
- package/dist/adapters/acuity-scraper.js +8 -0
- package/dist/adapters/acuity-scraper.js.map +1 -0
- package/dist/adapters/types.d.ts +8 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +8 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/core/types.d.ts +10 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/acuity-wizard.d.ts +49 -0
- package/dist/middleware/acuity-wizard.d.ts.map +1 -0
- package/dist/middleware/acuity-wizard.js +265 -0
- package/dist/middleware/acuity-wizard.js.map +1 -0
- package/dist/middleware/browser-service.d.ts +53 -0
- package/dist/middleware/browser-service.d.ts.map +1 -0
- package/dist/middleware/browser-service.js +105 -0
- package/dist/middleware/browser-service.js.map +1 -0
- package/dist/middleware/errors.d.ts +58 -0
- package/dist/middleware/errors.d.ts.map +1 -0
- package/dist/middleware/errors.js +43 -0
- package/dist/middleware/errors.js.map +1 -0
- package/{src/middleware/index.ts → dist/middleware/index.d.ts} +5 -52
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +38 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/logger.d.ts +26 -0
- package/dist/middleware/logger.d.ts.map +1 -0
- package/dist/middleware/logger.js +65 -0
- package/dist/middleware/logger.js.map +1 -0
- package/dist/middleware/remote-adapter.d.ts +45 -0
- package/dist/middleware/remote-adapter.d.ts.map +1 -0
- package/dist/middleware/remote-adapter.js +178 -0
- package/dist/middleware/remote-adapter.js.map +1 -0
- package/dist/middleware/selector-health.d.ts +44 -0
- package/dist/middleware/selector-health.d.ts.map +1 -0
- package/dist/middleware/selector-health.js +144 -0
- package/dist/middleware/selector-health.js.map +1 -0
- package/dist/middleware/selectors.d.ts +108 -0
- package/dist/middleware/selectors.d.ts.map +1 -0
- package/dist/middleware/selectors.js +249 -0
- package/dist/middleware/selectors.js.map +1 -0
- package/dist/middleware/server.d.ts +34 -0
- package/dist/middleware/server.d.ts.map +1 -0
- package/dist/middleware/server.js +377 -0
- package/dist/middleware/server.js.map +1 -0
- package/dist/middleware/service-resolver.d.ts +46 -0
- package/dist/middleware/service-resolver.d.ts.map +1 -0
- package/dist/middleware/service-resolver.js +274 -0
- package/dist/middleware/service-resolver.js.map +1 -0
- package/dist/middleware/slot-parser.d.ts +29 -0
- package/dist/middleware/slot-parser.d.ts.map +1 -0
- package/dist/middleware/slot-parser.js +50 -0
- package/dist/middleware/slot-parser.js.map +1 -0
- package/dist/middleware/steps/__tests__/fixtures.d.ts +14 -0
- package/dist/middleware/steps/__tests__/fixtures.d.ts.map +1 -0
- package/dist/middleware/steps/__tests__/fixtures.js +204 -0
- package/dist/middleware/steps/__tests__/fixtures.js.map +1 -0
- package/dist/middleware/steps/bypass-payment.d.ts +54 -0
- package/dist/middleware/steps/bypass-payment.d.ts.map +1 -0
- package/dist/middleware/steps/bypass-payment.js +164 -0
- package/dist/middleware/steps/bypass-payment.js.map +1 -0
- package/dist/middleware/steps/extract-business.d.ts +93 -0
- package/dist/middleware/steps/extract-business.d.ts.map +1 -0
- package/dist/middleware/steps/extract-business.js +170 -0
- package/dist/middleware/steps/extract-business.js.map +1 -0
- package/dist/middleware/steps/extract.d.ts +41 -0
- package/dist/middleware/steps/extract.d.ts.map +1 -0
- package/dist/middleware/steps/extract.js +128 -0
- package/dist/middleware/steps/extract.js.map +1 -0
- package/dist/middleware/steps/fill-form.d.ts +45 -0
- package/dist/middleware/steps/fill-form.d.ts.map +1 -0
- package/dist/middleware/steps/fill-form.js +262 -0
- package/dist/middleware/steps/fill-form.js.map +1 -0
- package/dist/middleware/steps/index.d.ts +12 -0
- package/dist/middleware/steps/index.d.ts.map +1 -0
- package/dist/middleware/steps/index.js +12 -0
- package/dist/middleware/steps/index.js.map +1 -0
- package/dist/middleware/steps/navigate.d.ts +51 -0
- package/dist/middleware/steps/navigate.d.ts.map +1 -0
- package/dist/middleware/steps/navigate.js +391 -0
- package/dist/middleware/steps/navigate.js.map +1 -0
- package/dist/middleware/steps/read-availability.d.ts +37 -0
- package/dist/middleware/steps/read-availability.d.ts.map +1 -0
- package/dist/middleware/steps/read-availability.js +298 -0
- package/dist/middleware/steps/read-availability.js.map +1 -0
- package/dist/middleware/steps/read-slots.d.ts +33 -0
- package/dist/middleware/steps/read-slots.d.ts.map +1 -0
- package/dist/middleware/steps/read-slots.js +295 -0
- package/dist/middleware/steps/read-slots.js.map +1 -0
- package/dist/middleware/steps/read-via-url.d.ts +39 -0
- package/dist/middleware/steps/read-via-url.d.ts.map +1 -0
- package/dist/middleware/steps/read-via-url.js +141 -0
- package/dist/middleware/steps/read-via-url.js.map +1 -0
- package/dist/middleware/steps/submit.d.ts +22 -0
- package/dist/middleware/steps/submit.d.ts.map +1 -0
- package/dist/middleware/steps/submit.js +112 -0
- package/dist/middleware/steps/submit.js.map +1 -0
- package/dist/middleware/wizard-calendar.d.ts +37 -0
- package/dist/middleware/wizard-calendar.d.ts.map +1 -0
- package/dist/middleware/wizard-calendar.js +177 -0
- package/dist/middleware/wizard-calendar.js.map +1 -0
- package/dist/middleware/wizard-service.d.ts +30 -0
- package/dist/middleware/wizard-service.d.ts.map +1 -0
- package/dist/middleware/wizard-service.js +89 -0
- package/dist/middleware/wizard-service.js.map +1 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.d.ts.map +1 -0
- package/{src/server.ts → dist/server.js} +1 -0
- package/dist/server.js.map +1 -0
- package/package.json +16 -4
- package/.github/workflows/build-paper.yml +0 -39
- package/.github/workflows/ci.yml +0 -37
- package/Dockerfile +0 -53
- package/docs/blog-post.mdx +0 -240
- package/docs/paper/IEEEtran.bst +0 -2409
- package/docs/paper/IEEEtran.cls +0 -6347
- package/docs/paper/acuity-middleware-paper.tex +0 -375
- package/docs/paper/balance.sty +0 -87
- package/docs/paper/references.bib +0 -231
- package/docs/paper.md +0 -400
- package/flake.nix +0 -32
- package/modal-app.py +0 -82
- package/src/adapters/acuity-scraper.ts +0 -543
- package/src/adapters/types.ts +0 -193
- package/src/core/types.ts +0 -325
- package/src/index.ts +0 -75
- package/src/middleware/acuity-wizard.ts +0 -456
- package/src/middleware/browser-service.ts +0 -183
- package/src/middleware/errors.ts +0 -70
- package/src/middleware/remote-adapter.ts +0 -246
- package/src/middleware/selectors.ts +0 -308
- package/src/middleware/server.ts +0 -372
- package/src/middleware/steps/bypass-payment.ts +0 -226
- package/src/middleware/steps/extract.ts +0 -174
- package/src/middleware/steps/fill-form.ts +0 -359
- package/src/middleware/steps/index.ts +0 -27
- package/src/middleware/steps/navigate.ts +0 -537
- package/src/middleware/steps/read-availability.ts +0 -399
- package/src/middleware/steps/read-slots.ts +0 -405
- package/src/middleware/steps/submit.ts +0 -168
- 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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"}
|