@naturalcycles/abba 2.0.1 → 2.1.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/abba.d.ts +12 -9
- package/dist/abba.js +98 -101
- package/dist/dao/bucket.dao.d.ts +3 -3
- package/dist/dao/bucket.dao.js +20 -15
- package/dist/dao/experiment.dao.d.ts +8 -4
- package/dist/dao/experiment.dao.js +39 -28
- package/dist/dao/userAssignment.dao.d.ts +5 -1
- package/dist/dao/userAssignment.dao.js +21 -4
- package/dist/migrations/init.sql +1 -0
- package/dist/types.d.ts +16 -11
- package/dist/util.d.ts +8 -8
- package/dist/util.js +18 -18
- package/package.json +3 -2
- package/src/abba.ts +122 -105
- package/src/dao/bucket.dao.ts +9 -5
- package/src/dao/experiment.dao.ts +26 -9
- package/src/dao/userAssignment.dao.ts +29 -3
- package/src/migrations/init.sql +1 -0
- package/src/types.ts +16 -12
- package/src/util.ts +16 -18
package/src/types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CommonDB } from '@naturalcycles/db-lib'
|
|
2
|
-
import type { AnyObject, BaseDBEntity, IsoDate
|
|
2
|
+
import type { AnyObject, BaseDBEntity, IsoDate } from '@naturalcycles/js-lib'
|
|
3
3
|
|
|
4
4
|
export interface AbbaConfig {
|
|
5
5
|
db: CommonDB
|
|
@@ -31,6 +31,10 @@ export type BaseExperiment = BaseDBEntity & {
|
|
|
31
31
|
* Date range end for the experiment assignments
|
|
32
32
|
*/
|
|
33
33
|
endDateExcl: IsoDate
|
|
34
|
+
/**
|
|
35
|
+
* Whether the experiment is flagged as deleted. This acts as a soft delete only.
|
|
36
|
+
*/
|
|
37
|
+
deleted: boolean
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
export type Experiment = BaseExperiment & {
|
|
@@ -39,8 +43,8 @@ export type Experiment = BaseExperiment & {
|
|
|
39
43
|
data: AnyObject | null
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
export type ExperimentWithBuckets =
|
|
43
|
-
buckets:
|
|
46
|
+
export type ExperimentWithBuckets = Experiment & {
|
|
47
|
+
buckets: Bucket[]
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
export type BaseBucket = BaseDBEntity & {
|
|
@@ -59,15 +63,11 @@ export type UserAssignment = BaseDBEntity & {
|
|
|
59
63
|
bucketId: string | null
|
|
60
64
|
}
|
|
61
65
|
|
|
62
|
-
export
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
bucketData: AnyObject | null
|
|
68
|
-
})
|
|
69
|
-
| null
|
|
70
|
-
experiment: Saved<Experiment>
|
|
66
|
+
export type DecoratedUserAssignment = UserAssignment & {
|
|
67
|
+
experimentKey: Experiment['key']
|
|
68
|
+
experimentData: Experiment['data']
|
|
69
|
+
bucketKey: Bucket['key'] | null
|
|
70
|
+
bucketData: Bucket['data']
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
export type SegmentationData = AnyObject
|
|
@@ -127,3 +127,7 @@ export interface BucketAssignmentStatistics {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
export type ExclusionSet = Set<string>
|
|
130
|
+
|
|
131
|
+
export interface UserExperiment extends ExperimentWithBuckets {
|
|
132
|
+
userAssignment?: DecoratedUserAssignment
|
|
133
|
+
}
|
package/src/util.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
SegmentationRule,
|
|
11
11
|
SegmentationRuleFn,
|
|
12
12
|
UserAssignment,
|
|
13
|
+
UserExperiment,
|
|
13
14
|
} from './types.js'
|
|
14
15
|
import { AssignmentStatus, SegmentationRuleOperator } from './types.js'
|
|
15
16
|
|
|
@@ -17,11 +18,11 @@ import { AssignmentStatus, SegmentationRuleOperator } from './types.js'
|
|
|
17
18
|
* Generate a new assignment for a given user.
|
|
18
19
|
* Doesn't save it.
|
|
19
20
|
*/
|
|
20
|
-
export
|
|
21
|
+
export function generateUserAssignmentData(
|
|
21
22
|
experiment: ExperimentWithBuckets,
|
|
22
23
|
userId: string,
|
|
23
24
|
segmentationData: SegmentationData,
|
|
24
|
-
): UserAssignment | null
|
|
25
|
+
): Unsaved<UserAssignment> | null {
|
|
25
26
|
const segmentationMatch = validateSegmentationRules(experiment.rules, segmentationData)
|
|
26
27
|
if (!segmentationMatch) return null
|
|
27
28
|
|
|
@@ -31,7 +32,7 @@ export const generateUserAssignmentData = (
|
|
|
31
32
|
userId,
|
|
32
33
|
experimentId: experiment.id,
|
|
33
34
|
bucketId: bucket?.id || null,
|
|
34
|
-
}
|
|
35
|
+
}
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
class RandomService {
|
|
@@ -48,7 +49,7 @@ export const randomService = new RandomService()
|
|
|
48
49
|
/**
|
|
49
50
|
* Determines a users assignment for this experiment. Returns null if they are not considered to be in the sampling group
|
|
50
51
|
*/
|
|
51
|
-
export
|
|
52
|
+
export function determineAssignment(sampling: number, buckets: Bucket[]): Bucket | null {
|
|
52
53
|
// Should this person be considered for the experiment?
|
|
53
54
|
if (randomService.rollDie() > sampling) {
|
|
54
55
|
return null
|
|
@@ -61,7 +62,7 @@ export const determineAssignment = (sampling: number, buckets: Bucket[]): Bucket
|
|
|
61
62
|
/**
|
|
62
63
|
* Determines which bucket a user assignment will recieve
|
|
63
64
|
*/
|
|
64
|
-
export
|
|
65
|
+
export function determineBucket(buckets: Bucket[]): Bucket {
|
|
65
66
|
const bucketRoll = randomService.rollDie()
|
|
66
67
|
let range: [number, number] | undefined
|
|
67
68
|
const bucket = buckets.find(b => {
|
|
@@ -86,7 +87,7 @@ export const determineBucket = (buckets: Bucket[]): Bucket => {
|
|
|
86
87
|
/**
|
|
87
88
|
* Validate the total ratio of the buckets equals 100
|
|
88
89
|
*/
|
|
89
|
-
export
|
|
90
|
+
export function validateTotalBucketRatio(buckets: Unsaved<Bucket>[]): void {
|
|
90
91
|
const bucketSum = buckets.reduce((sum, current) => sum + current.ratio, 0)
|
|
91
92
|
if (bucketSum !== 100) {
|
|
92
93
|
throw new Error('Total bucket ratio must be 100 before you can activate an experiment')
|
|
@@ -100,10 +101,10 @@ export const validateTotalBucketRatio = (buckets: Unsaved<Bucket>[]): void => {
|
|
|
100
101
|
* @param segmentationData
|
|
101
102
|
* @returns
|
|
102
103
|
*/
|
|
103
|
-
export
|
|
104
|
+
export function validateSegmentationRules(
|
|
104
105
|
rules: SegmentationRule[],
|
|
105
106
|
segmentationData: SegmentationData,
|
|
106
|
-
): boolean
|
|
107
|
+
): boolean {
|
|
107
108
|
for (const rule of rules) {
|
|
108
109
|
const { key, value, operator } = rule
|
|
109
110
|
if (!segmentationRuleMap[operator](segmentationData[key], value)) return false
|
|
@@ -144,10 +145,10 @@ export const segmentationRuleMap: Record<SegmentationRuleOperator, SegmentationR
|
|
|
144
145
|
/**
|
|
145
146
|
* Returns true if an experiment is able to generate new assignments based on status and start/end dates
|
|
146
147
|
*/
|
|
147
|
-
export
|
|
148
|
+
export function canGenerateNewAssignments(
|
|
148
149
|
experiment: Experiment,
|
|
149
150
|
exclusionSet: ExclusionSet,
|
|
150
|
-
): boolean
|
|
151
|
+
): boolean {
|
|
151
152
|
return (
|
|
152
153
|
!exclusionSet.has(experiment.id) &&
|
|
153
154
|
experiment.status === AssignmentStatus.Active &&
|
|
@@ -159,18 +160,15 @@ export const canGenerateNewAssignments = (
|
|
|
159
160
|
* Returns an object that includes keys of all experimentIds a user should not be assigned to
|
|
160
161
|
* based on a combination of existing assignments and mutual exclusion configuration
|
|
161
162
|
*/
|
|
162
|
-
export
|
|
163
|
-
experiments: Experiment[],
|
|
164
|
-
existingAssignments: UserAssignment[],
|
|
165
|
-
): ExclusionSet => {
|
|
163
|
+
export function getUserExclusionSet(experiments: UserExperiment[]): ExclusionSet {
|
|
166
164
|
const exclusionSet: ExclusionSet = new Set()
|
|
167
|
-
|
|
165
|
+
experiments.forEach(experiment => {
|
|
166
|
+
const { userAssignment } = experiment
|
|
168
167
|
// Users who are excluded from an experiment due to sampling
|
|
169
168
|
// should not prevent potential assignment to other mutually exclusive experiments
|
|
170
|
-
if (
|
|
169
|
+
if (!userAssignment || userAssignment?.bucketId === null) return
|
|
171
170
|
|
|
172
|
-
|
|
173
|
-
experiment?.exclusions.forEach(experimentId => exclusionSet.add(experimentId))
|
|
171
|
+
experiment.exclusions.forEach(experimentId => exclusionSet.add(experimentId))
|
|
174
172
|
})
|
|
175
173
|
return exclusionSet
|
|
176
174
|
}
|