Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | 2x 2x 2x 2x 2x 105x 105x 105x 2x 95x 95x 95x 88x 88x 7x 2x 2x 5x 5x 5x 95x 95x 102x 102x 13x 89x 81x 8x 5x 95x 95x 95x 10x 85x 2x 95x 95x 95x 95x 2x 2x 93x | import * as murmurhash from "murmurhash";
import { Feature, BucketKey, BucketValue, Context, AttributeValue } from "@featurevisor/types";
import { Logger } from "./logger";
/**
* Generic hashing
*/
const HASH_SEED = 1;
const MAX_HASH_VALUE = Math.pow(2, 32);
export const MAX_BUCKETED_NUMBER = 100000; // 100% * 1000 to include three decimal places in the same integer value
export function getBucketedNumber(bucketKey: string): number {
const hashValue = murmurhash.v3(bucketKey, HASH_SEED);
const ratio = hashValue / MAX_HASH_VALUE;
return Math.floor(ratio * MAX_BUCKETED_NUMBER);
}
/**
* Feature specific bucketing
*/
export type ConfigureBucketKey = (
feature: Feature,
context: Context,
bucketKey: BucketKey,
) => BucketKey;
export interface BucketKeyOptions {
feature: Feature;
context: Context;
logger: Logger;
bucketKeySeparator?: string;
configureBucketKey?: ConfigureBucketKey;
}
export function getBucketKey(options: BucketKeyOptions): BucketKey {
const { feature, context, logger, bucketKeySeparator = ".", configureBucketKey } = options;
const featureKey = feature.key;
let type;
let attributeKeys;
if (typeof feature.bucketBy === "string") {
type = "plain";
attributeKeys = [feature.bucketBy];
} else if (Array.isArray(feature.bucketBy)) {
type = "and";
attributeKeys = feature.bucketBy;
} else if (typeof feature.bucketBy === "object" && Array.isArray(feature.bucketBy.or)) {
type = "or";
attributeKeys = feature.bucketBy.or;
} else E{
logger.error("invalid bucketBy", { featureKey, bucketBy: feature.bucketBy });
throw new Error("invalid bucketBy");
}
const bucketKey: AttributeValue[] = [];
attributeKeys.forEach((attributeKey) => {
const attributeValue = context[attributeKey];
if (typeof attributeValue === "undefined") {
return;
}
if (type === "plain" || type === "and") {
bucketKey.push(attributeValue);
} else {
// or
if (bucketKey.length === 0) {
bucketKey.push(attributeValue);
}
}
});
bucketKey.push(featureKey);
const result = bucketKey.join(bucketKeySeparator);
if (configureBucketKey) {
return configureBucketKey(feature, context, result);
}
return result;
}
export interface Bucket {
bucketKey: BucketKey;
bucketValue: BucketValue;
}
export type ConfigureBucketValue = (
feature: Feature,
context: Context,
bucketValue: BucketValue,
) => BucketValue;
export interface BucketValueOptions {
// common with BucketKeyOptions
feature: Feature;
context: Context;
logger: Logger;
bucketKeySeparator?: string;
configureBucketKey?: ConfigureBucketKey;
// specific to BucketValueOptions
configureBucketValue?: ConfigureBucketValue;
}
export function getBucket(options: BucketValueOptions): Bucket {
const { feature, context, logger, bucketKeySeparator, configureBucketKey, configureBucketValue } =
options;
const bucketKey = getBucketKey({
feature,
context,
logger,
bucketKeySeparator,
configureBucketKey,
});
const value = getBucketedNumber(bucketKey);
if (configureBucketValue) {
const configuredValue = configureBucketValue(feature, context, value);
return {
bucketKey,
bucketValue: configuredValue,
};
}
return {
bucketKey,
bucketValue: value,
};
}
|