@absolutejs/voice 0.0.22-beta.511 → 0.0.22-beta.512

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.
@@ -10,6 +10,19 @@ var __name = (target, name) => {
10
10
  });
11
11
  return target;
12
12
  };
13
+ var __returnValue = (v) => v;
14
+ function __exportSetter(name, newValue) {
15
+ this[name] = __returnValue.bind(null, newValue);
16
+ }
17
+ var __export = (target, all) => {
18
+ for (var name in all)
19
+ __defProp(target, name, {
20
+ get: all[name],
21
+ enumerable: true,
22
+ configurable: true,
23
+ set: __exportSetter.bind(all, name)
24
+ });
25
+ };
13
26
  var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
14
27
  var __typeError = (msg) => {
15
28
  throw TypeError(msg);
@@ -0,0 +1,43 @@
1
+ import type { VoiceCalendarAdapter, VoiceCalendarAppointment } from "./calendarAdapter";
2
+ import type { VoiceCalendarSlot } from "./calendarSlots";
3
+ export type VoiceBookingFlowStep = "ask-service" | "ask-date" | "ask-time" | "confirm" | "booking" | "booked" | "failed";
4
+ export type VoiceBookingFlowState = {
5
+ step: VoiceBookingFlowStep;
6
+ serviceId?: string;
7
+ serviceDurationMinutes?: number;
8
+ attendees?: string[];
9
+ proposedSlots: VoiceCalendarSlot[];
10
+ selectedSlot?: VoiceCalendarSlot;
11
+ appointment?: VoiceCalendarAppointment;
12
+ error?: string;
13
+ };
14
+ export type VoiceBookingFlowServiceCatalog = {
15
+ id: string;
16
+ label: string;
17
+ durationMinutes: number;
18
+ }[];
19
+ export type CreateVoiceBookingFlowOptions = {
20
+ adapter: VoiceCalendarAdapter;
21
+ calendarId: string;
22
+ services?: VoiceBookingFlowServiceCatalog;
23
+ defaultDurationMinutes?: number;
24
+ initialStep?: VoiceBookingFlowStep;
25
+ maxSlotsPerDay?: number;
26
+ };
27
+ export declare const createVoiceBookingFlow: (options: CreateVoiceBookingFlowOptions) => {
28
+ chooseService: (serviceId: string) => void;
29
+ chooseSlot: (slotIndex: number) => void;
30
+ confirm: (input?: {
31
+ attendees?: string[];
32
+ title?: string;
33
+ notes?: string;
34
+ }) => Promise<VoiceCalendarAppointment | null>;
35
+ getState: () => VoiceBookingFlowState;
36
+ proposeSlotsForDay: (input: {
37
+ fromMs: number;
38
+ toMs: number;
39
+ }) => Promise<VoiceCalendarSlot[]>;
40
+ reset: () => void;
41
+ subscribe(listener: (state: VoiceBookingFlowState) => void): () => void;
42
+ };
43
+ export type VoiceBookingFlow = ReturnType<typeof createVoiceBookingFlow>;
@@ -0,0 +1,47 @@
1
+ import type { VoiceCalendarBookedRange, VoiceCalendarBusinessHours, VoiceCalendarSlot } from "./calendarSlots";
2
+ export type VoiceCalendarAppointment = {
3
+ id: string;
4
+ calendarId: string;
5
+ startMs: number;
6
+ endMs: number;
7
+ title?: string;
8
+ attendees?: string[];
9
+ notes?: string;
10
+ metadata?: Record<string, string>;
11
+ createdAt: number;
12
+ status: "scheduled" | "cancelled" | "completed" | "no-show";
13
+ };
14
+ export type VoiceCalendarAvailabilityQuery = {
15
+ calendarId: string;
16
+ fromMs: number;
17
+ toMs: number;
18
+ durationMinutes: number;
19
+ bufferMinutes?: number;
20
+ granularityMinutes?: number;
21
+ maxSlots?: number;
22
+ };
23
+ export type VoiceCalendarBookInput = {
24
+ calendarId: string;
25
+ startMs: number;
26
+ endMs: number;
27
+ title?: string;
28
+ attendees?: string[];
29
+ notes?: string;
30
+ metadata?: Record<string, string>;
31
+ };
32
+ export type VoiceCalendarAdapter = {
33
+ readonly providerName: string;
34
+ listAvailability(query: VoiceCalendarAvailabilityQuery): Promise<VoiceCalendarSlot[]>;
35
+ book(input: VoiceCalendarBookInput): Promise<VoiceCalendarAppointment>;
36
+ cancel(appointmentId: string): Promise<VoiceCalendarAppointment | null>;
37
+ get(appointmentId: string): Promise<VoiceCalendarAppointment | null>;
38
+ reschedule(appointmentId: string, nextStartMs: number, nextEndMs: number): Promise<VoiceCalendarAppointment | null>;
39
+ };
40
+ export type CreateVoiceInMemoryCalendarAdapterOptions = {
41
+ businessHours: VoiceCalendarBusinessHours[];
42
+ timezone?: string;
43
+ bookedRanges?: VoiceCalendarBookedRange[];
44
+ generateId?: () => string;
45
+ now?: () => number;
46
+ };
47
+ export declare const createVoiceInMemoryCalendarAdapter: (options: CreateVoiceInMemoryCalendarAdapterOptions) => VoiceCalendarAdapter;
@@ -0,0 +1,35 @@
1
+ export type VoiceCalendarBusinessHours = {
2
+ weekday: number;
3
+ start: string;
4
+ end: string;
5
+ };
6
+ export type VoiceCalendarBlackout = {
7
+ date: string;
8
+ reason?: string;
9
+ };
10
+ export type VoiceCalendarBookedRange = {
11
+ startMs: number;
12
+ endMs: number;
13
+ };
14
+ export type VoiceCalendarSlot = {
15
+ startMs: number;
16
+ endMs: number;
17
+ durationMinutes: number;
18
+ };
19
+ export type GenerateVoiceCalendarSlotsInput = {
20
+ fromMs: number;
21
+ toMs: number;
22
+ durationMinutes: number;
23
+ bufferMinutes?: number;
24
+ granularityMinutes?: number;
25
+ timezone?: string;
26
+ businessHours: VoiceCalendarBusinessHours[];
27
+ blackoutDates?: VoiceCalendarBlackout[];
28
+ bookedRanges?: VoiceCalendarBookedRange[];
29
+ maxSlots?: number;
30
+ };
31
+ export declare const generateVoiceCalendarSlots: (input: GenerateVoiceCalendarSlotsInput) => VoiceCalendarSlot[];
32
+ export declare const summarizeVoiceCalendarSlot: (slot: VoiceCalendarSlot, options?: {
33
+ timezone?: string;
34
+ locale?: string;
35
+ }) => string;
@@ -10,6 +10,19 @@ var __name = (target, name) => {
10
10
  });
