Add AnswerContract type to wrap FR, MCM, Bounty

This commit is contained in:
Austin Chen 2022-08-15 16:35:13 -07:00
parent 49f60cdb80
commit 00717c90ef
15 changed files with 49 additions and 34 deletions

View File

@ -24,6 +24,7 @@ import {
FreeResponseContract, FreeResponseContract,
PseudoNumericContract, PseudoNumericContract,
MultipleChoiceContract, MultipleChoiceContract,
AnswerContract,
} from './contract' } from './contract'
import { floatingEqual } from './util/math' import { floatingEqual } from './util/math'
@ -201,9 +202,7 @@ export function getContractBetNullMetrics() {
} }
} }
export function getTopAnswer( export function getTopAnswer(contract: AnswerContract) {
contract: FreeResponseContract | MultipleChoiceContract
) {
const { answers } = contract const { answers } = contract
const top = maxBy( const top = maxBy(
answers?.map((answer) => ({ answers?.map((answer) => ({

View File

@ -71,6 +71,10 @@ export type DPMContract = Contract & DPM
export type CPMMContract = Contract & CPMM export type CPMMContract = Contract & CPMM
export type DPMBinaryContract = BinaryContract & DPM export type DPMBinaryContract = BinaryContract & DPM
export type CPMMBinaryContract = BinaryContract & CPMM export type CPMMBinaryContract = BinaryContract & CPMM
export type AnswerContract =
| FreeResponseContract
| MultipleChoiceContract
| BountyContract
export type DPM = { export type DPM = {
mechanism: 'dpm-2' mechanism: 'dpm-2'

View File

@ -15,6 +15,7 @@ import {
getCpmmProbability, getCpmmProbability,
} from './calculate-cpmm' } from './calculate-cpmm'
import { import {
AnswerContract,
CPMMBinaryContract, CPMMBinaryContract,
DPMBinaryContract, DPMBinaryContract,
FreeResponseContract, FreeResponseContract,
@ -323,7 +324,7 @@ export const getNewBinaryDpmBetInfo = (
export const getNewMultiBetInfo = ( export const getNewMultiBetInfo = (
outcome: string, outcome: string,
amount: number, amount: number,
contract: FreeResponseContract | MultipleChoiceContract, contract: AnswerContract,
loanAmount: number loanAmount: number
) => { ) => {
const { pool, totalShares, totalBets } = contract const { pool, totalShares, totalBets } = contract

View File

@ -3,6 +3,7 @@ import { sum, groupBy, sumBy, mapValues } from 'lodash'
import { Bet, NumericBet } from './bet' import { Bet, NumericBet } from './bet'
import { deductDpmFees, getDpmProbability } from './calculate-dpm' import { deductDpmFees, getDpmProbability } from './calculate-dpm'
import { import {
AnswerContract,
DPMContract, DPMContract,
FreeResponseContract, FreeResponseContract,
MultipleChoiceContract, MultipleChoiceContract,
@ -184,7 +185,7 @@ export const getDpmMktPayouts = (
export const getPayoutsMultiOutcome = ( export const getPayoutsMultiOutcome = (
resolutions: { [outcome: string]: number }, resolutions: { [outcome: string]: number },
contract: FreeResponseContract | MultipleChoiceContract, contract: AnswerContract,
bets: Bet[] bets: Bet[]
) => { ) => {
const poolTotal = sum(Object.values(contract.pool)) const poolTotal = sum(Object.values(contract.pool))

View File

@ -36,7 +36,10 @@ export const createanswer = newEndpoint(opts, async (req, auth) => {
if (!contractSnap.exists) throw new APIError(400, 'Invalid contract') if (!contractSnap.exists) throw new APIError(400, 'Invalid contract')
const contract = contractSnap.data() as Contract const contract = contractSnap.data() as Contract
if (contract.outcomeType !== 'FREE_RESPONSE') if (
contract.outcomeType !== 'FREE_RESPONSE' &&
contract.outcomeType !== 'BOUNTY'
)
throw new APIError(400, 'Requires a free response contract') throw new APIError(400, 'Requires a free response contract')
const { closeTime, volume } = contract const { closeTime, volume } = contract

View File

@ -3,6 +3,7 @@ import { z } from 'zod'
import { difference, uniq, mapValues, groupBy, sumBy } from 'lodash' import { difference, uniq, mapValues, groupBy, sumBy } from 'lodash'
import { import {
AnswerContract,
Contract, Contract,
FreeResponseContract, FreeResponseContract,
MultipleChoiceContract, MultipleChoiceContract,
@ -295,10 +296,7 @@ function getResolutionParams(contract: Contract, body: string) {
throw new APIError(500, `Invalid outcome type: ${outcomeType}`) throw new APIError(500, `Invalid outcome type: ${outcomeType}`)
} }
function validateAnswer( function validateAnswer(contract: AnswerContract, answer: number) {
contract: FreeResponseContract | MultipleChoiceContract,
answer: number
) {
const validIds = contract.answers.map((a) => a.id) const validIds = contract.answers.map((a) => a.id)
if (!validIds.includes(answer.toString())) { if (!validIds.includes(answer.toString())) {
throw new APIError(400, `${answer} is not a valid answer ID`) throw new APIError(400, `${answer} is not a valid answer ID`)

View File

@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from 'react'
import { XIcon } from '@heroicons/react/solid' import { XIcon } from '@heroicons/react/solid'
import { Answer } from 'common/answer' import { Answer } from 'common/answer'
import { FreeResponseContract, MultipleChoiceContract } from 'common/contract' import { AnswerContract } from 'common/contract'
import { BuyAmountInput } from '../amount-input' import { BuyAmountInput } from '../amount-input'
import { Col } from '../layout/col' import { Col } from '../layout/col'
import { APIError, placeBet } from 'web/lib/firebase/api' import { APIError, placeBet } from 'web/lib/firebase/api'
@ -30,7 +30,7 @@ import { AlertBox } from '../alert-box'
export function AnswerBetPanel(props: { export function AnswerBetPanel(props: {
answer: Answer answer: Answer
contract: FreeResponseContract | MultipleChoiceContract contract: AnswerContract
closePanel: () => void closePanel: () => void
className?: string className?: string
isModal?: boolean isModal?: boolean

View File

@ -1,7 +1,7 @@
import clsx from 'clsx' import clsx from 'clsx'
import { Answer } from 'common/answer' import { Answer } from 'common/answer'
import { FreeResponseContract, MultipleChoiceContract } from 'common/contract' import { AnswerContract } from 'common/contract'
import { Col } from '../layout/col' import { Col } from '../layout/col'
import { Row } from '../layout/row' import { Row } from '../layout/row'
import { Avatar } from '../avatar' import { Avatar } from '../avatar'
@ -13,7 +13,7 @@ import { Linkify } from '../linkify'
export function AnswerItem(props: { export function AnswerItem(props: {
answer: Answer answer: Answer
contract: FreeResponseContract | MultipleChoiceContract contract: AnswerContract
showChoice: 'radio' | 'checkbox' | undefined showChoice: 'radio' | 'checkbox' | undefined
chosenProb: number | undefined chosenProb: number | undefined
totalChosenProb?: number totalChosenProb?: number

View File

@ -2,7 +2,7 @@ import clsx from 'clsx'
import { sum } from 'lodash' import { sum } from 'lodash'
import { useState } from 'react' import { useState } from 'react'
import { FreeResponseContract, MultipleChoiceContract } from 'common/contract' import { AnswerContract } from 'common/contract'
import { Col } from '../layout/col' import { Col } from '../layout/col'
import { APIError, resolveMarket } from 'web/lib/firebase/api' import { APIError, resolveMarket } from 'web/lib/firebase/api'
import { Row } from '../layout/row' import { Row } from '../layout/row'
@ -11,7 +11,7 @@ import { ResolveConfirmationButton } from '../confirmation-button'
import { removeUndefinedProps } from 'common/util/object' import { removeUndefinedProps } from 'common/util/object'
export function AnswerResolvePanel(props: { export function AnswerResolvePanel(props: {
contract: FreeResponseContract | MultipleChoiceContract contract: AnswerContract
resolveOption: 'CHOOSE' | 'CHOOSE_MULTIPLE' | 'CANCEL' | undefined resolveOption: 'CHOOSE' | 'CHOOSE_MULTIPLE' | 'CANCEL' | undefined
setResolveOption: ( setResolveOption: (
option: 'CHOOSE' | 'CHOOSE_MULTIPLE' | 'CANCEL' | undefined option: 'CHOOSE' | 'CHOOSE_MULTIPLE' | 'CANCEL' | undefined

View File

@ -5,14 +5,18 @@ import { groupBy, sortBy, sumBy } from 'lodash'
import { memo } from 'react' import { memo } from 'react'
import { Bet } from 'common/bet' import { Bet } from 'common/bet'
import { FreeResponseContract, MultipleChoiceContract } from 'common/contract' import {
AnswerContract,
FreeResponseContract,
MultipleChoiceContract,
} from 'common/contract'
import { getOutcomeProbability } from 'common/calculate' import { getOutcomeProbability } from 'common/calculate'
import { useWindowSize } from 'web/hooks/use-window-size' import { useWindowSize } from 'web/hooks/use-window-size'
const NUM_LINES = 6 const NUM_LINES = 6
export const AnswersGraph = memo(function AnswersGraph(props: { export const AnswersGraph = memo(function AnswersGraph(props: {
contract: FreeResponseContract | MultipleChoiceContract contract: AnswerContract
bets: Bet[] bets: Bet[]
height?: number height?: number
}) { }) {
@ -178,10 +182,7 @@ function formatTime(
return d.format(format) return d.format(format)
} }
const computeProbsByOutcome = ( const computeProbsByOutcome = (bets: Bet[], contract: AnswerContract) => {
bets: Bet[],
contract: FreeResponseContract | MultipleChoiceContract
) => {
const { totalBets, outcomeType } = contract const { totalBets, outcomeType } = contract
const betsByOutcome = groupBy(bets, (bet) => bet.outcome) const betsByOutcome = groupBy(bets, (bet) => bet.outcome)

View File

@ -1,7 +1,12 @@
import { sortBy, partition, sum, uniq } from 'lodash' import { sortBy, partition, sum, uniq } from 'lodash'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { FreeResponseContract, MultipleChoiceContract } from 'common/contract' import {
AnswerContract,
BountyContract,
FreeResponseContract,
MultipleChoiceContract,
} from 'common/contract'
import { Col } from '../layout/col' import { Col } from '../layout/col'
import { useUser } from 'web/hooks/use-user' import { useUser } from 'web/hooks/use-user'
import { getDpmOutcomeProbability } from 'common/calculate-dpm' import { getDpmOutcomeProbability } from 'common/calculate-dpm'
@ -25,9 +30,7 @@ import { UserLink } from 'web/components/user-page'
import { Linkify } from 'web/components/linkify' import { Linkify } from 'web/components/linkify'
import { BuyButton } from 'web/components/yes-no-selector' import { BuyButton } from 'web/components/yes-no-selector'
export function AnswersPanel(props: { export function AnswersPanel(props: { contract: AnswerContract }) {
contract: FreeResponseContract | MultipleChoiceContract
}) {
const { contract } = props const { contract } = props
const { creatorId, resolution, resolutions, totalBets, outcomeType } = const { creatorId, resolution, resolutions, totalBets, outcomeType } =
contract contract
@ -136,7 +139,7 @@ export function AnswersPanel(props: {
<div className="pb-4 text-gray-500">No answers yet...</div> <div className="pb-4 text-gray-500">No answers yet...</div>
)} )}
{outcomeType === 'FREE_RESPONSE' && {(outcomeType === 'FREE_RESPONSE' || outcomeType === 'BOUNTY') &&
tradingAllowed(contract) && tradingAllowed(contract) &&
(!resolveOption || resolveOption === 'CANCEL') && ( (!resolveOption || resolveOption === 'CANCEL') && (
<CreateAnswerPanel contract={contract} /> <CreateAnswerPanel contract={contract} />
@ -158,7 +161,7 @@ export function AnswersPanel(props: {
} }
function getAnswerItems( function getAnswerItems(
contract: FreeResponseContract | MultipleChoiceContract, contract: AnswerContract,
answers: Answer[], answers: Answer[],
user: User | undefined | null user: User | undefined | null
) { ) {
@ -184,7 +187,7 @@ function getAnswerItems(
} }
function OpenAnswer(props: { function OpenAnswer(props: {
contract: FreeResponseContract | MultipleChoiceContract contract: AnswerContract
answer: Answer answer: Answer
items: ActivityItem[] items: ActivityItem[]
type: string type: string

View File

@ -3,7 +3,7 @@ import { useState } from 'react'
import Textarea from 'react-expanding-textarea' import Textarea from 'react-expanding-textarea'
import { findBestMatch } from 'string-similarity' import { findBestMatch } from 'string-similarity'
import { FreeResponseContract } from 'common/contract' import { BountyContract, FreeResponseContract } from 'common/contract'
import { BuyAmountInput } from '../amount-input' import { BuyAmountInput } from '../amount-input'
import { Col } from '../layout/col' import { Col } from '../layout/col'
import { APIError, createAnswer } from 'web/lib/firebase/api' import { APIError, createAnswer } from 'web/lib/firebase/api'
@ -26,7 +26,9 @@ import { MAX_ANSWER_LENGTH } from 'common/answer'
import { withTracking } from 'web/lib/service/analytics' import { withTracking } from 'web/lib/service/analytics'
import { lowerCase } from 'lodash' import { lowerCase } from 'lodash'
export function CreateAnswerPanel(props: { contract: FreeResponseContract }) { export function CreateAnswerPanel(props: {
contract: FreeResponseContract | BountyContract
}) {
const { contract } = props const { contract } = props
const user = useUser() const user = useUser()
const [text, setText] = useState('') const [text, setText] = useState('')

View File

@ -9,6 +9,7 @@ import {
import { contractPath, getBinaryProbPercent } from 'web/lib/firebase/contracts' import { contractPath, getBinaryProbPercent } from 'web/lib/firebase/contracts'
import { Col } from '../layout/col' import { Col } from '../layout/col'
import { import {
AnswerContract,
BinaryContract, BinaryContract,
BountyContract, BountyContract,
Contract, Contract,
@ -232,7 +233,7 @@ export function BountyValue(props: {
} }
function FreeResponseTopAnswer(props: { function FreeResponseTopAnswer(props: {
contract: FreeResponseContract | MultipleChoiceContract contract: AnswerContract
truncate: 'short' | 'long' | 'none' truncate: 'short' | 'long' | 'none'
className?: string className?: string
}) { }) {

View File

@ -56,7 +56,8 @@ export function ContractTabs(props: {
tips={tips} tips={tips}
user={user} user={user}
mode={ mode={
contract.outcomeType === 'FREE_RESPONSE' contract.outcomeType === 'FREE_RESPONSE' ||
contract.outcomeType === 'BOUNTY'
? 'free-response-comment-answer-groups' ? 'free-response-comment-answer-groups'
: 'comments' : 'comments'
} }

View File

@ -3,6 +3,7 @@ import { Answer } from 'common/answer'
import { getProbability } from 'common/calculate' import { getProbability } from 'common/calculate'
import { getValueFromBucket } from 'common/calculate-dpm' import { getValueFromBucket } from 'common/calculate-dpm'
import { import {
AnswerContract,
BinaryContract, BinaryContract,
BountyContract, BountyContract,
Contract, Contract,
@ -78,7 +79,7 @@ export function BinaryContractOutcomeLabel(props: {
} }
export function FreeResponseOutcomeLabel(props: { export function FreeResponseOutcomeLabel(props: {
contract: FreeResponseContract | MultipleChoiceContract | BountyContract contract: AnswerContract
resolution: string | 'CANCEL' | 'MKT' resolution: string | 'CANCEL' | 'MKT'
truncate: 'short' | 'long' | 'none' truncate: 'short' | 'long' | 'none'
answerClassName?: string answerClassName?: string