diff --git a/package-lock.json b/package-lock.json
index 06ea10edb..f58bbfda3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4830,6 +4830,14 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/deeks": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/deeks/-/deeks-3.0.2.tgz",
+ "integrity": "sha512-c6OmjIygIB/avwXwEQOiODS+nw6fEX4cvOdDMqdL7dt3dicV/xykAJ9AeVc/8/JTVQDuacjRc9KCMmXafL1Y4A==",
+ "engines": {
+ "node": ">= 16"
+ }
+ },
"node_modules/deep-eql": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz",
@@ -4958,6 +4966,14 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/doc-path": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/doc-path/-/doc-path-4.0.2.tgz",
+ "integrity": "sha512-OqZEk7EM1aP3JpO+mq0pv1msEJWrzZVXu4q3YjEYJKc+Wt3/chac4KJdaGueK5IGemOwfptrLctG9I8xkb59qQ==",
+ "engines": {
+ "node": ">=16"
+ }
+ },
"node_modules/doctrine": {
"version": "3.0.0",
"dev": true,
@@ -7245,6 +7261,18 @@
"node": ">=4"
}
},
+ "node_modules/json-2-csv": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-2-csv/-/json-2-csv-5.0.1.tgz",
+ "integrity": "sha512-rP9ChyMskS0angbvFdQ43SwEe72mEvqcY1/V2OeukQWxtlreUuZWhMlTdWjtd4L6kJxq+HPFTI06yqLvZiEVIA==",
+ "dependencies": {
+ "deeks": "3.0.2",
+ "doc-path": "4.0.2"
+ },
+ "engines": {
+ "node": ">= 16"
+ }
+ },
"node_modules/json-bigint": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
@@ -12138,6 +12166,7 @@
"google-auth-library": "9.2.0",
"hls.js": "^1.4.12",
"idb": "^7.1.1",
+ "json-2-csv": "5.0.1",
"svelte-local-storage-store": "^0.6.4",
"throttle-debounce": "^5.0.0"
},
@@ -13635,6 +13664,7 @@
"google-auth-library": "9.2.0",
"hls.js": "^1.4.12",
"idb": "^7.1.1",
+ "json-2-csv": "5.0.1",
"postcss": "^8.4.31",
"svelte": "^3.59.2",
"svelte-check": "^3.6.2",
@@ -15878,6 +15908,11 @@
"version": "10.4.3",
"dev": true
},
+ "deeks": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/deeks/-/deeks-3.0.2.tgz",
+ "integrity": "sha512-c6OmjIygIB/avwXwEQOiODS+nw6fEX4cvOdDMqdL7dt3dicV/xykAJ9AeVc/8/JTVQDuacjRc9KCMmXafL1Y4A=="
+ },
"deep-eql": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz",
@@ -15972,6 +16007,11 @@
"version": "1.1.3",
"dev": true
},
+ "doc-path": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/doc-path/-/doc-path-4.0.2.tgz",
+ "integrity": "sha512-OqZEk7EM1aP3JpO+mq0pv1msEJWrzZVXu4q3YjEYJKc+Wt3/chac4KJdaGueK5IGemOwfptrLctG9I8xkb59qQ=="
+ },
"doctrine": {
"version": "3.0.0",
"dev": true,
@@ -17567,6 +17607,15 @@
"optional": true,
"peer": true
},
+ "json-2-csv": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-2-csv/-/json-2-csv-5.0.1.tgz",
+ "integrity": "sha512-rP9ChyMskS0angbvFdQ43SwEe72mEvqcY1/V2OeukQWxtlreUuZWhMlTdWjtd4L6kJxq+HPFTI06yqLvZiEVIA==",
+ "requires": {
+ "deeks": "3.0.2",
+ "doc-path": "4.0.2"
+ }
+ },
"json-bigint": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
diff --git a/packages/experiments/package.json b/packages/experiments/package.json
index 4d2d2ae3c..0d0eeba4c 100644
--- a/packages/experiments/package.json
+++ b/packages/experiments/package.json
@@ -20,6 +20,7 @@
"google-auth-library": "9.2.0",
"hls.js": "^1.4.12",
"idb": "^7.1.1",
+ "json-2-csv": "5.0.1",
"firebase": "10.6.0",
"svelte-local-storage-store": "^0.6.4",
"throttle-debounce": "^5.0.0"
diff --git a/packages/experiments/src/routes/(feed)/up-down/results/ResultCard.svelte b/packages/experiments/src/routes/(feed)/up-down/results/ResultCard.svelte
index c9fd8f2ce..9cf1fc4d7 100644
--- a/packages/experiments/src/routes/(feed)/up-down/results/ResultCard.svelte
+++ b/packages/experiments/src/routes/(feed)/up-down/results/ResultCard.svelte
@@ -2,7 +2,6 @@
import Avatar from '$components/avatar/Avatar.svelte'
import Icon from '$components/icon/Icon.svelte'
import type { VoteRecordWithId } from '$components/up-down/UpDownVote.svelte'
-import type { VoteRecord } from '$lib/db/db.types'
import { getThumbnailUrl } from '$lib/utils/cloudflare'
import { getMsLeftForResult, getVoteEndTime } from '$lib/utils/countdown'
import { pluralize } from '$lib/utils/pluralize'
diff --git a/packages/experiments/src/routes/dev/+page.svelte b/packages/experiments/src/routes/dev/+page.svelte
index 4635a6b7a..bdedef593 100644
--- a/packages/experiments/src/routes/dev/+page.svelte
+++ b/packages/experiments/src/routes/dev/+page.svelte
@@ -1,86 +1,17 @@
+
+{#if !allowed}
+
+
Oops! Seems like you are lost
+
+
+{:else}
+
+
+
Current config:
+ {#if loading}
+
Loading ...
+ {:else if currentParams}
+ {#each Object.keys(currentParams).sort() as key}
+
{key} : {JSON.stringify(
+ currentParams[key],
+ null,
+ 4,
+ )}
+ {/each}
+ {:else}
+
No config found
+ {/if}
+
+
Create new config:
+
+
+
Score change by like:
+
Formula: (score) ± (like-change)
+
+
+
+
+
Change in score:
+
+ Liked: ({newParams.liked.yes}) = {newParams.liked.yes}
+
+
+ Not liked: ({newParams.liked.no}) = {newParams.liked.no}
+
+
+
+
Score change by dislike:
+
Formula: (score) ± (dislik-change)
+
+
+
+
+
Change in score:
+
+ Disliked: ({newParams.disliked.yes}) = {newParams.disliked.yes}
+
+
+ Not disliked: ({newParams.disliked.no}) = {newParams.disliked.no}
+
+
+
+
Score change by share:
+
Formula: (score) ± (share-change)
+
+
+
+
+
Change in score:
+
+ Shared: ({newParams.shared.yes}) = {newParams.shared.yes}
+
+
+ Not shared: ({newParams.shared.no}) = {newParams.shared.no}
+
+
+
+
Score change by watching a percentage of video:
+
+ Formula: (score) + (percentage of video watched)/(divisor) *
+ (multiplier)
+
+
+
+
+
+
Change in score:
+
+ Watched percentage is 15: quotient(15 / {newParams.watched.divisor}) * {newParams
+ .watched.multiplier} = {Math.floor(15 / newParams.watched.divisor) *
+ newParams.watched.multiplier}
+
+
+ Watched percentage is 29: quotient(29 / {newParams.watched.divisor}) * {newParams
+ .watched.multiplier} = {Math.floor(29 / newParams.watched.divisor) *
+ newParams.watched.multiplier}
+
+
+ Watched percentage is 100: quotient(100 / {newParams.watched.divisor})
+ * {newParams.watched.multiplier} = {Math.floor(
+ 100 / newParams.watched.divisor,
+ ) * newParams.watched.multiplier}
+
+
+
+
+
Score change by watching a minimum-percentage of video:
+
+ Formula: (score) ± (threshold change)
+
+
+
+
+
+
+
Change in score:
+
+ Watched percentage is 20: 20% >= {newParams.threshold.minPercentage} ?
+ {newParams.threshold.yes}
+ : {newParams.threshold.no} = {20 >= newParams.threshold.minPercentage
+ ? newParams.threshold.yes
+ : newParams.threshold.no}
+
+
+ Watched percentage is 50: 50% >= {newParams.threshold.minPercentage} ?
+ {newParams.threshold.yes}
+ : {newParams.threshold.no} = {50 >= newParams.threshold.minPercentage
+ ? newParams.threshold.yes
+ : newParams.threshold.no}
+
+
+
+
+
Score change by watching the video completely:
+
Formula: (score) ± (watch-change)
+
+
+
+
+
Change in score:
+
+ Fully watched: ({newParams.fullyWatched.yes}) = {newParams
+ .fullyWatched.yes}
+
+
+ Not fully watched: ({newParams.fullyWatched.no}) = {newParams
+ .fullyWatched.no}
+
+
+
+
+ Score change when a minute has passed (counted from upload time):
+
+
Formula: (score) ± (minute-change)
+
+
+
+
Change in score:
+
+ When a minute passes: ({newParams.minutePassed}) = {newParams.minutePassed}
+
+
+
+
Score change on the basis of views/minute:
+
+ Formula: (score) ± ( (v/m)/(divisor) > (threshold) ? (greaterThan) :
+ (lessThan) )
+
+
+
+
+
+
+
+
+
+
Change in score:
+
Views Per Minute is 0: 0
+
+ Views Per Minute is 5: ( 1 / {newParams.viewsPerMinute.divisor} > {newParams
+ .viewsPerMinute.threshold} ? ( {newParams.viewsPerMinute.yes} : {newParams
+ .viewsPerMinute.no} ) ) = {Number(
+ 1 / newParams.viewsPerMinute.divisor >
+ newParams.viewsPerMinute.threshold
+ ? newParams.viewsPerMinute.yes
+ : newParams.viewsPerMinute.no,
+ )}
+
+
+ Views Per Minute is 11: ( 11 / {newParams.viewsPerMinute.divisor} > {newParams
+ .viewsPerMinute.threshold} ? ( {newParams.viewsPerMinute.yes} : {newParams
+ .viewsPerMinute.no} ) ) = {11 / newParams.viewsPerMinute.divisor >
+ newParams.viewsPerMinute.threshold
+ ? newParams.viewsPerMinute.yes
+ : newParams.viewsPerMinute.no}
+
+
+ Views Per Minute is 20: ( 20 / {newParams.viewsPerMinute.divisor} > {newParams
+ .viewsPerMinute.threshold} ? ( {newParams.viewsPerMinute.yes} : {newParams
+ .viewsPerMinute.no} ) ) = {20 / newParams.viewsPerMinute.divisor >
+ newParams.viewsPerMinute.threshold
+ ? newParams.viewsPerMinute.yes
+ : newParams.viewsPerMinute.no}
+
+
+
+
+
+
+
+{/if}
+
+
diff --git a/packages/experiments/src/routes/dev/export/+page.svelte b/packages/experiments/src/routes/dev/export/+page.svelte
new file mode 100644
index 000000000..8bd35980d
--- /dev/null
+++ b/packages/experiments/src/routes/dev/export/+page.svelte
@@ -0,0 +1,166 @@
+
+
+{#if !allowed}
+
+
Oops! Seems like you are lost
+
+
+{:else}
+
+
+
Current config:
+ {#if loading}
+
Loading ...
+ {:else}
+
+
+
Get score update hisotry of a video:
+
+
+
+
+
+ {#if videoError}
+
+ {videoError}
+
+ {/if}
+
+
+
+
Get all actions & journey of a user:
+
+
+
+
+
+ {#if userError}
+
+ {userError}
+
+ {/if}
+
+
+ {/if}
+
+
+{/if}
+
+