@bash-app/bash-common 30.176.0 → 30.178.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/index.d.ts CHANGED
@@ -34,6 +34,7 @@ export * from "./utils/entityUtils.js";
34
34
  export * from "./utils/generalDateTimeUtils.js";
35
35
  export * from "./utils/luxonUtils.js";
36
36
  export * from "./utils/mathUtils.js";
37
+ export * from "./utils/birthdayExperienceQuality.js";
37
38
  export * from "./utils/service/apiServiceBookingApiUtils.js";
38
39
  export * from "./utils/service/frontendServiceBookingUtils.js";
39
40
  export * from "./utils/service/serviceBookingStatusUtils.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAG3C,YAAY,EACV,OAAO,EACP,YAAY,EACZ,IAAI,EACJ,SAAS,EACT,YAAY,EACZ,mBAAmB,EACnB,OAAO,GACR,MAAM,gBAAgB,CAAC;AAExB,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mCAAmC,CAAC;AAClD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6CAA6C,CAAC;AAC5D,cAAc,gDAAgD,CAAC;AAC/D,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wCAAwC,CAAC;AACvD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAEhD,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,qBAAqB,CAAC;AACpC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,kCAAkC,CAAC;AACjD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,iCAAiC,CAAC;AAChD,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8CAA8C,CAAC;AAC7D,cAAc,gDAAgD,CAAC;AAC/D,cAAc,8CAA8C,CAAC;AAC7D,cAAc,mCAAmC,CAAC;AAClD,cAAc,qCAAqC,CAAC;AACpD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,sBAAsB,CAAC;AACrC,cAAc,+BAA+B,CAAC;AAG9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,YAAY,EACR,WAAW,EAAE,YAAY,EACzB,YAAY,EAAE,QAAQ,EAAE,sBAAsB,EAAE,cAAc,EACjE,MAAM,sBAAsB,CAAC;AAG9B,cAAc,+BAA+B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAG3C,YAAY,EACV,OAAO,EACP,YAAY,EACZ,IAAI,EACJ,SAAS,EACT,YAAY,EACZ,mBAAmB,EACnB,OAAO,GACR,MAAM,gBAAgB,CAAC;AAExB,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mCAAmC,CAAC;AAClD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6CAA6C,CAAC;AAC5D,cAAc,gDAAgD,CAAC;AAC/D,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wCAAwC,CAAC;AACvD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAEhD,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,qBAAqB,CAAC;AACpC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,kCAAkC,CAAC;AACjD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,iCAAiC,CAAC;AAChD,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sCAAsC,CAAC;AACrD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,gDAAgD,CAAC;AAC/D,cAAc,8CAA8C,CAAC;AAC7D,cAAc,mCAAmC,CAAC;AAClD,cAAc,qCAAqC,CAAC;AACpD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,sBAAsB,CAAC;AACrC,cAAc,+BAA+B,CAAC;AAG9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,YAAY,EACR,WAAW,EAAE,YAAY,EACzB,YAAY,EAAE,QAAQ,EAAE,sBAAsB,EAAE,cAAc,EACjE,MAAM,sBAAsB,CAAC;AAG9B,cAAc,+BAA+B,CAAC"}
package/dist/index.js CHANGED
@@ -34,6 +34,7 @@ export * from "./utils/entityUtils.js";
34
34
  export * from "./utils/generalDateTimeUtils.js";
35
35
  export * from "./utils/luxonUtils.js";
36
36
  export * from "./utils/mathUtils.js";
37
+ export * from "./utils/birthdayExperienceQuality.js";
37
38
  export * from "./utils/service/apiServiceBookingApiUtils.js";
38
39
  export * from "./utils/service/frontendServiceBookingUtils.js";
