Flag incorrectly resolved markets, warn about unreliable creators
This commit is contained in:
parent
a12ed78813
commit
d9ccdce8d4
|
@ -62,6 +62,7 @@ export type Contract<T extends AnyContractType = AnyContractType> = {
|
|||
featuredOnHomeRank?: number
|
||||
likedByUserIds?: string[]
|
||||
likedByUserCount?: number
|
||||
resolutionReports?: string[]
|
||||
} & T
|
||||
|
||||
export type BinaryContract = Contract & Binary
|
||||
|
|
|
@ -33,6 +33,8 @@ export type User = {
|
|||
allTime: number
|
||||
}
|
||||
|
||||
correctResolutionPercentageCached: number
|
||||
|
||||
nextLoanCached: number
|
||||
followerCountCached: number
|
||||
|
||||
|
|
|
@ -69,6 +69,7 @@ export const createuser = newEndpoint(opts, async (req, auth) => {
|
|||
followerCountCached: 0,
|
||||
followedCategories: DEFAULT_CATEGORIES,
|
||||
shouldShowWelcome: true,
|
||||
correctResolutionPercentageCached: 1,
|
||||
}
|
||||
|
||||
await firestore.collection('users').doc(auth.uid).create(user)
|
||||
|
|
|
@ -116,6 +116,25 @@ export async function updateMetricsCore() {
|
|||
lastPortfolio.investmentValue !== newPortfolio.investmentValue
|
||||
|
||||
const newProfit = calculateNewProfit(portfolioHistory, newPortfolio)
|
||||
const contractRatios = userContracts
|
||||
.map((contract) => {
|
||||
if (
|
||||
!contract.resolutionReports ||
|
||||
contract.resolutionReports?.length === 0
|
||||
) {
|
||||
return 0
|
||||
}
|
||||
const contractRatio =
|
||||
contract.resolutionReports.length / (contract.uniqueBettorCount ?? 1)
|
||||
|
||||
return contractRatio
|
||||
})
|
||||
.filter((ratio) => ratio > 0)
|
||||
const badResolutions = contractRatios.filter(
|
||||
(ratio) => ratio > BAD_RESOLUTION_THRESHOLD
|
||||
)
|
||||
const newCorrectResolutionPercentage =
|
||||
(userContracts.length - badResolutions.length) / userContracts.length
|
||||
|
||||
return {
|
||||
user,
|
||||
|
@ -123,6 +142,7 @@ export async function updateMetricsCore() {
|
|||
newPortfolio,
|
||||
newProfit,
|
||||
didPortfolioChange,
|
||||
newCorrectResolutionPercentage,
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -144,6 +164,7 @@ export async function updateMetricsCore() {
|
|||
newPortfolio,
|
||||
newProfit,
|
||||
didPortfolioChange,
|
||||
newCorrectResolutionPercentage,
|
||||
}) => {
|
||||
const nextLoanCached = nextLoanByUser[user.id]?.payout ?? 0
|
||||
return {
|
||||
|
@ -153,6 +174,7 @@ export async function updateMetricsCore() {
|
|||
creatorVolumeCached: newCreatorVolume,
|
||||
profitCached: newProfit,
|
||||
nextLoanCached,
|
||||
correctResolutionPercentageCached: newCorrectResolutionPercentage,
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -224,3 +246,5 @@ const topUserScores = (scores: { [userId: string]: number }) => {
|
|||
}
|
||||
|
||||
type GroupContractDoc = { contractId: string; createdTime: number }
|
||||
|
||||
const BAD_RESOLUTION_THRESHOLD = 0.1
|
||||
|
|
|
@ -68,10 +68,7 @@ export function ConfirmationButton(props: {
|
|||
</Row>
|
||||
</Col>
|
||||
</Modal>
|
||||
<div
|
||||
className={clsx('btn', openModalBtn.className)}
|
||||
onClick={() => updateOpen(true)}
|
||||
>
|
||||
<div className={openModalBtn.className} onClick={() => updateOpen(true)}>
|
||||
{openModalBtn.icon}
|
||||
{openModalBtn.label}
|
||||
</div>
|
||||
|
@ -91,7 +88,7 @@ export function ResolveConfirmationButton(props: {
|
|||
<ConfirmationButton
|
||||
openModalBtn={{
|
||||
className: clsx(
|
||||
'border-none self-start',
|
||||
'btn border-none self-start',
|
||||
openModalButtonClass,
|
||||
isSubmitting && 'btn-disabled loading'
|
||||
),
|
||||
|
|
|
@ -35,6 +35,7 @@ import { getMappedValue } from 'common/pseudo-numeric'
|
|||
import { Tooltip } from '../tooltip'
|
||||
import { SiteLink } from '../site-link'
|
||||
import { ProbChange } from './prob-change-table'
|
||||
import { ContractReportResolution } from './contract-report-resolution'
|
||||
|
||||
export function ContractCard(props: {
|
||||
contract: Contract
|
||||
|
@ -213,7 +214,8 @@ export function BinaryResolutionOrChance(props: {
|
|||
return (
|
||||
<Col className={clsx(large ? 'text-4xl' : 'text-3xl', className)}>
|
||||
{resolution ? (
|
||||
<>
|
||||
<Row className="flex items-start">
|
||||
<div>
|
||||
<div
|
||||
className={clsx('text-gray-500', large ? 'text-xl' : 'text-base')}
|
||||
>
|
||||
|
@ -223,7 +225,9 @@ export function BinaryResolutionOrChance(props: {
|
|||
contract={contract}
|
||||
resolution={resolution}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
<ContractReportResolution contract={contract} />
|
||||
</Row>
|
||||
) : (
|
||||
<>
|
||||
{probAfter && probChanged ? (
|
||||
|
|
|
@ -14,7 +14,7 @@ import { useState } from 'react'
|
|||
import NewContractBadge from '../new-contract-badge'
|
||||
import { MiniUserFollowButton } from '../follow-button'
|
||||
import { DAY_MS } from 'common/util/time'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
import { useUser, useUserById } from 'web/hooks/use-user'
|
||||
import { exhibitExts } from 'common/util/parse'
|
||||
import { Button } from 'web/components/button'
|
||||
import { Modal } from 'web/components/layout/modal'
|
||||
|
@ -28,7 +28,7 @@ import { UserLink } from 'web/components/user-link'
|
|||
import { FeaturedContractBadge } from 'web/components/contract/featured-contract-badge'
|
||||
import { Tooltip } from 'web/components/tooltip'
|
||||
import { ExtraContractActionsRow } from './extra-contract-actions-row'
|
||||
import { PlusCircleIcon } from '@heroicons/react/solid'
|
||||
import { ExclamationIcon, PlusCircleIcon } from '@heroicons/react/solid'
|
||||
import { GroupLink } from 'common/group'
|
||||
import { Subtitle } from '../subtitle'
|
||||
import { useIsMobile } from 'web/hooks/use-is-mobile'
|
||||
|
@ -142,6 +142,8 @@ export function MarketSubheader(props: {
|
|||
const { creatorName, creatorUsername, creatorId, creatorAvatarUrl } = contract
|
||||
const { resolvedDate } = contractMetrics(contract)
|
||||
const user = useUser()
|
||||
const correctResolutionPercentage =
|
||||
useUserById(creatorId)?.correctResolutionPercentageCached
|
||||
const isCreator = user?.id === creatorId
|
||||
const isMobile = useIsMobile()
|
||||
return (
|
||||
|
@ -153,13 +155,14 @@ export function MarketSubheader(props: {
|
|||
size={9}
|
||||
className="mr-1.5"
|
||||
/>
|
||||
|
||||
{!disabled && (
|
||||
<div className="absolute mt-3 ml-[11px]">
|
||||
<MiniUserFollowButton userId={creatorId} />
|
||||
</div>
|
||||
)}
|
||||
<Col className="text-greyscale-6 ml-2 flex-1 flex-wrap text-sm">
|
||||
<Row className="w-full justify-between ">
|
||||
<Row className="w-full space-x-1 ">
|
||||
{disabled ? (
|
||||
creatorName
|
||||
) : (
|
||||
|
@ -170,6 +173,12 @@ export function MarketSubheader(props: {
|
|||
short={isMobile}
|
||||
/>
|
||||
)}
|
||||
{correctResolutionPercentage != null &&
|
||||
correctResolutionPercentage < BAD_CREATOR_THRESHOLD && (
|
||||
<Tooltip text="This creator has a track record of creating contracts that are resolved incorrectly.">
|
||||
<ExclamationIcon className="h-6 w-6 text-yellow-500" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</Row>
|
||||
<Row className="text-2xs text-greyscale-4 gap-2 sm:text-xs">
|
||||
<CloseOrResolveTime
|
||||
|
@ -445,3 +454,5 @@ function EditableCloseDate(props: {
|
|||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const BAD_CREATOR_THRESHOLD = 0.8
|
||||
|
|
57
web/components/contract/contract-report-resolution.tsx
Normal file
57
web/components/contract/contract-report-resolution.tsx
Normal file
|
@ -0,0 +1,57 @@
|
|||
import { Contract } from 'common/contract'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
import clsx from 'clsx'
|
||||
import { updateContract } from 'web/lib/firebase/contracts'
|
||||
import { Tooltip } from '../tooltip'
|
||||
import { ConfirmationButton } from '../confirmation-button'
|
||||
import { Row } from '../layout/row'
|
||||
import { FlagIcon } from '@heroicons/react/solid'
|
||||
|
||||
export function ContractReportResolution(props: { contract: Contract }) {
|
||||
const { contract } = props
|
||||
const user = useUser()
|
||||
if (!user) {
|
||||
return <></>
|
||||
}
|
||||
const userReported = contract.resolutionReports?.includes(user.id)
|
||||
|
||||
const onSubmit = async () => {
|
||||
if (!user) {
|
||||
return true
|
||||
}
|
||||
await updateContract(contract.id, {
|
||||
resolutionReports: [...(contract.resolutionReports || []), user.id],
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
const flagClass = clsx(
|
||||
'mx-2 flex flex-col items-center gap-1 w-6 h-6 text-gray-500 rounded-md bg-gray-100 px-2 py-1 hover:bg-gray-300',
|
||||
userReported && 'text-red-500'
|
||||
)
|
||||
|
||||
return (
|
||||
<Tooltip text="Flag this market as incorrectly resolved">
|
||||
<ConfirmationButton
|
||||
openModalBtn={{
|
||||
label: '',
|
||||
icon: <FlagIcon className="h-5 w-5" />,
|
||||
className: flagClass,
|
||||
}}
|
||||
cancelBtn={{
|
||||
label: 'Cancel',
|
||||
className: 'border-none btn-sm btn-ghost self-center',
|
||||
}}
|
||||
submitBtn={{
|
||||
label: 'Submit',
|
||||
className: 'btn-secondary',
|
||||
}}
|
||||
onSubmitWithSuccess={onSubmit}
|
||||
>
|
||||
<Row className="items-center text-xl">
|
||||
Flag this market as incorrectly resolved
|
||||
</Row>
|
||||
</ConfirmationButton>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue
Block a user