@bash-app/bash-common 30.386.0 → 30.393.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/bashSectionTitleForBashType.test.js +5 -0
- package/dist/__tests__/bashSectionTitleForBashType.test.js.map +1 -1
- package/dist/__tests__/budgetApproval.test.d.ts +2 -0
- package/dist/__tests__/budgetApproval.test.d.ts.map +1 -0
- package/dist/__tests__/budgetApproval.test.js +109 -0
- package/dist/__tests__/budgetApproval.test.js.map +1 -0
- package/dist/__tests__/crewSocialCopy.test.d.ts +2 -0
- package/dist/__tests__/crewSocialCopy.test.d.ts.map +1 -0
- package/dist/__tests__/crewSocialCopy.test.js +21 -0
- package/dist/__tests__/crewSocialCopy.test.js.map +1 -0
- package/dist/__tests__/discoveryScope.test.d.ts +2 -0
- package/dist/__tests__/discoveryScope.test.d.ts.map +1 -0
- package/dist/__tests__/discoveryScope.test.js +40 -0
- package/dist/__tests__/discoveryScope.test.js.map +1 -0
- package/dist/__tests__/scenePulse.test.d.ts +2 -0
- package/dist/__tests__/scenePulse.test.d.ts.map +1 -0
- package/dist/__tests__/scenePulse.test.js +52 -0
- package/dist/__tests__/scenePulse.test.js.map +1 -0
- package/dist/__tests__/suggestedNight.test.d.ts +2 -0
- package/dist/__tests__/suggestedNight.test.d.ts.map +1 -0
- package/dist/__tests__/suggestedNight.test.js +10 -0
- package/dist/__tests__/suggestedNight.test.js.map +1 -0
- package/dist/__tests__/suggestionScore.test.d.ts +2 -0
- package/dist/__tests__/suggestionScore.test.d.ts.map +1 -0
- package/dist/__tests__/suggestionScore.test.js +68 -0
- package/dist/__tests__/suggestionScore.test.js.map +1 -0
- package/dist/__tests__/userTaste.test.d.ts +2 -0
- package/dist/__tests__/userTaste.test.d.ts.map +1 -0
- package/dist/__tests__/userTaste.test.js +21 -0
- package/dist/__tests__/userTaste.test.js.map +1 -0
- package/dist/budgetApproval.d.ts +126 -0
- package/dist/budgetApproval.d.ts.map +1 -0
- package/dist/budgetApproval.js +161 -0
- package/dist/budgetApproval.js.map +1 -0
- package/dist/crewSocialCopy.d.ts +16 -0
- package/dist/crewSocialCopy.d.ts.map +1 -0
- package/dist/crewSocialCopy.js +23 -0
- package/dist/crewSocialCopy.js.map +1 -0
- package/dist/definitions.d.ts +6 -0
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +6 -0
- package/dist/definitions.js.map +1 -1
- package/dist/discoveryScope.d.ts +48 -0
- package/dist/discoveryScope.d.ts.map +1 -0
- package/dist/discoveryScope.js +114 -0
- package/dist/discoveryScope.js.map +1 -0
- package/dist/extendedSchemas.d.ts +7 -0
- package/dist/extendedSchemas.d.ts.map +1 -1
- package/dist/extendedSchemas.js +8 -0
- package/dist/extendedSchemas.js.map +1 -1
- package/dist/home.d.ts +6 -1
- package/dist/home.d.ts.map +1 -1
- package/dist/home.js +5 -1
- package/dist/home.js.map +1 -1
- package/dist/hostCrmCrowdPulse.d.ts +2 -0
- package/dist/hostCrmCrowdPulse.d.ts.map +1 -1
- package/dist/hostCrmCrowdPulse.js.map +1 -1
- package/dist/hostCrmSegments.d.ts +6 -0
- package/dist/hostCrmSegments.d.ts.map +1 -1
- package/dist/hostCrmSegments.js.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -2
- package/dist/index.js.map +1 -1
- package/dist/scenePulse.d.ts +66 -0
- package/dist/scenePulse.d.ts.map +1 -0
- package/dist/scenePulse.js +54 -0
- package/dist/scenePulse.js.map +1 -0
- package/dist/sms/smsTemplates.d.ts +6 -1
- package/dist/sms/smsTemplates.d.ts.map +1 -1
- package/dist/sms/smsTemplates.js +1 -0
- package/dist/sms/smsTemplates.js.map +1 -1
- package/dist/suggestedNight.d.ts +12 -0
- package/dist/suggestedNight.d.ts.map +1 -0
- package/dist/suggestedNight.js +12 -0
- package/dist/suggestedNight.js.map +1 -0
- package/dist/suggestionScore.d.ts +42 -0
- package/dist/suggestionScore.d.ts.map +1 -0
- package/dist/suggestionScore.js +100 -0
- package/dist/suggestionScore.js.map +1 -0
- package/dist/userTaste.d.ts +40 -0
- package/dist/userTaste.d.ts.map +1 -0
- package/dist/userTaste.js +103 -0
- package/dist/userTaste.js.map +1 -0
- package/dist/utils/__tests__/featuredDiscoveryGeo.test.js +9 -1
- package/dist/utils/__tests__/featuredDiscoveryGeo.test.js.map +1 -1
- package/dist/utils/featuredDiscoveryGeo.d.ts +7 -0
- package/dist/utils/featuredDiscoveryGeo.d.ts.map +1 -1
- package/dist/utils/featuredDiscoveryGeo.js +11 -0
- package/dist/utils/featuredDiscoveryGeo.js.map +1 -1
- package/dist/utils/service/__tests__/foodAndBeverageQuote.test.d.ts +2 -0
- package/dist/utils/service/__tests__/foodAndBeverageQuote.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/foodAndBeverageQuote.test.js +353 -0
- package/dist/utils/service/__tests__/foodAndBeverageQuote.test.js.map +1 -0
- package/dist/utils/service/foodAndBeverageQuote.d.ts +171 -0
- package/dist/utils/service/foodAndBeverageQuote.d.ts.map +1 -0
- package/dist/utils/service/foodAndBeverageQuote.js +467 -0
- package/dist/utils/service/foodAndBeverageQuote.js.map +1 -0
- package/package.json +1 -1
- package/prisma/schema.prisma +185 -53
- package/src/__tests__/bashSectionTitleForBashType.test.ts +10 -0
- package/src/__tests__/budgetApproval.test.ts +153 -0
- package/src/__tests__/crewSocialCopy.test.ts +25 -0
- package/src/__tests__/discoveryScope.test.ts +65 -0
- package/src/__tests__/scenePulse.test.ts +69 -0
- package/src/__tests__/suggestedNight.test.ts +11 -0
- package/src/__tests__/suggestionScore.test.ts +81 -0
- package/src/__tests__/userTaste.test.ts +33 -0
- package/src/budgetApproval.ts +303 -0
- package/src/crewSocialCopy.ts +37 -0
- package/src/definitions.ts +11 -0
- package/src/discoveryScope.ts +170 -0
- package/src/extendedSchemas.ts +8 -0
- package/src/home.ts +15 -0
- package/src/hostCrmCrowdPulse.ts +2 -0
- package/src/hostCrmSegments.ts +6 -0
- package/src/index.ts +18 -3
- package/src/scenePulse.ts +113 -0
- package/src/sms/smsTemplates.ts +6 -0
- package/src/suggestedNight.ts +20 -0
- package/src/suggestionScore.ts +140 -0
- package/src/userTaste.ts +152 -0
- package/src/utils/__tests__/featuredDiscoveryGeo.test.ts +13 -0
- package/src/utils/featuredDiscoveryGeo.ts +15 -0
- package/src/utils/service/__tests__/foodAndBeverageQuote.test.ts +502 -0
- package/src/utils/service/foodAndBeverageQuote.ts +613 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Geographic / importance scope for public discovery.
|
|
3
|
+
* Orthogonal to `BashEventType` — a Fair can be Local or State.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const DiscoveryScope = {
|
|
7
|
+
Local: "Local",
|
|
8
|
+
State: "State",
|
|
9
|
+
National: "National",
|
|
10
|
+
International: "International",
|
|
11
|
+
Unspecified: "Unspecified",
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
export type DiscoveryScope =
|
|
15
|
+
(typeof DiscoveryScope)[keyof typeof DiscoveryScope];
|
|
16
|
+
|
|
17
|
+
export const DISCOVERY_SCOPE_VALUES: readonly DiscoveryScope[] = [
|
|
18
|
+
DiscoveryScope.Local,
|
|
19
|
+
DiscoveryScope.State,
|
|
20
|
+
DiscoveryScope.National,
|
|
21
|
+
DiscoveryScope.International,
|
|
22
|
+
DiscoveryScope.Unspecified,
|
|
23
|
+
] as const;
|
|
24
|
+
|
|
25
|
+
/** Homepage / category row keys (not Unspecified). */
|
|
26
|
+
export const DISCOVERY_SCOPE_ROW_KEYS = [
|
|
27
|
+
DiscoveryScope.Local,
|
|
28
|
+
DiscoveryScope.State,
|
|
29
|
+
DiscoveryScope.National,
|
|
30
|
+
DiscoveryScope.International,
|
|
31
|
+
] as const;
|
|
32
|
+
|
|
33
|
+
export const DISCOVERY_SCOPE_SECTION_TITLE: Record<
|
|
34
|
+
(typeof DISCOVERY_SCOPE_ROW_KEYS)[number],
|
|
35
|
+
string
|
|
36
|
+
> = {
|
|
37
|
+
Local: "Local",
|
|
38
|
+
State: "Statewide",
|
|
39
|
+
National: "Across the country",
|
|
40
|
+
International: "Worth the flight",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Hosts may set these without a hard signal. */
|
|
44
|
+
export const HOST_SETTABLE_DISCOVERY_SCOPES: readonly DiscoveryScope[] = [
|
|
45
|
+
DiscoveryScope.Unspecified,
|
|
46
|
+
DiscoveryScope.Local,
|
|
47
|
+
DiscoveryScope.State,
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
export const HOST_SCOPE_CHIPS: ReadonlyArray<{
|
|
51
|
+
id: DiscoveryScope;
|
|
52
|
+
label: string;
|
|
53
|
+
}> = [
|
|
54
|
+
{ id: DiscoveryScope.Unspecified, label: "Not sure yet" },
|
|
55
|
+
{ id: DiscoveryScope.Local, label: "Just the neighborhood / city-wide" },
|
|
56
|
+
{
|
|
57
|
+
id: DiscoveryScope.State,
|
|
58
|
+
label: "People will drive from across the state",
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
export function isDiscoveryScope(value: unknown): value is DiscoveryScope {
|
|
63
|
+
return (
|
|
64
|
+
typeof value === "string" &&
|
|
65
|
+
(DISCOVERY_SCOPE_VALUES as readonly string[]).includes(value)
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function isHostSettableDiscoveryScope(
|
|
70
|
+
value: unknown
|
|
71
|
+
): value is DiscoveryScope {
|
|
72
|
+
return (
|
|
73
|
+
typeof value === "string" &&
|
|
74
|
+
(HOST_SETTABLE_DISCOVERY_SCOPES as readonly string[]).includes(value)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function isNationalOrInternational(scope: DiscoveryScope): boolean {
|
|
79
|
+
return (
|
|
80
|
+
scope === DiscoveryScope.National || scope === DiscoveryScope.International
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Known touring / destination brands that are a hard signal for National.
|
|
86
|
+
* Small allowlist — not a taxonomy.
|
|
87
|
+
*/
|
|
88
|
+
export const NATIONAL_SCOPE_BRAND_PATTERN =
|
|
89
|
+
/\b(insomniac|c3 presents|aeg presents|live nation|coachella|lollapalooza|bonnaroo|ultra miami|edc las vegas|edc vegas|burning man|sxsw|comic-?con|ces\b|super bowl|final four|wrestlemania)\b/i;
|
|
90
|
+
|
|
91
|
+
export const INTERNATIONAL_SCOPE_PATTERN =
|
|
92
|
+
/\b(world tour|world cup|olympics|tomorrowland|glastonbury|primavera sound|oktoberfest|cannes|eurovision|ibiza)\b/i;
|
|
93
|
+
|
|
94
|
+
export const STATE_SCOPE_PATTERN =
|
|
95
|
+
/\b(state fair|statewide|governor'?s? (?:ball|festival)|days of ['’]?47|pioneer day)\b/i;
|
|
96
|
+
|
|
97
|
+
export const LOCAL_SCOPE_PATTERN =
|
|
98
|
+
/\b(block party|neighborhood|community day|city-sponsored|city festival)\b/i;
|
|
99
|
+
|
|
100
|
+
export function inferDiscoveryScopeFromText(
|
|
101
|
+
title: string | undefined,
|
|
102
|
+
description?: string | undefined
|
|
103
|
+
): DiscoveryScope {
|
|
104
|
+
const text = `${title ?? ""} ${description ?? ""}`.trim();
|
|
105
|
+
if (!text) return DiscoveryScope.Unspecified;
|
|
106
|
+
if (INTERNATIONAL_SCOPE_PATTERN.test(text)) {
|
|
107
|
+
return DiscoveryScope.International;
|
|
108
|
+
}
|
|
109
|
+
if (NATIONAL_SCOPE_BRAND_PATTERN.test(text)) {
|
|
110
|
+
return DiscoveryScope.National;
|
|
111
|
+
}
|
|
112
|
+
if (STATE_SCOPE_PATTERN.test(text)) return DiscoveryScope.State;
|
|
113
|
+
if (LOCAL_SCOPE_PATTERN.test(text)) return DiscoveryScope.Local;
|
|
114
|
+
return DiscoveryScope.Unspecified;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export type DiscoveryScopeWriteRole = "host" | "superUser" | "scraper";
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Hosts cannot self-promote to National/International unless a hard text
|
|
121
|
+
* signal is already present (or the writer is a super-user / scraper).
|
|
122
|
+
*/
|
|
123
|
+
export function resolveDiscoveryScopeForWrite(args: {
|
|
124
|
+
requested: string | null | undefined;
|
|
125
|
+
existing?: string | null | undefined;
|
|
126
|
+
role: DiscoveryScopeWriteRole;
|
|
127
|
+
title?: string | null;
|
|
128
|
+
description?: string | null;
|
|
129
|
+
}): DiscoveryScope {
|
|
130
|
+
const inferred = inferDiscoveryScopeFromText(
|
|
131
|
+
args.title ?? undefined,
|
|
132
|
+
args.description ?? undefined
|
|
133
|
+
);
|
|
134
|
+
const requested = isDiscoveryScope(args.requested)
|
|
135
|
+
? args.requested
|
|
136
|
+
: undefined;
|
|
137
|
+
const existing = isDiscoveryScope(args.existing) ? args.existing : undefined;
|
|
138
|
+
|
|
139
|
+
if (args.role === "superUser" || args.role === "scraper") {
|
|
140
|
+
return requested ?? existing ?? inferred;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (requested && isHostSettableDiscoveryScope(requested)) {
|
|
144
|
+
return requested;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (
|
|
148
|
+
requested &&
|
|
149
|
+
isNationalOrInternational(requested) &&
|
|
150
|
+
inferred === requested
|
|
151
|
+
) {
|
|
152
|
+
return requested;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (existing && isHostSettableDiscoveryScope(existing)) {
|
|
156
|
+
return existing;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (existing && isNationalOrInternational(existing)) {
|
|
160
|
+
return existing;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (inferred !== DiscoveryScope.Unspecified && !requested) {
|
|
164
|
+
return inferred;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return DiscoveryScope.Unspecified;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const MIN_EVENTS_FOR_SCOPE_ROW = 3;
|
package/src/extendedSchemas.ts
CHANGED
|
@@ -1054,6 +1054,14 @@ export const SERVICE_PUBLIC_LIST_DATA_TO_INCLUDE = {
|
|
|
1054
1054
|
eventServiceSubType: true,
|
|
1055
1055
|
formatOptions: true,
|
|
1056
1056
|
crowdSize: true,
|
|
1057
|
+
// F&B per-guest pricing & logistics fields
|
|
1058
|
+
pricingModel: true,
|
|
1059
|
+
baseRateCents: true,
|
|
1060
|
+
minimumQuantity: true,
|
|
1061
|
+
minimumChargeCents: true,
|
|
1062
|
+
goodsOrServices: true,
|
|
1063
|
+
fulfillmentModes: true,
|
|
1064
|
+
leadTimeDays: true,
|
|
1057
1065
|
},
|
|
1058
1066
|
},
|
|
1059
1067
|
entertainmentService: {
|
package/src/home.ts
CHANGED
|
@@ -23,7 +23,22 @@ export {
|
|
|
23
23
|
|
|
24
24
|
export {
|
|
25
25
|
DEFAULT_FEATURED_RADIUS_MILES,
|
|
26
|
+
haversineMiles,
|
|
26
27
|
sortPublicEventsForFeaturedDiscovery,
|
|
27
28
|
} from "./utils/featuredDiscoveryGeo.js";
|
|
28
29
|
|
|
29
30
|
export { causeTagsMatchSearch } from "./causeTags.js";
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
DiscoveryScope,
|
|
34
|
+
DISCOVERY_SCOPE_ROW_KEYS,
|
|
35
|
+
MIN_EVENTS_FOR_SCOPE_ROW,
|
|
36
|
+
isNationalOrInternational,
|
|
37
|
+
} from "./discoveryScope.js";
|
|
38
|
+
|
|
39
|
+
export { userHasTravelTaste, UserTasteKind } from "./userTaste.js";
|
|
40
|
+
|
|
41
|
+
export { CREW_GOING_RAIL_TITLE, SCENE_PROMPT_COPY } from "./crewSocialCopy.js";
|
|
42
|
+
|
|
43
|
+
export { scoreSuggestion } from "./suggestionScore.js";
|
|
44
|
+
export type { SuggestionTasteRow } from "./suggestionScore.js";
|
package/src/hostCrmCrowdPulse.ts
CHANGED
|
@@ -83,6 +83,8 @@ export type HostCrmCrowdPulseBash = {
|
|
|
83
83
|
capacity: number | null;
|
|
84
84
|
/** Distinct checked-in ticket holders. */
|
|
85
85
|
checkedInCount: number;
|
|
86
|
+
/** Anonymized Accepted SuggestedNight / favorite saves. */
|
|
87
|
+
savedCount: number;
|
|
86
88
|
};
|
|
87
89
|
|
|
88
90
|
export type HostCrmCrowdPulse = {
|
package/src/hostCrmSegments.ts
CHANGED
|
@@ -41,6 +41,12 @@ export type HostCrmSegmentRules = {
|
|
|
41
41
|
bringsGuests?: boolean;
|
|
42
42
|
/** Ticket buyers for any of these bash event ids (e.g. the host's 3 most recently ended bashes — see the last3Events preset) */
|
|
43
43
|
sourceEventIdIn?: string[];
|
|
44
|
+
/** Guest taste: EventType value (own audience + hostsMayTargetTaste only). */
|
|
45
|
+
tasteEventType?: string;
|
|
46
|
+
/** Guest taste: MusicGenre value. */
|
|
47
|
+
tasteGenre?: string;
|
|
48
|
+
/** Guest opted into National/International travel chips. */
|
|
49
|
+
tasteTravel?: boolean;
|
|
44
50
|
};
|
|
45
51
|
|
|
46
52
|
export type HostCrmDiscoverySource = "promoter" | "referral" | "direct";
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from "./definitions.js";
|
|
2
2
|
export * from "./fundingMeter.js";
|
|
3
|
+
export * from "./budgetApproval.js";
|
|
3
4
|
export * from "./giveaway.js";
|
|
4
5
|
export * from "./requestAuthUser.js";
|
|
5
6
|
export * from "./sms/smsTemplates.js";
|
|
@@ -13,6 +14,11 @@ export * from "./sideQuestIdeas.js";
|
|
|
13
14
|
export * from "./sideQuestTypes.js";
|
|
14
15
|
export * from "./icebreakerPrompts.js";
|
|
15
16
|
export * from "./causeTags.js";
|
|
17
|
+
export * from "./discoveryScope.js";
|
|
18
|
+
export * from "./userTaste.js";
|
|
19
|
+
export * from "./crewSocialCopy.js";
|
|
20
|
+
export * from "./suggestionScore.js";
|
|
21
|
+
export * from "./suggestedNight.js";
|
|
16
22
|
export * from "./competitionValidation.js";
|
|
17
23
|
export * from "./groupTicketUtils.js";
|
|
18
24
|
export * from "./guestPartySize.js";
|
|
@@ -36,6 +42,7 @@ export * from "./accessScreening.js";
|
|
|
36
42
|
export * from "./applicationFunnel.js";
|
|
37
43
|
export * from "./hostCrmSegments.js";
|
|
38
44
|
export * from "./hostCrmCrowdPulse.js";
|
|
45
|
+
export * from "./scenePulse.js";
|
|
39
46
|
export * from "./hostCrmLastBashProof.js";
|
|
40
47
|
export * from "./hostCrmInsights.js";
|
|
41
48
|
export * from "./hostCrmAutomation.js";
|
|
@@ -55,10 +62,15 @@ export * from "./targetAudienceLabels.js";
|
|
|
55
62
|
export * from "./liveActivity/liveActivityTypes.js";
|
|
56
63
|
|
|
57
64
|
// Re-export ALL Prisma enums as values (usable as runtime constants, e.g. ServiceTypes.Entertainment)
|
|
58
|
-
// Excludes enums already re-exported by definitions.ts, mirroredPrismaEnums.ts,
|
|
65
|
+
// Excludes enums already re-exported by definitions.ts, mirroredPrismaEnums.ts, membershipDefinitions.ts,
|
|
66
|
+
// and local domain modules (so API boot does not wait on prisma generate):
|
|
59
67
|
// mirroredPrismaEnums.ts: ServiceCancellationPolicy
|
|
60
68
|
// definitions.ts: YearsOfExperience, GovIdRetentionPolicy, EntertainmentServiceType, MusicGenreType, ServiceTypes,
|
|
61
69
|
// membershipDefinitions.ts: MembershipTier, ReferralTier, CreditTransactionType
|
|
70
|
+
// budgetApproval.ts: BudgetApprovalKind, BudgetApprovalStatus
|
|
71
|
+
// discoveryScope.ts: DiscoveryScope
|
|
72
|
+
// userTaste.ts: UserTasteKind, UserTasteSource
|
|
73
|
+
// suggestedNight.ts: SuggestedNightStatus
|
|
62
74
|
export {
|
|
63
75
|
$Enums,
|
|
64
76
|
AISpecialistFormat,
|
|
@@ -107,13 +119,13 @@ export {
|
|
|
107
119
|
BrandStrategistFormat,
|
|
108
120
|
CPAsFormat,
|
|
109
121
|
CateringFormat,
|
|
122
|
+
CelebrationCakeFormat,
|
|
123
|
+
GrazingTableFormat,
|
|
110
124
|
CelebrityAppearanceFormat,
|
|
111
125
|
CenterpiecesFormat,
|
|
112
126
|
ChairsFormat,
|
|
113
127
|
ClaimRequestStatus,
|
|
114
128
|
CleaningSubType,
|
|
115
|
-
ClubAdminRole,
|
|
116
|
-
ClubMemberStatus,
|
|
117
129
|
ConnectionStatus,
|
|
118
130
|
ConsentType,
|
|
119
131
|
ConsultantFormat,
|
|
@@ -189,6 +201,7 @@ export {
|
|
|
189
201
|
NotificationPriority,
|
|
190
202
|
NotificationType,
|
|
191
203
|
Occupation,
|
|
204
|
+
OfficiantFormat,
|
|
192
205
|
OfferClaimStatus,
|
|
193
206
|
OfferDiscountType,
|
|
194
207
|
OrganizationArtistInvitationStatus,
|
|
@@ -225,6 +238,7 @@ export {
|
|
|
225
238
|
RecurringFrequency,
|
|
226
239
|
RedemptionMethod,
|
|
227
240
|
RentalEquipmentSubType,
|
|
241
|
+
RestroomTrailerFormat,
|
|
228
242
|
ReportStatus,
|
|
229
243
|
ReportingFormat,
|
|
230
244
|
RewardReportStatus,
|
|
@@ -429,6 +443,7 @@ export * from "./utils/mathUtils.js";
|
|
|
429
443
|
export * from "./utils/birthdayExperienceQuality.js";
|
|
430
444
|
export * from "./utils/service/apiServiceBookingApiUtils.js";
|
|
431
445
|
export * from "./utils/service/frontendServiceBookingUtils.js";
|
|
446
|
+
export * from "./utils/service/foodAndBeverageQuote.js";
|
|
432
447
|
export * from "./utils/service/serviceBookingStatusUtils.js";
|
|
433
448
|
export * from "./utils/service/serviceDBUtils.js";
|
|
434
449
|
export * from "./utils/service/serviceRateUtils.js";
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Community Builder scene pulse — city / region / world aggregates.
|
|
3
|
+
* You (Crowd Pulse) stays host ops; these scopes are public scene energy + platform proof.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const SCENE_PULSE_SCOPES = ["city", "region", "world"] as const;
|
|
7
|
+
|
|
8
|
+
export type ScenePulseScope = (typeof SCENE_PULSE_SCOPES)[number];
|
|
9
|
+
|
|
10
|
+
export type ScenePulseGeo = {
|
|
11
|
+
city: string | null;
|
|
12
|
+
state: string | null;
|
|
13
|
+
country: string | null;
|
|
14
|
+
/** Short label for copy ("Salt Lake City", "Utah", "World"). */
|
|
15
|
+
label: string;
|
|
16
|
+
timeZone: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type ScenePulseEnergy = {
|
|
20
|
+
/** Public rooms currently live. */
|
|
21
|
+
liveNow: number;
|
|
22
|
+
/** Unique Going RSVPs + ticketed people on live + tonight + this weekend. */
|
|
23
|
+
going: number;
|
|
24
|
+
tonightBashes: number;
|
|
25
|
+
tonightGoing: number;
|
|
26
|
+
weekendBashes: number;
|
|
27
|
+
weekendGoing: number;
|
|
28
|
+
/** Unique people who RSVP'd Going or paid for a ticket in the last 7 days. */
|
|
29
|
+
newThisWeek: number;
|
|
30
|
+
/**
|
|
31
|
+
* Distinct live/tonight/weekend public bashes with a lineup slot or a
|
|
32
|
+
* host-spotlighted featured guest (names in the room — not CRM).
|
|
33
|
+
*/
|
|
34
|
+
namesInTheRoom: number;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** World-only cumulative credibility. Null on city/region. */
|
|
38
|
+
export type ScenePulseWorldCred = {
|
|
39
|
+
totalPeople: number;
|
|
40
|
+
bashesHosted: number;
|
|
41
|
+
ticketsSold: number;
|
|
42
|
+
gmvCents: number;
|
|
43
|
+
citiesRepresented: number;
|
|
44
|
+
regionsRepresented: number;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type ScenePulse = {
|
|
48
|
+
scope: ScenePulseScope;
|
|
49
|
+
geo: ScenePulseGeo | null;
|
|
50
|
+
/** True when city (or region) can't be resolved from profile or hosted bashes. */
|
|
51
|
+
needsLocation: boolean;
|
|
52
|
+
energy: ScenePulseEnergy;
|
|
53
|
+
world: ScenePulseWorldCred | null;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** Optional city/state used to pulse a scene other than the host's profile. */
|
|
57
|
+
export type ScenePulseGeoOverride = {
|
|
58
|
+
city: string | null;
|
|
59
|
+
state: string | null;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const MAX_GEO_CITY_CHARS = 80;
|
|
63
|
+
const MAX_GEO_STATE_CHARS = 40;
|
|
64
|
+
|
|
65
|
+
function normalizeGeoPart(raw: unknown, maxChars: number): string | null {
|
|
66
|
+
if (typeof raw !== "string") return null;
|
|
67
|
+
const trimmed = raw.replace(/\s+/g, " ").trim();
|
|
68
|
+
if (!trimmed) return null;
|
|
69
|
+
return trimmed.slice(0, maxChars);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Blank city+state → null (use profile). Otherwise a trimmed override.
|
|
74
|
+
*/
|
|
75
|
+
export function normalizeScenePulseGeoOverride(input: {
|
|
76
|
+
city?: unknown;
|
|
77
|
+
state?: unknown;
|
|
78
|
+
}): ScenePulseGeoOverride | null {
|
|
79
|
+
const city = normalizeGeoPart(input.city, MAX_GEO_CITY_CHARS);
|
|
80
|
+
const state = normalizeGeoPart(input.state, MAX_GEO_STATE_CHARS);
|
|
81
|
+
if (!city && !state) return null;
|
|
82
|
+
return { city, state };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function emptyScenePulseEnergy(): ScenePulseEnergy {
|
|
86
|
+
return {
|
|
87
|
+
liveNow: 0,
|
|
88
|
+
going: 0,
|
|
89
|
+
tonightBashes: 0,
|
|
90
|
+
tonightGoing: 0,
|
|
91
|
+
weekendBashes: 0,
|
|
92
|
+
weekendGoing: 0,
|
|
93
|
+
newThisWeek: 0,
|
|
94
|
+
namesInTheRoom: 0,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Missing / blank → city. Invalid string → null (caller should 400).
|
|
100
|
+
*/
|
|
101
|
+
export function parseScenePulseScope(raw: unknown): ScenePulseScope | null {
|
|
102
|
+
if (raw == null || raw === "") return "city";
|
|
103
|
+
if (typeof raw !== "string") return null;
|
|
104
|
+
const normalized = raw.trim().toLowerCase();
|
|
105
|
+
if (
|
|
106
|
+
normalized === "city" ||
|
|
107
|
+
normalized === "region" ||
|
|
108
|
+
normalized === "world"
|
|
109
|
+
) {
|
|
110
|
+
return normalized;
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
package/src/sms/smsTemplates.ts
CHANGED
|
@@ -15,6 +15,7 @@ export const SMS_TEMPLATE_KEYS = [
|
|
|
15
15
|
"EVENT_WAITLIST_APPROVED",
|
|
16
16
|
"TICKET_CONFIRMED",
|
|
17
17
|
"COAT_CHECK_CLAIM",
|
|
18
|
+
"SUGGESTED_NIGHT_REMINDER_24H",
|
|
18
19
|
] as const;
|
|
19
20
|
|
|
20
21
|
export type SmsTemplateKey = (typeof SMS_TEMPLATE_KEYS)[number];
|
|
@@ -56,4 +57,9 @@ export type SmsTemplateContext = {
|
|
|
56
57
|
claimLabel: string;
|
|
57
58
|
ticketsUrl?: string;
|
|
58
59
|
};
|
|
60
|
+
SUGGESTED_NIGHT_REMINDER_24H: {
|
|
61
|
+
eventTitle: string;
|
|
62
|
+
shortUrl: string;
|
|
63
|
+
socialProof?: string;
|
|
64
|
+
};
|
|
59
65
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const SuggestedNightStatus = {
|
|
2
|
+
Proposed: "Proposed",
|
|
3
|
+
Accepted: "Accepted",
|
|
4
|
+
Dismissed: "Dismissed",
|
|
5
|
+
Expired: "Expired",
|
|
6
|
+
} as const;
|
|
7
|
+
|
|
8
|
+
export type SuggestedNightStatus =
|
|
9
|
+
(typeof SuggestedNightStatus)[keyof typeof SuggestedNightStatus];
|
|
10
|
+
|
|
11
|
+
export const FAVORITE_NUDGE_REMINDER_TYPE = "SUGGESTED_NIGHT_24H";
|
|
12
|
+
|
|
13
|
+
export type UnfavoriteSuggestedNightAction = "delete" | "dismiss";
|
|
14
|
+
|
|
15
|
+
/** Never nudged → delete. Already nudged → keep Dismissed so we do not re-propose. */
|
|
16
|
+
export function unfavoriteSuggestedNightAction(
|
|
17
|
+
remindedAt: Date | string | null | undefined
|
|
18
|
+
): UnfavoriteSuggestedNightAction {
|
|
19
|
+
return remindedAt ? "dismiss" : "delete";
|
|
20
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { DiscoveryScope, isNationalOrInternational } from "./discoveryScope.js";
|
|
2
|
+
import { UserTasteKind, UserTasteSource } from "./userTaste.js";
|
|
3
|
+
|
|
4
|
+
export type SuggestionTasteRow = {
|
|
5
|
+
kind: string;
|
|
6
|
+
value: string;
|
|
7
|
+
weight: number;
|
|
8
|
+
source: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type SuggestionSignals = {
|
|
12
|
+
connectionsGoingVisible: number;
|
|
13
|
+
connectionsGoingHidden: number;
|
|
14
|
+
followedHost: boolean;
|
|
15
|
+
attendedHost: boolean;
|
|
16
|
+
eventType: string;
|
|
17
|
+
causeTags: readonly string[];
|
|
18
|
+
musicGenre?: string | null;
|
|
19
|
+
discoveryScope: string;
|
|
20
|
+
artistsToSee: readonly string[];
|
|
21
|
+
eventTitle: string;
|
|
22
|
+
distanceMiles: number | null;
|
|
23
|
+
radiusMiles: number;
|
|
24
|
+
isFeatured: boolean;
|
|
25
|
+
trendingScore: number;
|
|
26
|
+
paidFeatureTasteOverlap: boolean;
|
|
27
|
+
tastes: readonly SuggestionTasteRow[];
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const SUGGESTION_WEIGHTS = {
|
|
31
|
+
connectionsGoing: 40,
|
|
32
|
+
followedHost: 24,
|
|
33
|
+
attendedHost: 20,
|
|
34
|
+
explicitTaste: 16,
|
|
35
|
+
explicitTasteLove: 32,
|
|
36
|
+
inferredTaste: 8,
|
|
37
|
+
travelScope: 10,
|
|
38
|
+
featured: 3,
|
|
39
|
+
trending: 2,
|
|
40
|
+
paidFeatureTaste: 1,
|
|
41
|
+
artistsToSee: 4,
|
|
42
|
+
} as const;
|
|
43
|
+
|
|
44
|
+
export const PAID_FEATURE_TASTE_CAP = SUGGESTION_WEIGHTS.paidFeatureTaste;
|
|
45
|
+
|
|
46
|
+
export function scoreSuggestion(signals: SuggestionSignals): number {
|
|
47
|
+
let score = 0;
|
|
48
|
+
if (signals.connectionsGoingVisible + signals.connectionsGoingHidden > 0) {
|
|
49
|
+
score += SUGGESTION_WEIGHTS.connectionsGoing;
|
|
50
|
+
}
|
|
51
|
+
if (signals.followedHost) score += SUGGESTION_WEIGHTS.followedHost;
|
|
52
|
+
if (signals.attendedHost) score += SUGGESTION_WEIGHTS.attendedHost;
|
|
53
|
+
|
|
54
|
+
for (const taste of signals.tastes) {
|
|
55
|
+
if (taste.weight < 0) {
|
|
56
|
+
if (tasteMatchesEvent(taste, signals)) return Number.NEGATIVE_INFINITY;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (!tasteMatchesEvent(taste, signals)) continue;
|
|
60
|
+
if (taste.kind === UserTasteKind.DiscoveryScope) {
|
|
61
|
+
score += SUGGESTION_WEIGHTS.travelScope;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (taste.source === UserTasteSource.Explicit) {
|
|
65
|
+
score +=
|
|
66
|
+
taste.weight === 2
|
|
67
|
+
? SUGGESTION_WEIGHTS.explicitTasteLove
|
|
68
|
+
: SUGGESTION_WEIGHTS.explicitTaste;
|
|
69
|
+
} else {
|
|
70
|
+
score += SUGGESTION_WEIGHTS.inferredTaste;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (signals.isFeatured) score += SUGGESTION_WEIGHTS.featured;
|
|
75
|
+
if (signals.trendingScore > 0) score += SUGGESTION_WEIGHTS.trending;
|
|
76
|
+
if (signals.paidFeatureTasteOverlap) {
|
|
77
|
+
score += PAID_FEATURE_TASTE_CAP;
|
|
78
|
+
}
|
|
79
|
+
if (
|
|
80
|
+
signals.artistsToSee.some((a) =>
|
|
81
|
+
signals.eventTitle.toLowerCase().includes(a.toLowerCase())
|
|
82
|
+
)
|
|
83
|
+
) {
|
|
84
|
+
score += SUGGESTION_WEIGHTS.artistsToSee;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const withinRadius = isWithinDiscoveryRadius(signals);
|
|
88
|
+
if (!withinRadius) return Number.NEGATIVE_INFINITY;
|
|
89
|
+
|
|
90
|
+
if (signals.distanceMiles != null && signals.radiusMiles > 0) {
|
|
91
|
+
const decay = Math.min(1, signals.distanceMiles / signals.radiusMiles);
|
|
92
|
+
score -= decay * 4;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return score;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function isWithinDiscoveryRadius(signals: SuggestionSignals): boolean {
|
|
99
|
+
const travel = signals.tastes.some(
|
|
100
|
+
(t) =>
|
|
101
|
+
t.kind === UserTasteKind.DiscoveryScope &&
|
|
102
|
+
isNationalOrInternational(t.value as DiscoveryScope) &&
|
|
103
|
+
t.weight > 0
|
|
104
|
+
);
|
|
105
|
+
if (
|
|
106
|
+
travel &&
|
|
107
|
+
isNationalOrInternational(signals.discoveryScope as DiscoveryScope)
|
|
108
|
+
) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
if (signals.distanceMiles == null) return true;
|
|
112
|
+
return signals.distanceMiles <= signals.radiusMiles;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function tasteMatchesEvent(
|
|
116
|
+
taste: SuggestionTasteRow,
|
|
117
|
+
signals: SuggestionSignals
|
|
118
|
+
): boolean {
|
|
119
|
+
switch (taste.kind) {
|
|
120
|
+
case UserTasteKind.EventType:
|
|
121
|
+
return signals.eventType === taste.value;
|
|
122
|
+
case UserTasteKind.CauseTag:
|
|
123
|
+
return signals.causeTags.includes(taste.value);
|
|
124
|
+
case UserTasteKind.MusicGenre:
|
|
125
|
+
return (signals.musicGenre ?? "") === taste.value;
|
|
126
|
+
case UserTasteKind.DiscoveryScope:
|
|
127
|
+
return signals.discoveryScope === taste.value;
|
|
128
|
+
default:
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function crewOutranksPaid(signals: SuggestionSignals): boolean {
|
|
134
|
+
const crew =
|
|
135
|
+
signals.connectionsGoingVisible +
|
|
136
|
+
signals.connectionsGoingHidden +
|
|
137
|
+
(signals.followedHost ? 1 : 0) +
|
|
138
|
+
(signals.attendedHost ? 1 : 0);
|
|
139
|
+
return crew > 0;
|
|
140
|
+
}
|