39
40
  export * from "./utils/service/serviceBookingStatusUtils.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAa3C,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mCAAmC,CAAC;AAClD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6CAA6C,CAAC;AAC5D,cAAc,gDAAgD,CAAC;AAC/D,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wCAAwC,CAAC;AACvD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,+DAA+D;AAC/D,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,qBAAqB,CAAC;AACpC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,kCAAkC,CAAC;AACjD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,iCAAiC,CAAC;AAChD,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8CAA8C,CAAC;AAC7D,cAAc,gDAAgD,CAAC;AAC/D,cAAc,8CAA8C,CAAC;AAC7D,cAAc,mCAAmC,CAAC;AAClD,cAAc,qCAAqC,CAAC;AACpD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,sBAAsB,CAAC;AACrC,cAAc,+BAA+B,CAAC;AAE9C,iFAAiF;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAM3D,kBAAkB;AAClB,cAAc,+BAA+B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAa3C,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mCAAmC,CAAC;AAClD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6CAA6C,CAAC;AAC5D,cAAc,gDAAgD,CAAC;AAC/D,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wCAAwC,CAAC;AACvD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,+DAA+D;AAC/D,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,qBAAqB,CAAC;AACpC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,kCAAkC,CAAC;AACjD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,iCAAiC,CAAC;AAChD,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sCAAsC,CAAC;AACrD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,gDAAgD,CAAC;AAC/D,cAAc,8CAA8C,CAAC;AAC7D,cAAc,mCAAmC,CAAC;AAClD,cAAc,qCAAqC,CAAC;AACpD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,sBAAsB,CAAC;AACrC,cAAc,+BAA+B,CAAC;AAE9C,iFAAiF;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAM3D,kBAAkB;AAClB,cAAc,+BAA+B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=birthdayExperienceQuality.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"birthdayExperienceQuality.test.d.ts","sourceRoot":"","sources":["../../../src/utils/__tests__/birthdayExperienceQuality.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,22 @@