11
11
  return target;
12
12
  };
13
+ var __returnValue = (v) => v;
14
+ function __exportSetter(name, newValue) {
15
+ this[name] = __returnValue.bind(null, newValue);
16
+ }
17
+ var __export = (target, all) => {
18
+ for (var name in all)
19
+ __defProp(target, name, {
20
+ get: all[name],
21
+ enumerable: true,
22
+ configurable: true,
23
+ set: __exportSetter.bind(all, name)
24
+ });
25
+ };
13
26
  var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
14
27
  var __typeError = (msg) => {
15
28
  throw TypeError(msg);
package/dist/index.d.ts CHANGED
@@ -321,4 +321,14 @@ export { createVoiceSupervisorPresence } from "./supervisorPresence";
321
321
  export type { CreateVoiceSupervisorPresenceOptions, VoiceSupervisorPresence, VoiceSupervisorPresenceEvent, VoiceSupervisorRole, VoiceSupervisorWatcher, } from "./supervisorPresence";
322
322
  export { createVoiceSupervisorPermissions, VOICE_SUPERVISOR_TIER_CAPABILITIES, } from "./supervisorPermissions";
323
323
  export type { CreateVoiceSupervisorPermissionsOptions, VoiceSupervisorCapability, VoiceSupervisorPermission, VoiceSupervisorPermissionCheck, VoiceSupervisorPermissions, VoiceSupervisorTier, } from "./supervisorPermissions";
324
+ export { generateVoiceCalendarSlots, summarizeVoiceCalendarSlot, } from "./calendarSlots";
325
+ export type { GenerateVoiceCalendarSlotsInput, VoiceCalendarBlackout, VoiceCalendarBookedRange, VoiceCalendarBusinessHours, VoiceCalendarSlot, } from "./calendarSlots";
326
+ export { createVoiceInMemoryCalendarAdapter } from "./calendarAdapter";
327
+ export type { CreateVoiceInMemoryCalendarAdapterOptions, VoiceCalendarAdapter, VoiceCalendarAppointment, VoiceCalendarAvailabilityQuery, VoiceCalendarBookInput, } from "./calendarAdapter";
328
+ export { createVoiceBookingFlow } from "./bookingFlow";
329
+ export type { CreateVoiceBookingFlowOptions, VoiceBookingFlow, VoiceBookingFlowServiceCatalog, VoiceBookingFlowState, VoiceBookingFlowStep, } from "./bookingFlow";
330
+ export { scoreVoiceNoShowRisk, summarizeVoiceNoShowVerdict, } from "./noShowPredictor";
331
+ export type { VoiceNoShowHistoricalRecord, VoiceNoShowScoreInput, VoiceNoShowSignal, VoiceNoShowVerdict, } from "./noShowPredictor";
332
+ export { createVoiceReminderScheduler, DEFAULT_VOICE_REMINDER_TRIGGERS, } from "./reminderScheduler";
333
+ export type { CreateVoiceReminderSchedulerOptions, ScheduleVoiceRemindersInput, VoiceReminderChannel, VoiceReminderJob, VoiceReminderScheduler, VoiceReminderTrigger, } from "./reminderScheduler";
324
334
  export * from "./types";
package/dist/index.js CHANGED
@@ -10,6 +10,19 @@ var __name = (target, name) => {
10
10
  });
11
11
  return target;
12
12
  };
13
+ var __returnValue = (v) => v;
14
+ function __exportSetter(name, newValue) {
15
+ this[name] = __returnValue.bind(null, newValue);
16
+ }
17
+ var __export = (target, all) => {
18
+ for (var name in all)
19
+ __defProp(target, name, {
20
+ get: all[name],
21
+ enumerable: true,
22
+ configurable: true,
23
+ set: __exportSetter.bind(all, name)
24
+ });
25
+ };
13
26
  var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
