From d9ccdce8d426777728b8d5be06c4f68a042d75aa Mon Sep 17 00:00:00 2001 From: Pico2x Date: Tue, 27 Sep 2022 19:36:38 -0400 Subject: [PATCH] Flag incorrectly resolved markets, warn about unreliable creators --- common/contract.ts | 1 + common/user.ts | 2 + functions/src/create-user.ts | 1 + functions/src/update-metrics.ts | 24 ++++++++ web/components/confirmation-button.tsx | 7 +-- web/components/contract/contract-card.tsx | 24 ++++---- web/components/contract/contract-details.tsx | 17 +++++- .../contract/contract-report-resolution.tsx | 57 +++++++++++++++++++ 8 files changed, 115 insertions(+), 18 deletions(-) create mode 100644 web/components/contract/contract-report-resolution.tsx diff --git a/common/contract.ts b/common/contract.ts index 248c9745..aeb1d1b4 100644 --- a/common/contract.ts +++ b/common/contract.ts @@ -62,6 +62,7 @@ export type Contract = { featuredOnHomeRank?: number likedByUserIds?: string[] likedByUserCount?: number + resolutionReports?: string[] } & T export type BinaryContract = Contract & Binary diff --git a/common/user.ts b/common/user.ts index b1365929..55092586 100644 --- a/common/user.ts +++ b/common/user.ts @@ -33,6 +33,8 @@ export type User = { allTime: number } + correctResolutionPercentageCached: number + nextLoanCached: number followerCountCached: number diff --git a/functions/src/create-user.ts b/functions/src/create-user.ts index ab70b4e6..cd3a7569 100644 --- a/functions/src/create-user.ts +++ b/functions/src/create-user.ts @@ -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) diff --git a/functions/src/update-metrics.ts b/functions/src/update-metrics.ts index d2b5f9b2..564fe518 100644 --- a/functions/src/update-metrics.ts +++ b/functions/src/update-metrics.ts @@ -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 diff --git a/web/components/confirmation-button.tsx b/web/components/confirmation-button.tsx index 8dbe90c2..1fec24f8 100644 --- a/web/components/confirmation-button.tsx +++ b/web/components/confirmation-button.tsx @@ -68,10 +68,7 @@ export function ConfirmationButton(props: { -
updateOpen(true)} - > +
updateOpen(true)}> {openModalBtn.icon} {openModalBtn.label}
@@ -91,7 +88,7 @@ export function ResolveConfirmationButton(props: { {resolution ? ( - <> -
- Resolved + +
+
+ Resolved +
+
- - + +
) : ( <> {probAfter && probChanged ? ( diff --git a/web/components/contract/contract-details.tsx b/web/components/contract/contract-details.tsx index 3525b9f9..8ea6a003 100644 --- a/web/components/contract/contract-details.tsx +++ b/web/components/contract/contract-details.tsx @@ -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 && (
)} - + {disabled ? ( creatorName ) : ( @@ -170,6 +173,12 @@ export function MarketSubheader(props: { short={isMobile} /> )} + {correctResolutionPercentage != null && + correctResolutionPercentage < BAD_CREATOR_THRESHOLD && ( + + + + )} ) } + +const BAD_CREATOR_THRESHOLD = 0.8 diff --git a/web/components/contract/contract-report-resolution.tsx b/web/components/contract/contract-report-resolution.tsx new file mode 100644 index 00000000..bd6d3c7b --- /dev/null +++ b/web/components/contract/contract-report-resolution.tsx @@ -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 ( + + , + className: flagClass, + }} + cancelBtn={{ + label: 'Cancel', + className: 'border-none btn-sm btn-ghost self-center', + }} + submitBtn={{ + label: 'Submit', + className: 'btn-secondary', + }} + onSubmitWithSuccess={onSubmit} + > + + Flag this market as incorrectly resolved + + + + ) +}