1
+ import { averageExperienceStars, EXPERIENCE_STAR_BANDS, } from "../birthdayExperienceQuality.js";
2
+ describe("averageExperienceStars", () => {
3
+ it("returns null when there are no votes", () => {
4
+ expect(averageExperienceStars({ Awesome: 0, Okay: 0, Lame: 0 })).toBeNull();
5
+ expect(averageExperienceStars(undefined)).toBeNull();
6
+ });
7
+ it("returns midpoint when all votes are one tier", () => {
8
+ const awesomeMid = (EXPERIENCE_STAR_BANDS.Awesome.min +
9
+ EXPERIENCE_STAR_BANDS.Awesome.max) /
10
+ 2;
11
+ expect(averageExperienceStars({ Awesome: 2, Okay: 0, Lame: 0 })).toBeCloseTo(awesomeMid);
12
+ });
13
+ it("blends mixed votes", () => {
14
+ const awesomeMid = (EXPERIENCE_STAR_BANDS.Awesome.min +
15
+ EXPERIENCE_STAR_BANDS.Awesome.max) /
16
+ 2;
17
+ const lameMid = (EXPERIENCE_STAR_BANDS.Lame.min + EXPERIENCE_STAR_BANDS.Lame.max) / 2;
18
+ const a = averageExperienceStars({ Awesome: 1, Okay: 0, Lame: 1 });
19
+ expect(a).toBeCloseTo((awesomeMid + lameMid) / 2);
20
+ });
21
+ });
22
+ //# sourceMappingURL=birthdayExperienceQuality.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"birthdayExperienceQuality.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/birthdayExperienceQuality.test.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,iCAAiC,CAAC;AAEzC,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACtC,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,CACJ,sBAAsB,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CACzD,CAAC,QAAQ,EAAE,CAAC;QACb,MAAM,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,UAAU,GACd,CAAC,qBAAqB,CAAC,OAAO,CAAC,GAAG;YAChC,qBAAqB,CAAC,OAAO,CAAC,GAAG,CAAC;YACpC,CAAC,CAAC;QACJ,MAAM,CACJ,sBAAsB,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CACzD,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAC5B,MAAM,UAAU,GACd,CAAC,qBAAqB,CAAC,OAAO,CAAC,GAAG;YAChC,qBAAqB,CAAC,OAAO,CAAC,GAAG,CAAC;YACpC,CAAC,CAAC;QACJ,MAAM,OAAO,GACX,CAAC,qBAAqB,CAAC,IAAI,CAAC,GAAG,GAAG,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACxE,MAAM,CAAC,GAAG,sBAAsB,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;QACnE,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Shared Quality of Experience math for birthday freebie catalog offers.
3
+ * Vote tiers map to star bands; the displayed score is a weighted average of band midpoints (0–5).
4
+ */
5
+ export type BirthdayExperienceVoteTier = "Awesome" | "Okay" | "Lame";
6
+ export interface BirthdayExperienceVoteCounts {
7
+ Awesome: number;
8
+ Okay: number;
9
+ Lame: number;
10
+ }
11
+ export declare const EXPERIENCE_STAR_BANDS: Record<BirthdayExperienceVoteTier, {
12
+ min: number;
13
+ max: number;
14
+ label: string;
15
+ }>;
16
+ /**
17
+ * Weighted average of midpoint stars for each vote tier. Null when there are no votes.
18
+ */
19
+ export declare function averageExperienceStars(votes: BirthdayExperienceVoteCounts | null | undefined): number | null;
20
+ export declare function formatExperienceStars(value: number): string;
21
+ //# sourceMappingURL=birthdayExperienceQuality.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"birthdayExperienceQuality.d.ts","sourceRoot":"","sources":["../../src/utils/birthdayExperienceQuality.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,0BAA0B,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAErE,MAAM,WAAW,4BAA4B;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,qBAAqB,EAAE,MAAM,CACxC,0BAA0B,EAC1B;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAK5C,CAAC;AAUF;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACrD,MAAM,GAAG,IAAI,CASf;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE3D"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Shared Quality of Experience math for birthday freebie catalog offers.
3
+ * Vote tiers map to star bands; the displayed score is a weighted average of band midpoints (0–5).
4
+ */
5
+ export const EXPERIENCE_STAR_BANDS = {
6
+ Lame: { min: 0, max: 1.999, label: "0–2.0" },
7
+ Okay: { min: 2.0, max: 3.499, label: "2.0–3.5" },
8
+ Awesome: { min: 3.5, max: 5.0, label: "3.5–5.0" },
9
+ };
10
+ const MIDPOINT = {
11
+ Lame: (EXPERIENCE_STAR_BANDS.Lame.min + EXPERIENCE_STAR_BANDS.Lame.max) / 2,
12
+ Okay: (EXPERIENCE_STAR_BANDS.Okay.min + EXPERIENCE_STAR_BANDS.Okay.max) / 2,
13
+ Awesome: (EXPERIENCE_STAR_BANDS.Awesome.min + EXPERIENCE_STAR_BANDS.Awesome.max) /
14
+ 2,
15
+ };
16
+ /**
17
+ * Weighted average of midpoint stars for each vote tier. Null when there are no votes.
18
+ */
19
+ export function averageExperienceStars(votes) {
20
+ if (!votes)
21
+ return null;
22
+ const n = votes.Awesome + votes.Okay + votes.Lame;
23
+ if (n === 0)
24
+ return null;
25
+ const sum = votes.Awesome * MIDPOINT.Awesome +
26
+ votes.Okay * MIDPOINT.Okay +
27
+ votes.Lame * MIDPOINT.Lame;
28
+ return sum / n;
29
+ }
30
+ export function formatExperienceStars(value) {
31
+ return value.toFixed(1);
32
+ }
33
+ //# sourceMappingURL=birthdayExperienceQuality.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"birthdayExperienceQuality.js","sourceRoot":"","sources":["../../src/utils/birthdayExperienceQuality.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAUH,MAAM,CAAC,MAAM,qBAAqB,GAG9B;IACF,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE;IAC5C,IAAI,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE;IAChD,OAAO,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE;CAClD,CAAC;AAEF,MAAM,QAAQ,GAA+C;IAC3D,IAAI,EAAE,CAAC,qBAAqB,CAAC,IAAI,CAAC,GAAG,GAAG,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAC3E,IAAI,EAAE,CAAC,qBAAqB,CAAC,IAAI,CAAC,GAAG,GAAG,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAC3E,OAAO,EACL,CAAC,qBAAqB,CAAC,OAAO,CAAC,GAAG,GAAG,qBAAqB,CAAC,OAAO,CAAC,GAAG,CAAC;QACvE,CAAC;CACJ,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,sBAAsB,CACpC,KAAsD;IAEtD,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,CAAC,GAAG,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IAClD,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACzB,MAAM,GAAG,GACP,KAAK,CAAC,OAAO,GAAG,QAAQ,CAAC,OAAO;QAChC,KAAK,CAAC,IAAI,GAAG,QAAQ,CAAC,IAAI;QAC1B,KAAK,CAAC,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;IAC7B,OAAO,GAAG,GAAG,CAAC,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,KAAa;IACjD,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAC1B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bash-app/bash-common",
3
- "version": "30.176.0",
3
+ "version": "30.178.0",
4
4
  "description": "Common data and scripts to use on the frontend and backend",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1708,6 +1708,7 @@ model User {
1708
1708
  birthdayRewardFavorites BirthdayRewardFavorite[]
1709
1709
  birthdayDealSuggestions BirthdayDealSuggestion[]
1710
1710
  rewardReports RewardReport[]
1711
+ birthdayFreebieVotes BirthdayFreebieVote[]
1711
1712
  rewardReportsReviewed RewardReport[] @relation("RewardReportReviewedBy")
1712
1713
  nationalChainReports NationalChainReport[]
1713
1714
  nationalChainReportsReviewed NationalChainReport[] @relation("NationalChainReportReviewedBy")
@@ -6056,6 +6057,8 @@ model BirthdayFreebieCatalog {
6056
6057
  partner BirthdayOfferPartner? @relation(fields: [partnerId], references: [id], onDelete: SetNull)
6057
6058
  suggestion BirthdayDealSuggestion? @relation(fields: [suggestionId], references: [id], onDelete: SetNull)
6058
6059
  rewardReports RewardReport[]
6060
+ /// User quality votes (Awesome / Okay / Lame); one row per user per catalog entry.
6061
+ votes BirthdayFreebieVote[]
6059
6062
 
6060
6063
  @@index([stateSlug, citySlug])
6061
6064
  @@index([isActive])
@@ -6063,6 +6066,23 @@ model BirthdayFreebieCatalog {
6063
6066
  @@index([sector])
6064
6067
  }
6065
6068
 
6069
+ /// User quality vote on a catalog entry (Awesome / Okay / Lame). Drives aggregate counts and optional qualityTier updates.
6070
+ model BirthdayFreebieVote {
6071
+ id String @id @default(cuid())
6072
+ catalogId String
6073
+ userId String
6074
+ tier BirthdayOfferQualityTier
6075
+ createdAt DateTime @default(now())
6076
+ updatedAt DateTime @updatedAt
6077
+
6078
+ catalog BirthdayFreebieCatalog @relation(fields: [catalogId], references: [id], onDelete: Cascade)
6079
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
6080
+
6081
+ @@unique([catalogId, userId])
6082
+ @@index([catalogId])
6083
+ @@index([userId])
6084
+ }
6085
+
6066
6086
  // Per-city "free things to do" links (tourism calendar, Eventbrite, Meetup, alt-weekly)
6067
6087
  enum BirthdayCityResourceType {
6068
6088
  TourismCalendar
package/src/index.ts CHANGED
@@ -46,6 +46,7 @@ export * from "./utils/entityUtils.js";
46
46
  export * from "./utils/generalDateTimeUtils.js";
47
47
  export * from "./utils/luxonUtils.js";
48
48
  export * from "./utils/mathUtils.js";
49
+ export * from "./utils/birthdayExperienceQuality.js";
49
50
  export * from "./utils/service/apiServiceBookingApiUtils.js";
50
51
  export * from "./utils/service/frontendServiceBookingUtils.js";
51
52
  export * from "./utils/service/serviceBookingStatusUtils.js";
@@ -0,0 +1,34 @@
1
+ import {
2
+ averageExperienceStars,
3
+ EXPERIENCE_STAR_BANDS,
4
+ } from "../birthdayExperienceQuality.js";
5
+
6
+ describe("averageExperienceStars", () => {
7
+ it("returns null when there are no votes", () => {
8
+ expect(
9
+ averageExperienceStars({ Awesome: 0, Okay: 0, Lame: 0 })
10
+ ).toBeNull();
11
+ expect(averageExperienceStars(undefined)).toBeNull();
12
+ });
13
+
14
+ it("returns midpoint when all votes are one tier", () => {
15
+ const awesomeMid =
16
+ (EXPERIENCE_STAR_BANDS.Awesome.min +
17
+ EXPERIENCE_STAR_BANDS.Awesome.max) /
18
+ 2;
19
+ expect(
20
+ averageExperienceStars({ Awesome: 2, Okay: 0, Lame: 0 })
21
+ ).toBeCloseTo(awesomeMid);
22
+ });
23
+
24
+ it("blends mixed votes", () => {
25
+ const awesomeMid =
26
+ (EXPERIENCE_STAR_BANDS.Awesome.min +
27
+ EXPERIENCE_STAR_BANDS.Awesome.max) /
28
+ 2;
29
+ const lameMid =
30
+ (EXPERIENCE_STAR_BANDS.Lame.min + EXPERIENCE_STAR_BANDS.Lame.max) / 2;
31
+ const a = averageExperienceStars({ Awesome: 1, Okay: 0, Lame: 1 });
32
+ expect(a).toBeCloseTo((awesomeMid + lameMid) / 2);
33
+ });
34
+ });
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Shared Quality of Experience math for birthday freebie catalog offers.
3
+ * Vote tiers map to star bands; the displayed score is a weighted average of band midpoints (0–5).
4
+ */
5
+
6
+ export type BirthdayExperienceVoteTier = "Awesome" | "Okay" | "Lame";
7
+
8
+ export interface BirthdayExperienceVoteCounts {
9
+ Awesome: number;
10
+ Okay: number;
11
+ Lame: number;
12
+ }
13
+
14
+ export const EXPERIENCE_STAR_BANDS: Record<
15
+ BirthdayExperienceVoteTier,
16
+ { min: number; max: number; label: string }
17
+ > = {
18
+ Lame: { min: 0, max: 1.999, label: "0–2.0" },
19
+ Okay: { min: 2.0, max: 3.499, label: "2.0–3.5" },
20
+ Awesome: { min: 3.5, max: 5.0, label: "3.5–5.0" },
21
+ };
22
+
23
+ const MIDPOINT: Record<BirthdayExperienceVoteTier, number> = {
24
+ Lame: (EXPERIENCE_STAR_BANDS.Lame.min + EXPERIENCE_STAR_BANDS.Lame.max) / 2,
25
+ Okay: (EXPERIENCE_STAR_BANDS.Okay.min + EXPERIENCE_STAR_BANDS.Okay.max) / 2,
26
+ Awesome:
27
+ (EXPERIENCE_STAR_BANDS.Awesome.min + EXPERIENCE_STAR_BANDS.Awesome.max) /
28
+ 2,
29
+ };
30
+
31
+ /**
32
+ * Weighted average of midpoint stars for each vote tier. Null when there are no votes.
33
+ */
34
+ export function averageExperienceStars(
35
+ votes: BirthdayExperienceVoteCounts | null | undefined
36
+ ): number | null {
37
+ if (!votes) return null;
38
+ const n = votes.Awesome + votes.Okay + votes.Lame;
39
+ if (n === 0) return null;
40
+ const sum =
41
+ votes.Awesome * MIDPOINT.Awesome +
42
+ votes.Okay * MIDPOINT.Okay +
43
+ votes.Lame * MIDPOINT.Lame;
44
+ return sum / n;
45
+ }
46
+
47
+ export function formatExperienceStars(value: number): string {
48
+ return value.toFixed(1);
49
+ }