14
27
  var __typeError = (msg) => {
15
28
  throw TypeError(msg);
@@ -70,6 +83,115 @@ var __decorateElement = (array, flags, name, decorators, target, extra) => {
70
83
  };
71
84
  var __require = import.meta.require;
72
85
 
86
+ // src/calendarSlots.ts
87
+ var exports_calendarSlots = {};
88
+ __export(exports_calendarSlots, {
89
+ summarizeVoiceCalendarSlot: () => summarizeVoiceCalendarSlot,
90
+ generateVoiceCalendarSlots: () => generateVoiceCalendarSlots
91
+ });
92
+ var parseHHMM = (value) => {
93
+ const match = /^([0-9]{1,2}):([0-9]{2})$/u.exec(value);
94
+ if (!match)
95
+ throw new Error(`Invalid time string (expected HH:MM): ${value}`);
96
+ return Number(match[1]) * 60 + Number(match[2]);
97
+ }, partsAt = (ms, timezone) => {
98
+ const formatter = new Intl.DateTimeFormat("en-US", {
99
+ day: "2-digit",
100
+ hour: "2-digit",
101
+ hour12: false,
102
+ minute: "2-digit",
103
+ month: "2-digit",
104
+ timeZone: timezone,
105
+ weekday: "short",
106
+ year: "numeric"
107
+ });
108
+ const map = {};
109
+ for (const part of formatter.formatToParts(new Date(ms))) {
110
+ if (part.type !== "literal")
111
+ map[part.type] = part.value;
112
+ }
113
+ const weekdayMap = {
114
+ Fri: 5,
115
+ Mon: 1,
116
+ Sat: 6,
117
+ Sun: 0,
118
+ Thu: 4,
119
+ Tue: 2,
120
+ Wed: 3
121
+ };
122
+ const hourValue = map.hour === "24" ? "00" : map.hour ?? "0";
123
+ return {
124
+ date: `${map.year}-${map.month}-${map.day}`,
125
+ minutes: Number(hourValue) * 60 + Number(map.minute ?? "0"),
126
+ weekday: weekdayMap[map.weekday ?? ""] ?? 0
127
+ };
128
+ }, overlaps = (aStart, aEnd, bStart, bEnd) => aStart < bEnd && bStart < aEnd, generateVoiceCalendarSlots = (input) => {
129
+ if (input.durationMinutes <= 0) {
130
+ throw new Error("durationMinutes must be positive");
131
+ }
132
+ if (input.toMs <= input.fromMs)
133
+ return [];
134
+ const granularity = input.granularityMinutes ?? 15;
135
+ const buffer = input.bufferMinutes ?? 0;
136
+ const max = input.maxSlots ?? Infinity;
137
+ const hoursByDay = new Map;
138
+ for (const block of input.businessHours) {
139
+ const list = hoursByDay.get(block.weekday) ?? [];
140
+ list.push(block);
141
+ hoursByDay.set(block.weekday, list);
142
+ }
143
+ const blackoutDates = new Set((input.blackoutDates ?? []).map((b) => b.date));
144
+ const slots = [];
145
+ const stepMs = granularity * 60000;
146
+ const durationMs = input.durationMinutes * 60000;
147
+ const bufferMs = buffer * 60000;
148
+ let cursor = input.fromMs;
149
+ while (cursor + durationMs <= input.toMs && slots.length < max) {
150
+ const slotEnd = cursor + durationMs;
151
+ const startParts = partsAt(cursor, input.timezone);
152
+ if (blackoutDates.has(startParts.date)) {
153
+ cursor += stepMs;
154
+ continue;
155
+ }
156
+ const dayHours = hoursByDay.get(startParts.weekday);
157
+ if (!dayHours || dayHours.length === 0) {
158
+ cursor += stepMs;
159
+ continue;
160
+ }
161
+ const endParts = partsAt(slotEnd - 1, input.timezone);
162
+ const fitsHours = dayHours.some((block) => {
163
+ const startMin = parseHHMM(block.start);
164
+ const endMin = parseHHMM(block.end);
165
+ return startParts.minutes >= startMin && endParts.minutes < endMin && startParts.date === endParts.date;
166
+ });
167
+ if (!fitsHours) {
168
+ cursor += stepMs;
169
+ continue;
170
+ }
171
+ const collides = (input.bookedRanges ?? []).some((booked) => overlaps(cursor - bufferMs, slotEnd + bufferMs, booked.startMs, booked.endMs));
172
+ if (!collides) {
173
+ slots.push({
174
+ durationMinutes: input.durationMinutes,
175
+ endMs: slotEnd,
176
+ startMs: cursor
177
+ });
178
+ }
179
+ cursor += stepMs;
180
+ }
181
+ return slots;
182
+ }, summarizeVoiceCalendarSlot = (slot, options = {}) => {
183
+ const formatter = new Intl.DateTimeFormat(options.locale ?? "en-US", {
184
+ day: "numeric",
185
+ hour: "numeric",
186
+ hour12: true,
187
+ minute: "2-digit",
188
+ month: "long",
189
+ timeZone: options.timezone,
190
+ weekday: "long"
191
+ });
192
+ return formatter.format(new Date(slot.startMs));
193
+ };
194
+
73
195
  // src/audioConditioning.ts
74
196
  var DEFAULT_TARGET_LEVEL = 0.08;
75
197
  var DEFAULT_MAX_GAIN = 3;
@@ -49602,6 +49724,368 @@ var createVoiceSupervisorPermissions = (options = {}) => {
49602
49724
  };
49603
49725
  };
