Score & sort by unique bettors in last 3 days
This commit is contained in:
parent
58d6286361
commit
2152e5286a
|
@ -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
|
||||||
|
|
|
@ -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'
|
||||||
|
|
41
functions/src/score-contracts.ts
Normal file
41
functions/src/score-contracts.ts
Normal 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 })
|
||||||
|
}
|
||||||
|
}
|
|
@ -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'
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user