From 2152e5286af54ef1e8f3dc0a000b294f211b0df3 Mon Sep 17 00:00:00 2001 From: Ian Philips Date: Tue, 19 Jul 2022 16:29:41 -0600 Subject: [PATCH] Score & sort by unique bettors in last 3 days --- common/contract.ts | 1 + functions/src/index.ts | 1 + functions/src/score-contracts.ts | 41 +++++++++++++++++++++++++ web/components/contract-search.tsx | 5 +-- web/hooks/use-sort-and-query-params.tsx | 5 +-- web/pages/contract-search-firestore.tsx | 8 ++--- web/pages/home.tsx | 4 +-- 7 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 functions/src/score-contracts.ts diff --git a/common/contract.ts b/common/contract.ts index 5ddcf0b8..b1242ab9 100644 --- a/common/contract.ts +++ b/common/contract.ts @@ -48,6 +48,7 @@ export type Contract = { groupSlugs?: string[] uniqueBettorIds?: string[] uniqueBettorCount?: number + popularityScore?: number } & T export type BinaryContract = Contract & Binary diff --git a/functions/src/index.ts b/functions/src/index.ts index 3055f8dc..df311886 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -22,6 +22,7 @@ export * from './on-update-user' export * from './on-create-comment-on-group' export * from './on-create-txn' export * from './on-delete-group' +export * from './score-contracts' // v2 export * from './health' diff --git a/functions/src/score-contracts.ts b/functions/src/score-contracts.ts new file mode 100644 index 00000000..ab6512d0 --- /dev/null +++ b/functions/src/score-contracts.ts @@ -0,0 +1,41 @@ +import * as functions from 'firebase-functions' +import * as admin from 'firebase-admin' +import { Bet } from 'common/bet' +import { uniq } from 'lodash' +import { Contract } from 'common/contract' + +export const scoreContracts = functions.pubsub + .schedule('every 1 hours') + .onRun(async () => { + await scoreContractsInternal() + }) +const firestore = admin.firestore() + +async function scoreContractsInternal() { + const now = Date.now() + const lastHour = now - 3600000 + const last3Days = now - 2592000000 + + const contracts = await firestore + .collection('contracts') + .where('lastUpdatedTime', '>', lastHour) + .get() + + for (const contractSnap of contracts.docs) { + const contract = contractSnap.data() as Contract + const contractId = contractSnap.id + const bets = await firestore + .collection(`contracts/${contractId}/bets`) + .where('createdTime', '>', last3Days) + .get() + const bettors = bets.docs + .map((doc) => doc.data() as Bet) + .map((bet) => bet.userId) + const score = uniq(bettors).length + if (contract.popularityScore !== score) + await firestore + .collection('contracts') + .doc(contractId) + .update({ popularityScore: score }) + } +} diff --git a/web/components/contract-search.tsx b/web/components/contract-search.tsx index 013208d8..dc97f482 100644 --- a/web/components/contract-search.tsx +++ b/web/components/contract-search.tsx @@ -39,13 +39,14 @@ const indexPrefix = ENV === 'DEV' ? 'dev-' : '' const sortIndexes = [ { label: 'Newest', value: indexPrefix + 'contracts-newest' }, { label: 'Oldest', value: indexPrefix + 'contracts-oldest' }, - { label: 'Most popular', value: indexPrefix + 'contracts-most-popular' }, + { label: 'Most popular', value: indexPrefix + 'contracts-score' }, { label: 'Most traded', value: indexPrefix + 'contracts-most-traded' }, { label: '24h volume', value: indexPrefix + 'contracts-24-hour-vol' }, { label: 'Last updated', value: indexPrefix + 'contracts-last-updated' }, { label: 'Close date', value: indexPrefix + 'contracts-close-date' }, { label: 'Resolve date', value: indexPrefix + 'contracts-resolve-date' }, ] +export const DEFAULT_SORT = 'score' type filter = 'personal' | 'open' | 'closed' | 'resolved' | 'all' const filterOptions: { [label: string]: filter } = { @@ -95,7 +96,7 @@ export function ContractSearch(props: { .map(({ value }) => value) .includes(`${indexPrefix}contracts-${initialSort ?? ''}`) ? initialSort - : querySortOptions?.defaultSort ?? 'most-popular' + : querySortOptions?.defaultSort ?? DEFAULT_SORT const [filter, setFilter] = useState( querySortOptions?.defaultFilter ?? 'open' diff --git a/web/hooks/use-sort-and-query-params.tsx b/web/hooks/use-sort-and-query-params.tsx index 5c9a247f..9023dc1a 100644 --- a/web/hooks/use-sort-and-query-params.tsx +++ b/web/hooks/use-sort-and-query-params.tsx @@ -3,6 +3,7 @@ import { useRouter } from 'next/router' import { useEffect, useMemo, useState } from 'react' import { useSearchBox } from 'react-instantsearch-hooks-web' import { track } from 'web/lib/service/analytics' +import { DEFAULT_SORT } from 'web/components/contract-search' const MARKETS_SORT = 'markets_sort' @@ -10,11 +11,11 @@ export type Sort = | 'newest' | 'oldest' | 'most-traded' - | 'most-popular' | '24-hour-vol' | 'close-date' | 'resolve-date' | 'last-updated' + | 'score' export function getSavedSort() { // TODO: this obviously doesn't work with SSR, common sense would suggest @@ -31,7 +32,7 @@ export function useInitialQueryAndSort(options?: { shouldLoadFromStorage?: boolean }) { const { defaultSort, shouldLoadFromStorage } = defaults(options, { - defaultSort: 'most-popular', + defaultSort: DEFAULT_SORT, shouldLoadFromStorage: true, }) const router = useRouter() diff --git a/web/pages/contract-search-firestore.tsx b/web/pages/contract-search-firestore.tsx index 0ef8cdfe..2d45e831 100644 --- a/web/pages/contract-search-firestore.tsx +++ b/web/pages/contract-search-firestore.tsx @@ -54,10 +54,8 @@ export default function ContractSearchFirestore(props: { ) } else if (sort === 'most-traded') { matches.sort((a, b) => b.volume - a.volume) - } else if (sort === 'most-popular') { - matches.sort( - (a, b) => (b.uniqueBettorCount ?? 0) - (a.uniqueBettorCount ?? 0) - ) + } else if (sort === 'score') { + matches.sort((a, b) => (b.popularityScore ?? 0) - (a.popularityScore ?? 0)) } else if (sort === '24-hour-vol') { // Use lodash for stable sort, so previous sort breaks all ties. matches = sortBy(matches, ({ volume7Days }) => -1 * volume7Days) @@ -104,7 +102,7 @@ export default function ContractSearchFirestore(props: { > - + diff --git a/web/pages/home.tsx b/web/pages/home.tsx index 6aa99a07..53bb6ec9 100644 --- a/web/pages/home.tsx +++ b/web/pages/home.tsx @@ -5,7 +5,7 @@ import { PlusSmIcon } from '@heroicons/react/solid' import { Page } from 'web/components/page' import { Col } from 'web/components/layout/col' import { getSavedSort } from 'web/hooks/use-sort-and-query-params' -import { ContractSearch } from 'web/components/contract-search' +import { ContractSearch, DEFAULT_SORT } from 'web/components/contract-search' import { Contract } from 'common/contract' import { ContractPageContent } from './[username]/[contractSlug]' import { getContractFromSlug } from 'web/lib/firebase/contracts' @@ -28,7 +28,7 @@ const Home = () => { { // Show contract without navigating to contract page.