49604
49726
  var VOICE_SUPERVISOR_TIER_CAPABILITIES = TIER_CAPABILITIES;
49727
+ // src/calendarAdapter.ts
49728
+ var createVoiceInMemoryCalendarAdapter = (options) => {
49729
+ const now = options.now ?? (() => Date.now());
49730
+ const generateId = options.generateId ?? (() => `appt_${Math.random().toString(36).slice(2, 10)}`);
49731
+ const appointments = new Map;
49732
+ for (const range of options.bookedRanges ?? []) {
49733
+ const id = generateId();
49734
+ appointments.set(id, {
49735
+ calendarId: "default",
49736
+ createdAt: now(),
49737
+ endMs: range.endMs,
49738
+ id,
49739
+ startMs: range.startMs,
49740
+ status: "scheduled"
49741
+ });
49742
+ }
49743
+ const liveRanges = () => Array.from(appointments.values()).filter((a) => a.status === "scheduled").map((a) => ({ endMs: a.endMs, startMs: a.startMs }));
49744
+ return {
49745
+ async book(input) {
49746
+ const clash = liveRanges().some((r) => input.startMs < r.endMs && r.startMs < input.endMs);
49747
+ if (clash)
49748
+ throw new Error("Slot is already booked");
49749
+ const id = generateId();
49750
+ const appointment = {
49751
+ calendarId: input.calendarId,
49752
+ createdAt: now(),
49753
+ endMs: input.endMs,
49754
+ id,
49755
+ startMs: input.startMs,
49756
+ status: "scheduled",
49757
+ ...input.title !== undefined ? { title: input.title } : {},
49758
+ ...input.attendees !== undefined ? { attendees: input.attendees } : {},
49759
+ ...input.notes !== undefined ? { notes: input.notes } : {},
49760
+ ...input.metadata !== undefined ? { metadata: input.metadata } : {}
49761
+ };
49762
+ appointments.set(id, appointment);
49763
+ return appointment;
49764
+ },
49765
+ async cancel(id) {
49766
+ const existing = appointments.get(id);
49767
+ if (!existing)
49768
+ return null;
49769
+ const cancelled = {
49770
+ ...existing,
49771
+ status: "cancelled"
49772
+ };
49773
+ appointments.set(id, cancelled);
49774
+ return cancelled;
49775
+ },
49776
+ async get(id) {
49777
+ return appointments.get(id) ?? null;
49778
+ },
49779
+ async listAvailability(query) {
49780
+ const { generateVoiceCalendarSlots: generateVoiceCalendarSlots2 } = await Promise.resolve().then(() => exports_calendarSlots);
49781
+ return generateVoiceCalendarSlots2({
49782
+ bookedRanges: liveRanges(),
49783
+ ...query.bufferMinutes !== undefined ? { bufferMinutes: query.bufferMinutes } : {},
49784
+ businessHours: options.businessHours,
49785
+ durationMinutes: query.durationMinutes,
49786
+ fromMs: query.fromMs,
49787
+ ...query.granularityMinutes !== undefined ? { granularityMinutes: query.granularityMinutes } : {},
49788
+ ...query.maxSlots !== undefined ? { maxSlots: query.maxSlots } : {},
49789
+ ...options.timezone !== undefined ? { timezone: options.timezone } : {},
49790
+ toMs: query.toMs
49791
+ });
49792
+ },
49793
+ providerName: "in-memory",
49794
+ async reschedule(id, nextStartMs, nextEndMs) {
49795
+ const existing = appointments.get(id);
49796
+ if (!existing)
49797
+ return null;
49798
+ const others = liveRanges().filter((r) => r.startMs !== existing.startMs || r.endMs !== existing.endMs);
49799
+ const clash = others.some((r) => nextStartMs < r.endMs && r.startMs < nextEndMs);
49800
+ if (clash)
49801
+ throw new Error("Cannot reschedule onto a booked slot");
49802
+ const updated = {
49803
+ ...existing,
49804
+ endMs: nextEndMs,
49805
+ startMs: nextStartMs
49806
+ };
49807
+ appointments.set(id, updated);
49808
+ return updated;
49809
+ }
49810
+ };
49811
+ };
49812
+ // src/bookingFlow.ts
49813
+ var createVoiceBookingFlow = (options) => {
49814
+ const initial = {
49815
+ proposedSlots: [],
49816
+ step: options.initialStep ?? (options.services ? "ask-service" : "ask-date")
49817
+ };
49818
+ let state = initial;
49819
+ const listeners = new Set;
49820
+ const setState = (next) => {
49821
+ state = { ...state, ...next };
49822
+ for (const listener of listeners)
49823
+ listener(state);
49824
+ };
49825
+ const chooseService = (serviceId) => {
49826
+ const services = options.services ?? [];
49827
+ const service = services.find((s) => s.id === serviceId);
49828
+ if (!service) {
49829
+ setState({ error: `Unknown service: ${serviceId}`, step: "failed" });
49830
+ return;
49831
+ }
49832
+ setState({
49833
+ serviceDurationMinutes: service.durationMinutes,
49834
+ serviceId: service.id,
49835
+ step: "ask-date"
49836
+ });
49837
+ };
49838
+ const proposeSlotsForDay = async (input) => {
49839
+ const duration = state.serviceDurationMinutes ?? options.defaultDurationMinutes ?? 30;
49840
+ const slots = await options.adapter.listAvailability({
49841
+ calendarId: options.calendarId,
49842
+ durationMinutes: duration,
49843
+ fromMs: input.fromMs,
49844
+ ...options.maxSlotsPerDay !== undefined ? { maxSlots: options.maxSlotsPerDay } : {},
49845
+ toMs: input.toMs
49846
+ });
49847
+ setState({ proposedSlots: slots, step: slots.length > 0 ? "ask-time" : "ask-date" });
49848
+ return slots;
49849
+ };
49850
+ const chooseSlot = (slotIndex) => {
49851
+ const slot = state.proposedSlots[slotIndex];
49852
+ if (!slot) {
49853
+ setState({ error: "Invalid slot selection", step: "failed" });
49854
+ return;
49855
+ }
49856
+ setState({ selectedSlot: slot, step: "confirm" });
49857
+ };
49858
+ const confirm = async (input = {}) => {
49859
+ if (state.step !== "confirm" || !state.selectedSlot) {
49860
+ setState({ error: "Nothing to confirm", step: "failed" });
49861
+ return null;
49862
+ }
49863
+ setState({ step: "booking" });
49864
+ try {
49865
+ const appt = await options.adapter.book({
49866
+ calendarId: options.calendarId,
49867
+ endMs: state.selectedSlot.endMs,
49868
+ startMs: state.selectedSlot.startMs,
49869
+ ...input.attendees !== undefined ? { attendees: input.attendees } : {},
49870
+ ...input.title !== undefined ? { title: input.title } : {},
49871
+ ...input.notes !== undefined ? { notes: input.notes } : {}
49872
+ });
49873
+ setState({ appointment: appt, step: "booked" });
49874
+ return appt;
49875
+ } catch (error) {
49876
+ setState({
49877
+ error: error instanceof Error ? error.message : String(error),
49878
+ step: "failed"
49879
+ });
49880
+ return null;
49881
+ }
49882
+ };
49883
+ const reset = () => {
49884
+ state = {
49885
+ proposedSlots: [],
49886
+ step: options.initialStep ?? (options.services ? "ask-service" : "ask-date")
49887
+ };
49888
+ for (const listener of listeners)
49889
+ listener(state);
49890
+ };
49891
+ return {
49892
+ chooseService,
49893
+ chooseSlot,
49894
+ confirm,
49895
+ getState: () => state,
49896
+ proposeSlotsForDay,
49897
+ reset,
49898
+ subscribe(listener) {
49899
+ listeners.add(listener);
49900
+ listener(state);
49901
+ return () => {
49902
+ listeners.delete(listener);
49903
+ };
49904
+ }
49905
+ };
49906
+ };
49907
+ // src/noShowPredictor.ts
49908
+ var clamp = (value, min = 0, max = 1) => Math.max(min, Math.min(max, value));
49909
+ var scoreVoiceNoShowRisk = (input) => {
49910
+ const now = input.now ?? (() => Date.now());
49911
+ const leadHours = (input.appointmentStartMs - input.bookedAtMs) / 3600000;
49912
+ const startDate = new Date(input.appointmentStartMs);
49913
+ const weekday = startDate.getUTCDay();
49914
+ const hour = startDate.getUTCHours();
49915
+ const history = input.history ?? [];
49916
+ const past = history.filter((r) => r.scheduledStartMs < input.appointmentStartMs);
49917
+ const priorNoShows = past.filter((r) => r.outcome === "no-show").length;
49918
+ const priorKept = past.filter((r) => r.outcome === "kept").length;
49919
+ let score = 0.15;
49920
+ const drivers = [];
49921
+ if (leadHours > 72) {
49922
+ score += 0.1;
49923
+ drivers.push({ kind: "lead-time-hours", value: leadHours });
49924
+ } else if (leadHours < 12) {
49925
+ score -= 0.05;
49926
+ drivers.push({ kind: "lead-time-hours", value: leadHours });
49927
+ }
49928
+ if (weekday === 1) {
49929
+ score += 0.04;
49930
+ drivers.push({ kind: "weekday", value: weekday });
49931
+ }
49932
+ if (weekday === 5) {
49933
+ score += 0.03;
49934
+ drivers.push({ kind: "weekday", value: weekday });
49935
+ }
49936
+ if (hour < 9 || hour >= 17) {
49937
+ score += 0.04;
49938
+ drivers.push({ kind: "hour-of-day", value: hour });
49939
+ }
49940
+ if (priorNoShows > 0) {
49941
+ const delta = Math.min(0.5, priorNoShows * 0.2);
49942
+ score += delta;
49943
+ drivers.push({ kind: "prior-no-show-count", value: priorNoShows });
49944
+ }
49945
+ if (priorKept > 2 && priorNoShows === 0) {
49946
+ score -= 0.08;
49947
+ drivers.push({ kind: "prior-kept-count", value: priorKept });
49948
+ }
49949
+ if (input.reminderConfirmed === true) {
49950
+ score -= 0.15;
49951
+ drivers.push({ kind: "reminder-confirmed", value: true });
49952
+ } else if (input.reminderConfirmed === false) {
49953
+ score += 0.1;
49954
+ drivers.push({ kind: "reminder-confirmed", value: false });
49955
+ }
49956
+ if (input.callbackDistanceHours !== undefined && input.callbackDistanceHours > 24) {
49957
+ score += 0.06;
49958
+ drivers.push({
49959
+ kind: "callback-distance-hours",
49960
+ value: input.callbackDistanceHours
49961
+ });
49962
+ }
49963
+ if (input.weatherDisruption) {
49964
+ score += 0.18;
49965
+ drivers.push({ kind: "weather-disruption", value: true });
49966
+ }
49967
+ const finalScore = clamp(score);
49968
+ const band = finalScore >= 0.55 ? "high" : finalScore >= 0.3 ? "moderate" : "low";
49969
+ return { band, drivers, score: finalScore };
49970
+ };
49971
+ var summarizeVoiceNoShowVerdict = (verdict) => {
49972
+ const pct = Math.round(verdict.score * 100);
49973
+ const top = verdict.drivers.slice(0, 2).map((d) => d.kind).join(", ");
49974
+ return `${verdict.band} risk (${pct}%)${top ? ` \u2014 driven by ${top}` : ""}`;
49975
+ };
49976
+ // src/reminderScheduler.ts
49977
+ var DEFAULT_VOICE_REMINDER_TRIGGERS = [
49978
+ {
49979
+ channel: "sms",
49980
+ id: "remind-24h",
49981
+ offsetMinutesBeforeStart: 24 * 60
49982
+ },
49983
+ {
49984
+ channel: "sms",
49985
+ id: "remind-2h",
49986
+ offsetMinutesBeforeStart: 120
49987
+ },
49988
+ {
49989
+ channel: "call",
49990
+ id: "remind-call-30m",
49991
+ offsetMinutesBeforeStart: 30,
49992
+ retryOnFailure: true
49993
+ }
49994
+ ];
49995
+ var createVoiceReminderScheduler = (options = {}) => {
49996
+ const now = options.now ?? (() => Date.now());
49997
+ const generateId = options.generateJobId ?? (() => `rem_${Math.random().toString(36).slice(2, 10)}`);
49998
+ const defaultTriggers = options.defaultTriggers ?? DEFAULT_VOICE_REMINDER_TRIGGERS;
49999
+ const maxAttempts = options.maxAttempts ?? 2;
50000
+ const jobs = new Map;
50001
+ const listeners = new Set;
50002
+ const broadcast = (job) => {
50003
+ for (const listener of listeners)
50004
+ listener(job);
50005
+ };
50006
+ const schedule = (input) => {
50007
+ const triggers = input.triggers.length > 0 ? input.triggers : defaultTriggers;
50008
+ const at = now();
50009
+ const created = [];
50010
+ for (const trigger of triggers) {
50011
+ const fireAt = input.appointmentStartMs - trigger.offsetMinutesBeforeStart * 60000;
50012
+ if (fireAt <= at)
50013
+ continue;
50014
+ const job = {
50015
+ appointmentId: input.appointmentId,
50016
+ attempts: 0,
50017
+ channel: trigger.channel,
50018
+ id: generateId(),
50019
+ scheduledAtMs: fireAt,
50020
+ status: "pending",
50021
+ triggerId: trigger.id,
50022
+ ...input.metadata !== undefined ? { metadata: input.metadata } : {}
50023
+ };
50024
+ jobs.set(job.id, job);
50025
+ created.push(job);
50026
+ broadcast(job);
50027
+ }
50028
+ return created;
50029
+ };
50030
+ const due = (at = now()) => Array.from(jobs.values()).filter((j) => j.status === "pending" && j.scheduledAtMs <= at);
50031
+ const markInFlight = (jobId) => {
50032
+ const job = jobs.get(jobId);
50033
+ if (!job || job.status !== "pending")
50034
+ return false;
50035
+ job.status = "in-flight";
50036
+ job.attempts += 1;
50037
+ broadcast(job);
50038
+ return true;
50039
+ };
50040
+ const markSent = (jobId) => {
50041
+ const job = jobs.get(jobId);
50042
+ if (!job)
50043
+ return false;
50044
+ job.status = "sent";
50045
+ broadcast(job);
50046
+ return true;
50047
+ };
50048
+ const markFailed = (jobId, error) => {
50049
+ const job = jobs.get(jobId);
50050
+ if (!job)
50051
+ return false;
50052
+ job.lastError = error;
50053
+ if (job.attempts < maxAttempts) {
50054
+ job.status = "pending";
50055
+ job.scheduledAtMs = now() + 5 * 60000;
50056
+ } else {
50057
+ job.status = "failed";
50058
+ }
50059
+ broadcast(job);
50060
+ return true;
50061
+ };
50062
+ const cancelForAppointment = (appointmentId) => {
50063
+ let count = 0;
50064
+ for (const job of jobs.values()) {
50065
+ if (job.appointmentId === appointmentId && (job.status === "pending" || job.status === "in-flight")) {
50066
+ job.status = "cancelled";
50067
+ broadcast(job);
50068
+ count += 1;
50069
+ }
50070
+ }
50071
+ return count;
50072
+ };
50073
+ return {
50074
+ cancelForAppointment,
50075
+ due,
50076
+ list: (appointmentId) => Array.from(jobs.values()).filter((j) => !appointmentId || j.appointmentId === appointmentId),
50077
+ markFailed,
50078
+ markInFlight,
50079
+ markSent,
50080
+ schedule,
50081
+ subscribe(listener) {
50082
+ listeners.add(listener);
50083
+ return () => {
50084
+ listeners.delete(listener);
50085
+ };
50086
+ }
50087
+ };
50088
+ };
49605
50089
  export {
49606
50090
  writeVoiceProofPack,
49607
50091
  writeVoiceMediaPipelineArtifacts,
@@ -49649,6 +50133,7 @@ export {
49649
50133
  summarizeVoiceOpsTaskQueue,
49650
50134
  summarizeVoiceOpsTaskAnalytics,
49651
50135
  summarizeVoiceOpsStatus,
50136
+ summarizeVoiceNoShowVerdict,
49652
50137
  summarizeVoiceMediaPipelineReport,
49653
50138
  summarizeVoiceLiveLatency,
49654
50139
  summarizeVoiceIntegrationEvents,
@@ -49657,6 +50142,7 @@ export {
49657
50142
  summarizeVoiceCampaigns,
49658
50143
  summarizeVoiceCampaignDispositions,
49659
50144
  summarizeVoiceCallerTranscript,
50145
+ summarizeVoiceCalendarSlot,
49660
50146
  summarizeVoiceBrowserMedia,
49661
50147
  summarizeVoiceBargeIn,
49662
50148
  summarizeVoiceAuditTrail,
@@ -49670,6 +50156,7 @@ export {
49670
50156
  shouldRetryCampaignAttempt,
49671
50157
  shapeTelephonyAssistantText,
49672
50158
  selectVoiceTraceEventsForPrune,
50159
+ scoreVoiceNoShowRisk,
49673
50160
  saveVoiceIncidentBundleArtifact,
49674
50161
  runVoiceToolContractSuite,
49675
50162
  runVoiceToolContract,
@@ -49877,6 +50364,7 @@ export {
49877
50364
  getLatestVoiceTelephonyMediaReport,
49878
50365
  getLatestVoiceBrowserMediaReport,
49879
50366
  getDefaultVoiceTelephonyBenchmarkScenarios,
50367
+ generateVoiceCalendarSlots,
49880
50368
  fromVapiAssistantConfig,
49881
50369
  formatVoiceProofTrendAge,
49882
50370
  formatVoiceCallPlayerTimestamp,
@@ -50053,6 +50541,7 @@ export {
50053
50541
  createVoiceRetentionScheduler,
50054
50542
  createVoiceResilienceRoutes,
50055
50543
  createVoiceReplayTimelineHTMXRoute,
50544
+ createVoiceReminderScheduler,
50056
50545
  createVoiceRedisTelnyxWebhookEventStore,
50057
50546
  createVoiceRedisTelephonyWebhookIdempotencyStore,
50058
50547
  createVoiceRedisTaskLeaseCoordinator,
@@ -50193,6 +50682,7 @@ export {
50193
50682
  createVoiceIncidentBundleRoutes,
50194
50683
  createVoiceInMemoryRealCallProfileRecoveryJobStore,
50195
50684
  createVoiceInMemoryMonitorRegistry,
50685
+ createVoiceInMemoryCalendarAdapter,
50196
50686
  createVoiceIVRSession,
50197
50687
  createVoiceHubSpotTaskUpdateSink,
50198
50688
  createVoiceHubSpotTaskSyncSinks,
@@ -50267,6 +50757,7 @@ export {
50267
50757
  createVoiceCRMActivitySink,
50268
50758
  createVoiceBrowserMediaRoutes,
50269
50759
  createVoiceBrowserCallProfileRoutes,
50760
+ createVoiceBookingFlow,
50270
50761
  createVoiceBearerAuthVerifier,
50271
50762
  createVoiceBargeInRoutes,
50272
50763
  createVoiceBackchannelDriver,
@@ -50494,6 +50985,7 @@ export {
50494
50985
  VOICE_DTMF_DIGITS,
50495
50986
  VOICE_CALLER_MEMORY_KEY,
50496
50987
  TURN_PROFILE_DEFAULTS,
50988
+ DEFAULT_VOICE_REMINDER_TRIGGERS,
50497
50989
  DEFAULT_VOICE_REDACTION_PATTERNS,
50498
50990
  DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS,
50499
50991
  DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS,
@@ -0,0 +1,46 @@
1
+ export type VoiceNoShowHistoricalRecord = {
2
+ appointmentId: string;
3
+ scheduledStartMs: number;
4
+ outcome: "kept" | "no-show" | "cancelled" | "rescheduled";
5
+ };
6
+ export type VoiceNoShowSignal = {
7
+ kind: "lead-time-hours";
8
+ value: number;
9
+ } | {
10
+ kind: "weekday";
11
+ value: number;
12
+ } | {
13
+ kind: "hour-of-day";
14
+ value: number;
15
+ } | {
16
+ kind: "prior-no-show-count";
17
+ value: number;
18
+ } | {
19
+ kind: "prior-kept-count";
20
+ value: number;
21
+ } | {
22
+ kind: "reminder-confirmed";
23
+ value: boolean;
24
+ } | {
25
+ kind: "callback-distance-hours";
26
+ value: number;
27
+ } | {
28
+ kind: "weather-disruption";
29
+ value: boolean;
30
+ };
31
+ export type VoiceNoShowScoreInput = {
32
+ appointmentStartMs: number;
33
+ bookedAtMs: number;
34
+ history?: VoiceNoShowHistoricalRecord[];
35
+ reminderConfirmed?: boolean;
36
+ weatherDisruption?: boolean;
37
+ callbackDistanceHours?: number;
38
+ now?: () => number;
39
+ };
40
+ export type VoiceNoShowVerdict = {
41
+ score: number;
42
+ band: "low" | "moderate" | "high";
43
+ drivers: VoiceNoShowSignal[];
44
+ };
45
+ export declare const scoreVoiceNoShowRisk: (input: VoiceNoShowScoreInput) => VoiceNoShowVerdict;
46
+ export declare const summarizeVoiceNoShowVerdict: (verdict: VoiceNoShowVerdict) => string;
@@ -10,6 +10,19 @@ var __name = (target, name) => {
10
10
  });
11
11
  return target;
12
12
  };
13
+ var __returnValue = (v) => v;
14
+ function __exportSetter(name, newValue) {
15
+ this[name] = __returnValue.bind(null, newValue);
16
+ }
17
+ var __export = (target, all) => {
18
+ for (var name in all)
19
+ __defProp(target, name, {
20
+ get: all[name],
21
+ enumerable: true,
22
+ configurable: true,
23
+ set: __exportSetter.bind(all, name)
24
+ });
25
+ };
13
26
  var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
14
27
  var __typeError = (msg) => {
15
28
  throw TypeError(msg);
@@ -0,0 +1,43 @@
1
+ export type VoiceReminderChannel = "call" | "sms" | "email";
2
+ export type VoiceReminderTrigger = {
3
+ id: string;
4
+ channel: VoiceReminderChannel;
5
+ offsetMinutesBeforeStart: number;
6
+ templateId?: string;
7
+ retryOnFailure?: boolean;
8
+ };
9
+ export type VoiceReminderJob = {
10
+ id: string;
11
+ appointmentId: string;
12
+ triggerId: string;
13
+ channel: VoiceReminderChannel;
14
+ scheduledAtMs: number;
15
+ status: "pending" | "in-flight" | "sent" | "skipped" | "failed" | "cancelled";
16
+ attempts: number;
17
+ lastError?: string;
18
+ metadata?: Record<string, string>;
19
+ };
20
+ export type ScheduleVoiceRemindersInput = {
21
+ appointmentId: string;
22
+ appointmentStartMs: number;
23
+ triggers: VoiceReminderTrigger[];
24
+ metadata?: Record<string, string>;
25
+ };
26
+ export type CreateVoiceReminderSchedulerOptions = {
27
+ generateJobId?: () => string;
28
+ now?: () => number;
29
+ defaultTriggers?: VoiceReminderTrigger[];
30
+ maxAttempts?: number;
31
+ };
32
+ export declare const DEFAULT_VOICE_REMINDER_TRIGGERS: VoiceReminderTrigger[];
33
+ export declare const createVoiceReminderScheduler: (options?: CreateVoiceReminderSchedulerOptions) => {
34
+ cancelForAppointment: (appointmentId: string) => number;
35
+ due: (at?: number) => VoiceReminderJob[];
36
+ list: (appointmentId?: string) => VoiceReminderJob[];
37
+ markFailed: (jobId: string, error: string) => boolean;
38
+ markInFlight: (jobId: string) => boolean;
39
+ markSent: (jobId: string) => boolean;
40
+ schedule: (input: ScheduleVoiceRemindersInput) => VoiceReminderJob[];
41
+ subscribe(listener: (job: VoiceReminderJob) => void): () => void;
42
+ };
43
+ export type VoiceReminderScheduler = ReturnType<typeof createVoiceReminderScheduler>;
@@ -10,6 +10,19 @@ var __name = (target, name) => {
10
10
  });
11
11
  return target;
12
12
  };
13
+ var __returnValue = (v) => v;
14
+ function __exportSetter(name, newValue) {
15
+ this[name] = __returnValue.bind(null, newValue);
16
+ }
17
+ var __export = (target, all) => {
18
+ for (var name in all)
19
+ __defProp(target, name, {
20
+ get: all[name],
21
+ enumerable: true,
22
+ configurable: true,
23
+ set: __exportSetter.bind(all, name)
24
+ });
25
+ };
13
26
  var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
14
27
  var __typeError = (msg) => {
15
28
  throw TypeError(msg);
@@ -10,6 +10,19 @@ var __name = (target, name) => {
10
10
  });
11
11
  return target;
12
12
  };
13
+ var __returnValue = (v) => v;
14
+ function __exportSetter(name, newValue) {
15
+ this[name] = __returnValue.bind(null, newValue);
16
+ }
17
+ var __export = (target, all) => {
18
+ for (var name in all)
19
+ __defProp(target, name, {
20
+ get: all[name],
21
+ enumerable: true,
22
+ configurable: true,
23
+ set: __exportSetter.bind(all, name)
24
+ });
25
+ };
13
26
  var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
14
27
  var __typeError = (msg) => {
15
28
  throw TypeError(msg);
package/dist/vue/index.js CHANGED
@@ -10,6 +10,19 @@ var __name = (target, name) => {
10
10
  });
11
11
  return target;
12
12
  };
13
+ var __returnValue = (v) => v;
14
+ function __exportSetter(name, newValue) {
15
+ this[name] = __returnValue.bind(null, newValue);
16
+ }
17
+ var __export = (target, all) => {
18
+ for (var name in all)
19
+ __defProp(target, name, {
20
+ get: all[name],
21
+ enumerable: true,
22
+ configurable: true,
23
+ set: __exportSetter.bind(all, name)
24
+ });
25
+ };
13
26
  var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
14
27
  var __typeError = (msg) => {
15
28
  throw TypeError(msg);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.511",
3
+ "version": "0.0.22-beta.512",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",