Flag incorrectly resolved markets, warn about unreliable creators

This commit is contained in:
Pico2x 2022-09-27 19:36:38 -04:00
parent a12ed78813
commit d9ccdce8d4
8 changed files with 115 additions and 18 deletions

View File

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

View File

@ -33,6 +33,8 @@ export type User = {
allTime: number
}
correctResolutionPercentageCached: number
nextLoanCached: number
followerCountCached: number

View File

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

View File

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

View File

@ -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'
),

View File

@ -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 ? (

View File

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

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