Score & sort by unique bettors in last 3 days

This commit is contained in:
Ian Philips 2022-07-19 16:29:41 -06:00
parent 58d6286361
commit 2152e5286a
7 changed files with 54 additions and 11 deletions

View File

@ -48,6 +48,7 @@ export type Contract<T extends AnyContractType = AnyContractType> = {
groupSlugs?: string[] groupSlugs?: string[]
uniqueBettorIds?: string[] uniqueBettorIds?: string[]
uniqueBettorCount?: number uniqueBettorCount?: number
popularityScore?: number
} & T } & T
export type BinaryContract = Contract & Binary export type BinaryContract = Contract & Binary

View File

@ -22,6 +22,7 @@ export * from './on-update-user'
export * from './on-create-comment-on-group' export * from './on-create-comment-on-group'
export * from './on-create-txn' export * from './on-create-txn'
export * from './on-delete-group' export * from './on-delete-group'
export * from './score-contracts'
// v2 // v2
export * from './health' export * from './health'

View File

@ -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 })
}
}

View File

@ -39,13 +39,14 @@ const indexPrefix = ENV === 'DEV' ? 'dev-' : ''
const sortIndexes = [ const sortIndexes = [
{ label: 'Newest', value: indexPrefix + 'contracts-newest' }, { label: 'Newest', value: indexPrefix + 'contracts-newest' },
{ label: 'Oldest', value: indexPrefix + 'contracts-oldest' }, { 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: 'Most traded', value: indexPrefix + 'contracts-most-traded' },
{ label: '24h volume', value: indexPrefix + 'contracts-24-hour-vol' }, { label: '24h volume', value: indexPrefix + 'contracts-24-hour-vol' },
{ label: 'Last updated', value: indexPrefix + 'contracts-last-updated' }, { label: 'Last updated', value: indexPrefix + 'contracts-last-updated' },
{ label: 'Close date', value: indexPrefix + 'contracts-close-date' }, { label: 'Close date', value: indexPrefix + 'contracts-close-date' },
{ label: 'Resolve date', value: indexPrefix + 'contracts-resolve-date' }, { label: 'Resolve date', value: indexPrefix + 'contracts-resolve-date' },
] ]
export const DEFAULT_SORT = 'score'
type filter = 'personal' | 'open' | 'closed' | 'resolved' | 'all' type filter = 'personal' | 'open' | 'closed' | 'resolved' | 'all'
const filterOptions: { [label: string]: filter } = { const filterOptions: { [label: string]: filter } = {
@ -95,7 +96,7 @@ export function ContractSearch(props: {
.map(({ value }) => value) .map(({ value }) => value)
.includes(`${indexPrefix}contracts-${initialSort ?? ''}`) .includes(`${indexPrefix}contracts-${initialSort ?? ''}`)
? initialSort ? initialSort
: querySortOptions?.defaultSort ?? 'most-popular' : querySortOptions?.defaultSort ?? DEFAULT_SORT
const [filter, setFilter] = useState<filter>( const [filter, setFilter] = useState<filter>(
querySortOptions?.defaultFilter ?? 'open' querySortOptions?.defaultFilter ?? 'open'

View File

@ -3,6 +3,7 @@ import { useRouter } from 'next/router'
import { useEffect, useMemo, useState } from 'react' import { useEffect, useMemo, useState } from 'react'
import { useSearchBox } from 'react-instantsearch-hooks-web' import { useSearchBox } from 'react-instantsearch-hooks-web'
import { track } from 'web/lib/service/analytics' import { track } from 'web/lib/service/analytics'
import { DEFAULT_SORT } from 'web/components/contract-search'
const MARKETS_SORT = 'markets_sort' const MARKETS_SORT = 'markets_sort'
@ -10,11 +11,11 @@ export type Sort =
| 'newest' | 'newest'
| 'oldest' | 'oldest'
| 'most-traded' | 'most-traded'
| 'most-popular'
| '24-hour-vol' | '24-hour-vol'
| 'close-date' | 'close-date'
| 'resolve-date' | 'resolve-date'
| 'last-updated' | 'last-updated'
| 'score'
export function getSavedSort() { export function getSavedSort() {
// TODO: this obviously doesn't work with SSR, common sense would suggest // TODO: this obviously doesn't work with SSR, common sense would suggest
@ -31,7 +32,7 @@ export function useInitialQueryAndSort(options?: {
shouldLoadFromStorage?: boolean shouldLoadFromStorage?: boolean
}) { }) {
const { defaultSort, shouldLoadFromStorage } = defaults(options, { const { defaultSort, shouldLoadFromStorage } = defaults(options, {
defaultSort: 'most-popular', defaultSort: DEFAULT_SORT,
shouldLoadFromStorage: true, shouldLoadFromStorage: true,
}) })
const router = useRouter() const router = useRouter()

View File

@ -54,10 +54,8 @@ export default function ContractSearchFirestore(props: {
) )
} else if (sort === 'most-traded') { } else if (sort === 'most-traded') {
matches.sort((a, b) => b.volume - a.volume) matches.sort((a, b) => b.volume - a.volume)
} else if (sort === 'most-popular') { } else if (sort === 'score') {
matches.sort( matches.sort((a, b) => (b.popularityScore ?? 0) - (a.popularityScore ?? 0))
(a, b) => (b.uniqueBettorCount ?? 0) - (a.uniqueBettorCount ?? 0)
)
} else if (sort === '24-hour-vol') { } else if (sort === '24-hour-vol') {
// Use lodash for stable sort, so previous sort breaks all ties. // Use lodash for stable sort, so previous sort breaks all ties.
matches = sortBy(matches, ({ volume7Days }) => -1 * volume7Days) matches = sortBy(matches, ({ volume7Days }) => -1 * volume7Days)
@ -104,7 +102,7 @@ export default function ContractSearchFirestore(props: {
> >
<option value="newest">Newest</option> <option value="newest">Newest</option>
<option value="oldest">Oldest</option> <option value="oldest">Oldest</option>
<option value="most-popular">Most popular</option> <option value="score">Most popular</option>
<option value="most-traded">Most traded</option> <option value="most-traded">Most traded</option>
<option value="24-hour-vol">24h volume</option> <option value="24-hour-vol">24h volume</option>
<option value="close-date">Closing soon</option> <option value="close-date">Closing soon</option>

View File

@ -5,7 +5,7 @@ import { PlusSmIcon } from '@heroicons/react/solid'
import { Page } from 'web/components/page' import { Page } from 'web/components/page'
import { Col } from 'web/components/layout/col' import { Col } from 'web/components/layout/col'
import { getSavedSort } from 'web/hooks/use-sort-and-query-params' 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 { Contract } from 'common/contract'
import { ContractPageContent } from './[username]/[contractSlug]' import { ContractPageContent } from './[username]/[contractSlug]'
import { getContractFromSlug } from 'web/lib/firebase/contracts' import { getContractFromSlug } from 'web/lib/firebase/contracts'
@ -28,7 +28,7 @@ const Home = () => {
<ContractSearch <ContractSearch
querySortOptions={{ querySortOptions={{
shouldLoadFromStorage: true, shouldLoadFromStorage: true,
defaultSort: getSavedSort() ?? 'most-popular', defaultSort: getSavedSort() ?? DEFAULT_SORT,
}} }}
onContractClick={(c) => { onContractClick={(c) => {
// Show contract without navigating to contract page. // Show contract without navigating to contract page.