Precalculate and store current positions for users who make comments (#878)
This commit is contained in:
parent
e37b805b49
commit
58dcbaaf6e
|
@ -1,4 +1,4 @@
|
||||||
import { maxBy, sortBy, sum, sumBy } from 'lodash'
|
import { maxBy, partition, sortBy, sum, sumBy } from 'lodash'
|
||||||
import { Bet, LimitBet } from './bet'
|
import { Bet, LimitBet } from './bet'
|
||||||
import {
|
import {
|
||||||
calculateCpmmSale,
|
calculateCpmmSale,
|
||||||
|
@ -255,3 +255,43 @@ export function getTopAnswer(
|
||||||
)
|
)
|
||||||
return top?.answer
|
return top?.answer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getLargestPosition(contract: Contract, userBets: Bet[]) {
|
||||||
|
let yesFloorShares = 0,
|
||||||
|
yesShares = 0,
|
||||||
|
noShares = 0,
|
||||||
|
noFloorShares = 0
|
||||||
|
|
||||||
|
if (userBets.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (contract.outcomeType === 'FREE_RESPONSE') {
|
||||||
|
const answerCounts: { [outcome: string]: number } = {}
|
||||||
|
for (const bet of userBets) {
|
||||||
|
if (bet.outcome) {
|
||||||
|
if (!answerCounts[bet.outcome]) {
|
||||||
|
answerCounts[bet.outcome] = bet.amount
|
||||||
|
} else {
|
||||||
|
answerCounts[bet.outcome] += bet.amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const majorityAnswer =
|
||||||
|
maxBy(Object.keys(answerCounts), (outcome) => answerCounts[outcome]) ?? ''
|
||||||
|
return {
|
||||||
|
prob: undefined,
|
||||||
|
shares: answerCounts[majorityAnswer] || 0,
|
||||||
|
outcome: majorityAnswer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [yesBets, noBets] = partition(userBets, (bet) => bet.outcome === 'YES')
|
||||||
|
yesShares = sumBy(yesBets, (bet) => bet.shares)
|
||||||
|
noShares = sumBy(noBets, (bet) => bet.shares)
|
||||||
|
yesFloorShares = Math.floor(yesShares)
|
||||||
|
noFloorShares = Math.floor(noShares)
|
||||||
|
|
||||||
|
const shares = yesFloorShares || noFloorShares
|
||||||
|
const outcome = yesFloorShares > noFloorShares ? 'YES' : 'NO'
|
||||||
|
return { shares, outcome }
|
||||||
|
}
|
||||||
|
|
|
@ -33,6 +33,11 @@ export type OnContract = {
|
||||||
// denormalized from bet
|
// denormalized from bet
|
||||||
betAmount?: number
|
betAmount?: number
|
||||||
betOutcome?: string
|
betOutcome?: string
|
||||||
|
|
||||||
|
// denormalized based on betting history
|
||||||
|
commenterPositionProb?: number // binary only
|
||||||
|
commenterPositionShares?: number
|
||||||
|
commenterPositionOutcome?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OnGroup = {
|
export type OnGroup = {
|
||||||
|
|
|
@ -5,6 +5,8 @@ import { getContract, getUser, getValues } from './utils'
|
||||||
import { ContractComment } from '../../common/comment'
|
import { ContractComment } from '../../common/comment'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from '../../common/bet'
|
||||||
import { Answer } from '../../common/answer'
|
import { Answer } from '../../common/answer'
|
||||||
|
import { getLargestPosition } from '../../common/calculate'
|
||||||
|
import { maxBy } from 'lodash'
|
||||||
import {
|
import {
|
||||||
createCommentOrAnswerOrUpdatedContractNotification,
|
createCommentOrAnswerOrUpdatedContractNotification,
|
||||||
replied_users_info,
|
replied_users_info,
|
||||||
|
@ -45,6 +47,32 @@ export const onCreateCommentOnContract = functions
|
||||||
.doc(contract.id)
|
.doc(contract.id)
|
||||||
.update({ lastCommentTime, lastUpdatedTime: Date.now() })
|
.update({ lastCommentTime, lastUpdatedTime: Date.now() })
|
||||||
|
|
||||||
|
const previousBetsQuery = await firestore
|
||||||
|
.collection('contracts')
|
||||||
|
.doc(contractId)
|
||||||
|
.collection('bets')
|
||||||
|
.where('createdTime', '<', comment.createdTime)
|
||||||
|
.get()
|
||||||
|
const previousBets = previousBetsQuery.docs.map((d) => d.data() as Bet)
|
||||||
|
const position = getLargestPosition(
|
||||||
|
contract,
|
||||||
|
previousBets.filter((b) => b.userId === comment.userId && !b.isAnte)
|
||||||
|
)
|
||||||
|
if (position) {
|
||||||
|
const fields: { [k: string]: unknown } = {
|
||||||
|
commenterPositionShares: position.shares,
|
||||||
|
commenterPositionOutcome: position.outcome,
|
||||||
|
}
|
||||||
|
const previousProb =
|
||||||
|
contract.outcomeType === 'BINARY'
|
||||||
|
? maxBy(previousBets, (bet) => bet.createdTime)?.probAfter
|
||||||
|
: undefined
|
||||||
|
if (previousProb != null) {
|
||||||
|
fields.commenterPositionProb = previousProb
|
||||||
|
}
|
||||||
|
await change.ref.update(fields)
|
||||||
|
}
|
||||||
|
|
||||||
let bet: Bet | undefined
|
let bet: Bet | undefined
|
||||||
let answer: Answer | undefined
|
let answer: Answer | undefined
|
||||||
if (comment.answerOutcome) {
|
if (comment.answerOutcome) {
|
||||||
|
|
92
functions/src/scripts/backfill-comment-position-data.ts
Normal file
92
functions/src/scripts/backfill-comment-position-data.ts
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
// Filling in historical bet positions on comments.
|
||||||
|
|
||||||
|
// Warning: This just recalculates all of them, rather than trying to
|
||||||
|
// figure out which ones are out of date, since I'm using it to fill them
|
||||||
|
// in once in the first place.
|
||||||
|
|
||||||
|
import { maxBy } from 'lodash'
|
||||||
|
import * as admin from 'firebase-admin'
|
||||||
|
import { filterDefined } from '../../../common/util/array'
|
||||||
|
import { Bet } from '../../../common/bet'
|
||||||
|
import { Comment } from '../../../common/comment'
|
||||||
|
import { Contract } from '../../../common/contract'
|
||||||
|
import { getLargestPosition } from '../../../common/calculate'
|
||||||
|
import { initAdmin } from './script-init'
|
||||||
|
import { DocumentSnapshot } from 'firebase-admin/firestore'
|
||||||
|
import { log, writeAsync } from '../utils'
|
||||||
|
|
||||||
|
initAdmin()
|
||||||
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
|
async function getContractsById() {
|
||||||
|
const contracts = await firestore.collection('contracts').get()
|
||||||
|
const results = Object.fromEntries(
|
||||||
|
contracts.docs.map((doc) => [doc.id, doc.data() as Contract])
|
||||||
|
)
|
||||||
|
log(`Found ${contracts.size} contracts.`)
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getCommentsByContractId() {
|
||||||
|
const comments = await firestore
|
||||||
|
.collectionGroup('comments')
|
||||||
|
.where('contractId', '!=', null)
|
||||||
|
.get()
|
||||||
|
const results = new Map<string, DocumentSnapshot[]>()
|
||||||
|
comments.forEach((doc) => {
|
||||||
|
const contractId = doc.get('contractId')
|
||||||
|
const contractComments = results.get(contractId) || []
|
||||||
|
contractComments.push(doc)
|
||||||
|
results.set(contractId, contractComments)
|
||||||
|
})
|
||||||
|
log(`Found ${comments.size} comments on ${results.size} contracts.`)
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// not in a transaction for speed -- may need to be run more than once
|
||||||
|
async function denormalize() {
|
||||||
|
const contractsById = await getContractsById()
|
||||||
|
const commentsByContractId = await getCommentsByContractId()
|
||||||
|
for (const [contractId, comments] of commentsByContractId.entries()) {
|
||||||
|
const betsQuery = await firestore
|
||||||
|
.collection('contracts')
|
||||||
|
.doc(contractId)
|
||||||
|
.collection('bets')
|
||||||
|
.get()
|
||||||
|
log(`Loaded ${betsQuery.size} bets for contract ${contractId}.`)
|
||||||
|
const bets = betsQuery.docs.map((d) => d.data() as Bet)
|
||||||
|
const updates = comments.map((doc) => {
|
||||||
|
const comment = doc.data() as Comment
|
||||||
|
const contract = contractsById[contractId]
|
||||||
|
const previousBets = bets.filter(
|
||||||
|
(b) => b.createdTime < comment.createdTime
|
||||||
|
)
|
||||||
|
const position = getLargestPosition(
|
||||||
|
contract,
|
||||||
|
previousBets.filter((b) => b.userId === comment.userId && !b.isAnte)
|
||||||
|
)
|
||||||
|
if (position) {
|
||||||
|
const fields: { [k: string]: unknown } = {
|
||||||
|
commenterPositionShares: position.shares,
|
||||||
|
commenterPositionOutcome: position.outcome,
|
||||||
|
}
|
||||||
|
const previousProb =
|
||||||
|
contract.outcomeType === 'BINARY'
|
||||||
|
? maxBy(previousBets, (bet) => bet.createdTime)?.probAfter
|
||||||
|
: undefined
|
||||||
|
if (previousProb != null) {
|
||||||
|
fields.commenterPositionProb = previousProb
|
||||||
|
}
|
||||||
|
return { doc: doc.ref, fields }
|
||||||
|
} else {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
log(`Updating ${updates.length} comments.`)
|
||||||
|
await writeAsync(firestore, filterDefined(updates))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
denormalize().catch((e) => console.error(e))
|
||||||
|
}
|
|
@ -106,7 +106,6 @@ export function ContractTopTrades(props: {
|
||||||
contract={contract}
|
contract={contract}
|
||||||
comment={commentsById[topCommentId]}
|
comment={commentsById[topCommentId]}
|
||||||
tips={tips[topCommentId]}
|
tips={tips[topCommentId]}
|
||||||
betsBySameUser={[betsById[topCommentId]]}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Spacer h={16} />
|
<Spacer h={16} />
|
||||||
|
|
|
@ -85,7 +85,9 @@ export function ContractTabs(props: {
|
||||||
<div className={'mb-4 w-full border-b border-gray-200'} />
|
<div className={'mb-4 w-full border-b border-gray-200'} />
|
||||||
<ContractCommentsActivity
|
<ContractCommentsActivity
|
||||||
contract={contract}
|
contract={contract}
|
||||||
bets={generalBets}
|
betsByCurrentUser={
|
||||||
|
user ? generalBets.filter((b) => b.userId === user.id) : []
|
||||||
|
}
|
||||||
comments={generalComments}
|
comments={generalComments}
|
||||||
tips={tips}
|
tips={tips}
|
||||||
user={user}
|
user={user}
|
||||||
|
@ -95,7 +97,9 @@ export function ContractTabs(props: {
|
||||||
) : (
|
) : (
|
||||||
<ContractCommentsActivity
|
<ContractCommentsActivity
|
||||||
contract={contract}
|
contract={contract}
|
||||||
bets={visibleBets}
|
betsByCurrentUser={
|
||||||
|
user ? visibleBets.filter((b) => b.userId === user.id) : []
|
||||||
|
}
|
||||||
comments={comments}
|
comments={comments}
|
||||||
tips={tips}
|
tips={tips}
|
||||||
user={user}
|
user={user}
|
||||||
|
|
|
@ -73,13 +73,12 @@ export function ContractBetsActivity(props: {
|
||||||
|
|
||||||
export function ContractCommentsActivity(props: {
|
export function ContractCommentsActivity(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
bets: Bet[]
|
betsByCurrentUser: Bet[]
|
||||||
comments: ContractComment[]
|
comments: ContractComment[]
|
||||||
tips: CommentTipMap
|
tips: CommentTipMap
|
||||||
user: User | null | undefined
|
user: User | null | undefined
|
||||||
}) {
|
}) {
|
||||||
const { bets, contract, comments, user, tips } = props
|
const { betsByCurrentUser, contract, comments, user, tips } = props
|
||||||
const betsByUserId = groupBy(bets, (bet) => bet.userId)
|
|
||||||
const commentsByUserId = groupBy(comments, (c) => c.userId)
|
const commentsByUserId = groupBy(comments, (c) => c.userId)
|
||||||
const commentsByParentId = groupBy(comments, (c) => c.replyToCommentId ?? '_')
|
const commentsByParentId = groupBy(comments, (c) => c.replyToCommentId ?? '_')
|
||||||
const topLevelComments = sortBy(
|
const topLevelComments = sortBy(
|
||||||
|
@ -92,7 +91,7 @@ export function ContractCommentsActivity(props: {
|
||||||
<ContractCommentInput
|
<ContractCommentInput
|
||||||
className="mb-5"
|
className="mb-5"
|
||||||
contract={contract}
|
contract={contract}
|
||||||
betsByCurrentUser={(user && betsByUserId[user.id]) ?? []}
|
betsByCurrentUser={betsByCurrentUser}
|
||||||
commentsByCurrentUser={(user && commentsByUserId[user.id]) ?? []}
|
commentsByCurrentUser={(user && commentsByUserId[user.id]) ?? []}
|
||||||
/>
|
/>
|
||||||
{topLevelComments.map((parent) => (
|
{topLevelComments.map((parent) => (
|
||||||
|
@ -106,8 +105,7 @@ export function ContractCommentsActivity(props: {
|
||||||
(c) => c.createdTime
|
(c) => c.createdTime
|
||||||
)}
|
)}
|
||||||
tips={tips}
|
tips={tips}
|
||||||
bets={bets}
|
betsByCurrentUser={betsByCurrentUser}
|
||||||
betsByUserId={betsByUserId}
|
|
||||||
commentsByUserId={commentsByUserId}
|
commentsByUserId={commentsByUserId}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
@ -136,7 +134,9 @@ export function FreeResponseContractCommentsActivity(props: {
|
||||||
})
|
})
|
||||||
.filter((answer) => answer != null)
|
.filter((answer) => answer != null)
|
||||||
|
|
||||||
const betsByUserId = groupBy(bets, (bet) => bet.userId)
|
const betsByCurrentUser = user
|
||||||
|
? bets.filter((bet) => bet.userId === user.id)
|
||||||
|
: []
|
||||||
const commentsByUserId = groupBy(comments, (c) => c.userId)
|
const commentsByUserId = groupBy(comments, (c) => c.userId)
|
||||||
const commentsByOutcome = groupBy(comments, (c) => c.answerOutcome ?? '_')
|
const commentsByOutcome = groupBy(comments, (c) => c.answerOutcome ?? '_')
|
||||||
|
|
||||||
|
@ -157,7 +157,7 @@ export function FreeResponseContractCommentsActivity(props: {
|
||||||
(c) => c.createdTime
|
(c) => c.createdTime
|
||||||
)}
|
)}
|
||||||
tips={tips}
|
tips={tips}
|
||||||
betsByUserId={betsByUserId}
|
betsByCurrentUser={betsByCurrentUser}
|
||||||
commentsByUserId={commentsByUserId}
|
commentsByUserId={commentsByUserId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -27,7 +27,7 @@ export function FeedAnswerCommentGroup(props: {
|
||||||
answer: Answer
|
answer: Answer
|
||||||
answerComments: ContractComment[]
|
answerComments: ContractComment[]
|
||||||
tips: CommentTipMap
|
tips: CommentTipMap
|
||||||
betsByUserId: Dictionary<Bet[]>
|
betsByCurrentUser: Bet[]
|
||||||
commentsByUserId: Dictionary<ContractComment[]>
|
commentsByUserId: Dictionary<ContractComment[]>
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
|
@ -35,7 +35,7 @@ export function FeedAnswerCommentGroup(props: {
|
||||||
contract,
|
contract,
|
||||||
answerComments,
|
answerComments,
|
||||||
tips,
|
tips,
|
||||||
betsByUserId,
|
betsByCurrentUser,
|
||||||
commentsByUserId,
|
commentsByUserId,
|
||||||
user,
|
user,
|
||||||
} = props
|
} = props
|
||||||
|
@ -48,7 +48,6 @@ export function FeedAnswerCommentGroup(props: {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const answerElementId = `answer-${answer.id}`
|
const answerElementId = `answer-${answer.id}`
|
||||||
const betsByCurrentUser = (user && betsByUserId[user.id]) ?? []
|
|
||||||
const commentsByCurrentUser = (user && commentsByUserId[user.id]) ?? []
|
const commentsByCurrentUser = (user && commentsByUserId[user.id]) ?? []
|
||||||
const isFreeResponseContractPage = !!commentsByCurrentUser
|
const isFreeResponseContractPage = !!commentsByCurrentUser
|
||||||
const mostRecentCommentableBet = getMostRecentCommentableBet(
|
const mostRecentCommentableBet = getMostRecentCommentableBet(
|
||||||
|
@ -166,7 +165,6 @@ export function FeedAnswerCommentGroup(props: {
|
||||||
contract={contract}
|
contract={contract}
|
||||||
comment={comment}
|
comment={comment}
|
||||||
tips={tips[comment.id]}
|
tips={tips[comment.id]}
|
||||||
betsBySameUser={betsByUserId[comment.userId] ?? []}
|
|
||||||
onReplyClick={scrollAndOpenReplyInput}
|
onReplyClick={scrollAndOpenReplyInput}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { ContractComment } from 'common/comment'
|
||||||
import { User } from 'common/user'
|
import { User } from 'common/user'
|
||||||
import { Contract } from 'common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { minBy, maxBy, partition, sumBy, Dictionary } from 'lodash'
|
import { Dictionary } from 'lodash'
|
||||||
import { useUser } from 'web/hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { formatMoney } from 'common/util/format'
|
import { formatMoney } from 'common/util/format'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
|
@ -29,8 +29,7 @@ export function FeedCommentThread(props: {
|
||||||
threadComments: ContractComment[]
|
threadComments: ContractComment[]
|
||||||
tips: CommentTipMap
|
tips: CommentTipMap
|
||||||
parentComment: ContractComment
|
parentComment: ContractComment
|
||||||
bets: Bet[]
|
betsByCurrentUser: Bet[]
|
||||||
betsByUserId: Dictionary<Bet[]>
|
|
||||||
commentsByUserId: Dictionary<ContractComment[]>
|
commentsByUserId: Dictionary<ContractComment[]>
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
|
@ -38,8 +37,7 @@ export function FeedCommentThread(props: {
|
||||||
contract,
|
contract,
|
||||||
threadComments,
|
threadComments,
|
||||||
commentsByUserId,
|
commentsByUserId,
|
||||||
bets,
|
betsByCurrentUser,
|
||||||
betsByUserId,
|
|
||||||
tips,
|
tips,
|
||||||
parentComment,
|
parentComment,
|
||||||
} = props
|
} = props
|
||||||
|
@ -64,17 +62,7 @@ export function FeedCommentThread(props: {
|
||||||
contract={contract}
|
contract={contract}
|
||||||
comment={comment}
|
comment={comment}
|
||||||
tips={tips[comment.id]}
|
tips={tips[comment.id]}
|
||||||
betsBySameUser={betsByUserId[comment.userId] ?? []}
|
|
||||||
onReplyClick={scrollAndOpenReplyInput}
|
onReplyClick={scrollAndOpenReplyInput}
|
||||||
probAtCreatedTime={
|
|
||||||
contract.outcomeType === 'BINARY'
|
|
||||||
? minBy(bets, (bet) => {
|
|
||||||
return bet.createdTime < comment.createdTime
|
|
||||||
? comment.createdTime - bet.createdTime
|
|
||||||
: comment.createdTime
|
|
||||||
})?.probAfter
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{showReply && (
|
{showReply && (
|
||||||
|
@ -85,7 +73,7 @@ export function FeedCommentThread(props: {
|
||||||
/>
|
/>
|
||||||
<ContractCommentInput
|
<ContractCommentInput
|
||||||
contract={contract}
|
contract={contract}
|
||||||
betsByCurrentUser={(user && betsByUserId[user.id]) ?? []}
|
betsByCurrentUser={(user && betsByCurrentUser) ?? []}
|
||||||
commentsByCurrentUser={(user && commentsByUserId[user.id]) ?? []}
|
commentsByCurrentUser={(user && commentsByUserId[user.id]) ?? []}
|
||||||
parentCommentId={parentComment.id}
|
parentCommentId={parentComment.id}
|
||||||
replyToUser={replyTo}
|
replyToUser={replyTo}
|
||||||
|
@ -104,22 +92,21 @@ export function FeedComment(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
comment: ContractComment
|
comment: ContractComment
|
||||||
tips: CommentTips
|
tips: CommentTips
|
||||||
betsBySameUser: Bet[]
|
|
||||||
indent?: boolean
|
indent?: boolean
|
||||||
probAtCreatedTime?: number
|
|
||||||
onReplyClick?: (comment: ContractComment) => void
|
onReplyClick?: (comment: ContractComment) => void
|
||||||
}) {
|
}) {
|
||||||
|
const { contract, comment, tips, indent, onReplyClick } = props
|
||||||
const {
|
const {
|
||||||
contract,
|
text,
|
||||||
comment,
|
content,
|
||||||
tips,
|
userUsername,
|
||||||
betsBySameUser,
|
userName,
|
||||||
indent,
|
userAvatarUrl,
|
||||||
probAtCreatedTime,
|
commenterPositionProb,
|
||||||
onReplyClick,
|
commenterPositionShares,
|
||||||
} = props
|
commenterPositionOutcome,
|
||||||
const { text, content, userUsername, userName, userAvatarUrl, createdTime } =
|
createdTime,
|
||||||
comment
|
} = comment
|
||||||
const betOutcome = comment.betOutcome
|
const betOutcome = comment.betOutcome
|
||||||
let bought: string | undefined
|
let bought: string | undefined
|
||||||
let money: string | undefined
|
let money: string | undefined
|
||||||
|
@ -136,13 +123,6 @@ export function FeedComment(props: {
|
||||||
}
|
}
|
||||||
}, [comment.id, router.asPath])
|
}, [comment.id, router.asPath])
|
||||||
|
|
||||||
// Only calculated if they don't have a matching bet
|
|
||||||
const { userPosition, outcome } = getBettorsLargestPositionBeforeTime(
|
|
||||||
contract,
|
|
||||||
comment.createdTime,
|
|
||||||
comment.betId ? [] : betsBySameUser
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row
|
<Row
|
||||||
id={comment.id}
|
id={comment.id}
|
||||||
|
@ -167,14 +147,17 @@ export function FeedComment(props: {
|
||||||
username={userUsername}
|
username={userUsername}
|
||||||
name={userName}
|
name={userName}
|
||||||
/>{' '}
|
/>{' '}
|
||||||
{!comment.betId != null &&
|
{comment.betId == null &&
|
||||||
userPosition > 0 &&
|
commenterPositionProb != null &&
|
||||||
|
commenterPositionOutcome != null &&
|
||||||
|
commenterPositionShares != null &&
|
||||||
|
commenterPositionShares > 0 &&
|
||||||
contract.outcomeType !== 'NUMERIC' && (
|
contract.outcomeType !== 'NUMERIC' && (
|
||||||
<>
|
<>
|
||||||
{'is '}
|
{'is '}
|
||||||
<CommentStatus
|
<CommentStatus
|
||||||
prob={probAtCreatedTime}
|
prob={commenterPositionProb}
|
||||||
outcome={outcome}
|
outcome={commenterPositionOutcome}
|
||||||
contract={contract}
|
contract={contract}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
@ -310,56 +293,6 @@ export function ContractCommentInput(props: {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBettorsLargestPositionBeforeTime(
|
|
||||||
contract: Contract,
|
|
||||||
createdTime: number,
|
|
||||||
bets: Bet[]
|
|
||||||
) {
|
|
||||||
let yesFloorShares = 0,
|
|
||||||
yesShares = 0,
|
|
||||||
noShares = 0,
|
|
||||||
noFloorShares = 0
|
|
||||||
|
|
||||||
const previousBets = bets.filter(
|
|
||||||
(prevBet) => prevBet.createdTime < createdTime && !prevBet.isAnte
|
|
||||||
)
|
|
||||||
|
|
||||||
if (contract.outcomeType === 'FREE_RESPONSE') {
|
|
||||||
const answerCounts: { [outcome: string]: number } = {}
|
|
||||||
for (const bet of previousBets) {
|
|
||||||
if (bet.outcome) {
|
|
||||||
if (!answerCounts[bet.outcome]) {
|
|
||||||
answerCounts[bet.outcome] = bet.amount
|
|
||||||
} else {
|
|
||||||
answerCounts[bet.outcome] += bet.amount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const majorityAnswer =
|
|
||||||
maxBy(Object.keys(answerCounts), (outcome) => answerCounts[outcome]) ?? ''
|
|
||||||
return {
|
|
||||||
userPosition: answerCounts[majorityAnswer] || 0,
|
|
||||||
outcome: majorityAnswer,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (bets.length === 0) {
|
|
||||||
return { userPosition: 0, outcome: '' }
|
|
||||||
}
|
|
||||||
|
|
||||||
const [yesBets, noBets] = partition(
|
|
||||||
previousBets ?? [],
|
|
||||||
(bet) => bet.outcome === 'YES'
|
|
||||||
)
|
|
||||||
yesShares = sumBy(yesBets, (bet) => bet.shares)
|
|
||||||
noShares = sumBy(noBets, (bet) => bet.shares)
|
|
||||||
yesFloorShares = Math.floor(yesShares)
|
|
||||||
noFloorShares = Math.floor(noShares)
|
|
||||||
|
|
||||||
const userPosition = yesFloorShares || noFloorShares
|
|
||||||
const outcome = yesFloorShares > noFloorShares ? 'YES' : 'NO'
|
|
||||||
return { userPosition, outcome }
|
|
||||||
}
|
|
||||||
|
|
||||||
function canCommentOnBet(bet: Bet, user?: User | null) {
|
function canCommentOnBet(bet: Bet, user?: User | null) {
|
||||||
const { userId, createdTime, isRedemption } = bet
|
const { userId, createdTime, isRedemption } = bet
|
||||||
const isSelf = user?.id === userId
|
const isSelf = user?.id === userId
|
||||||
|
|
Loading…
Reference in New Issue
Block a user