Compute and store nextLoanCached for all users
This commit is contained in:
parent
599a68d93d
commit
565b6d3a87
|
@ -7,6 +7,7 @@ import {
|
|||
FreeResponseContract,
|
||||
MultipleChoiceContract,
|
||||
} from './contract'
|
||||
import { PortfolioMetrics, User } from './user'
|
||||
import { filterDefined } from './util/array'
|
||||
|
||||
const LOAN_WEEKLY_RATE = 0.05
|
||||
|
@ -16,7 +17,50 @@ const calculateNewLoan = (investedValue: number, loanTotal: number) => {
|
|||
return netValue * LOAN_WEEKLY_RATE
|
||||
}
|
||||
|
||||
export const getUserLoanUpdates = (
|
||||
export const getLoanUpdates = (
|
||||
users: User[],
|
||||
contractsById: { [contractId: string]: Contract },
|
||||
portfolioByUser: { [userId: string]: PortfolioMetrics | undefined },
|
||||
betsByUser: { [userId: string]: Bet[] }
|
||||
) => {
|
||||
const eligibleUsers = filterDefined(
|
||||
users.map((user) =>
|
||||
isUserEligibleForLoan(portfolioByUser[user.id]) ? user : undefined
|
||||
)
|
||||
)
|
||||
|
||||
const betUpdates = eligibleUsers
|
||||
.map((user) => {
|
||||
const updates = calculateLoanBetUpdates(
|
||||
betsByUser[user.id] ?? [],
|
||||
contractsById
|
||||
).betUpdates
|
||||
return updates.map((update) => ({ ...update, user }))
|
||||
})
|
||||
.flat()
|
||||
|
||||
const updatesByUser = groupBy(betUpdates, (update) => update.userId)
|
||||
const userPayouts = Object.values(updatesByUser).map((updates) => {
|
||||
return {
|
||||
user: updates[0].user,
|
||||
payout: sumBy(updates, (update) => update.newLoan),
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
betUpdates,
|
||||
userPayouts,
|
||||
}
|
||||
}
|
||||
|
||||
const isUserEligibleForLoan = (portfolio: PortfolioMetrics | undefined) => {
|
||||
if (!portfolio) return true
|
||||
|
||||
const { balance, investmentValue } = portfolio
|
||||
return balance + investmentValue > 0
|
||||
}
|
||||
|
||||
const calculateLoanBetUpdates = (
|
||||
bets: Bet[],
|
||||
contractsById: Dictionary<Contract>
|
||||
) => {
|
||||
|
|
|
@ -32,6 +32,7 @@ export type User = {
|
|||
allTime: number
|
||||
}
|
||||
|
||||
nextLoanCached: number
|
||||
followerCountCached: number
|
||||
|
||||
followedCategories?: string[]
|
||||
|
|
|
@ -75,6 +75,7 @@ export const createuser = newEndpoint(opts, async (req, auth) => {
|
|||
createdTime: Date.now(),
|
||||
profitCached: { daily: 0, weekly: 0, monthly: 0, allTime: 0 },
|
||||
creatorVolumeCached: { daily: 0, weekly: 0, monthly: 0, allTime: 0 },
|
||||
nextLoanCached: 0,
|
||||
followerCountCached: 0,
|
||||
followedCategories: DEFAULT_CATEGORIES,
|
||||
shouldShowWelcome: true,
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import * as functions from 'firebase-functions'
|
||||
import * as admin from 'firebase-admin'
|
||||
import { groupBy, keyBy, sumBy } from 'lodash'
|
||||
import { groupBy, keyBy } from 'lodash'
|
||||
import { getValues, log, payUser, writeAsync } from './utils'
|
||||
import { Bet } from '../../common/bet'
|
||||
import { Contract } from '../../common/contract'
|
||||
import { PortfolioMetrics, User } from '../../common/user'
|
||||
import { filterDefined } from '../../common/util/array'
|
||||
import { getUserLoanUpdates } from '../../common/loans'
|
||||
import { getLoanUpdates } from '../../common/loans'
|
||||
import { createLoanIncomeNotification } from './create-notification'
|
||||
|
||||
const firestore = admin.firestore()
|
||||
|
@ -31,31 +30,34 @@ async function updateLoansCore() {
|
|||
log(
|
||||
`Loaded ${users.length} users, ${contracts.length} contracts, and ${bets.length} bets.`
|
||||
)
|
||||
|
||||
const eligibleUsers = filterDefined(
|
||||
await Promise.all(
|
||||
users.map((user) =>
|
||||
isUserEligibleForLoan(user).then((isEligible) =>
|
||||
isEligible ? user : undefined
|
||||
)
|
||||
const userPortfolios = await Promise.all(
|
||||
users.map(async (user) => {
|
||||
const portfolio = await getValues<PortfolioMetrics>(
|
||||
firestore
|
||||
.collection(`users/${user.id}/portfolioHistory`)
|
||||
.orderBy('timestamp', 'desc')
|
||||
.limit(1)
|
||||
)
|
||||
)
|
||||
return portfolio[0]
|
||||
})
|
||||
)
|
||||
log(`${eligibleUsers.length} users are eligible for loans.`)
|
||||
log(`Loaded ${userPortfolios.length} portfolios`)
|
||||
const portfolioByUser = keyBy(userPortfolios, (portfolio) => portfolio.userId)
|
||||
|
||||
const contractsById = keyBy(contracts, (contract) => contract.id)
|
||||
const contractsById = Object.fromEntries(
|
||||
contracts.map((contract) => [contract.id, contract])
|
||||
)
|
||||
const betsByUser = groupBy(bets, (bet) => bet.userId)
|
||||
const { betUpdates, userPayouts } = getLoanUpdates(
|
||||
users,
|
||||
contractsById,
|
||||
portfolioByUser,
|
||||
betsByUser
|
||||
)
|
||||
|
||||
const userLoanUpdates = eligibleUsers
|
||||
.map(
|
||||
(user) =>
|
||||
getUserLoanUpdates(betsByUser[user.id] ?? [], contractsById).betUpdates
|
||||
)
|
||||
.flat()
|
||||
log(`${betUpdates.length} bet updates.`)
|
||||
|
||||
log(`${userLoanUpdates.length} bet updates.`)
|
||||
|
||||
const betUpdates = userLoanUpdates.map((update) => ({
|
||||
const betDocUpdates = betUpdates.map((update) => ({
|
||||
doc: firestore
|
||||
.collection('contracts')
|
||||
.doc(update.contractId)
|
||||
|
@ -66,17 +68,7 @@ async function updateLoansCore() {
|
|||
},
|
||||
}))
|
||||
|
||||
await writeAsync(firestore, betUpdates)
|
||||
|
||||
const userPayouts = eligibleUsers.map((user) => {
|
||||
const updates = userLoanUpdates.filter(
|
||||
(update) => update.userId === user.id
|
||||
)
|
||||
return {
|
||||
user,
|
||||
payout: sumBy(updates, (update) => update.newLoan),
|
||||
}
|
||||
})
|
||||
await writeAsync(firestore, betDocUpdates)
|
||||
|
||||
log(`${userPayouts.length} user payouts`)
|
||||
|
||||
|
@ -94,16 +86,3 @@ async function updateLoansCore() {
|
|||
|
||||
log('Notifications sent!')
|
||||
}
|
||||
|
||||
const isUserEligibleForLoan = async (user: User) => {
|
||||
const [portfolio] = await getValues<PortfolioMetrics>(
|
||||
firestore
|
||||
.collection(`users/${user.id}/portfolioHistory`)
|
||||
.orderBy('timestamp', 'desc')
|
||||
.limit(1)
|
||||
)
|
||||
if (!portfolio) return true
|
||||
|
||||
const { balance, investmentValue } = portfolio
|
||||
return balance + investmentValue > 0
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as functions from 'firebase-functions'
|
||||
import * as admin from 'firebase-admin'
|
||||
import { groupBy, isEmpty, sum, sumBy } from 'lodash'
|
||||
import { groupBy, isEmpty, keyBy, mapValues, maxBy, sum, sumBy } from 'lodash'
|
||||
import { getValues, log, logMemory, writeAsync } from './utils'
|
||||
import { Bet } from '../../common/bet'
|
||||
import { Contract } from '../../common/contract'
|
||||
|
@ -8,6 +8,7 @@ import { PortfolioMetrics, User } from '../../common/user'
|
|||
import { calculatePayout } from '../../common/calculate'
|
||||
import { DAY_MS } from '../../common/util/time'
|
||||
import { last } from 'lodash'
|
||||
import { getLoanUpdates } from '../../common/loans'
|
||||
|
||||
const firestore = admin.firestore()
|
||||
|
||||
|
@ -71,6 +72,18 @@ export const updateMetricsCore = async () => {
|
|||
const contractsByUser = groupBy(contracts, (contract) => contract.creatorId)
|
||||
const betsByUser = groupBy(bets, (bet) => bet.userId)
|
||||
const portfolioHistoryByUser = groupBy(allPortfolioHistories, (p) => p.userId)
|
||||
|
||||
const portfolioByUser = mapValues(portfolioHistoryByUser, (history) =>
|
||||
maxBy(history, (portfolio) => portfolio.timestamp)
|
||||
)
|
||||
const { userPayouts } = getLoanUpdates(
|
||||
users,
|
||||
contractsById,
|
||||
portfolioByUser,
|
||||
betsByUser
|
||||
)
|
||||
const nextLoanByUser = keyBy(userPayouts, (payout) => payout.user.id)
|
||||
|
||||
const userUpdates = users.map((user) => {
|
||||
const currentBets = betsByUser[user.id] ?? []
|
||||
const portfolioHistory = portfolioHistoryByUser[user.id] ?? []
|
||||
|
@ -93,6 +106,7 @@ export const updateMetricsCore = async () => {
|
|||
newPortfolio,
|
||||
didProfitChange
|
||||
)
|
||||
const nextLoanCached = nextLoanByUser[user.id]?.payout ?? 0
|
||||
|
||||
return {
|
||||
fieldUpdates: {
|
||||
|
@ -102,6 +116,7 @@ export const updateMetricsCore = async () => {
|
|||
...(didProfitChange && {
|
||||
profitCached: newProfit,
|
||||
}),
|
||||
nextLoanCached,
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
@ -181,7 +181,9 @@ export function UserPage(props: { user: User }) {
|
|||
}
|
||||
onClick={() => setShowLoansModal(true)}
|
||||
>
|
||||
<span className="text-green-600">🏦 {formatMoney(153)}</span>
|
||||
<span className="text-green-600">
|
||||
🏦 {formatMoney(user.nextLoanCached ?? 0)}
|
||||
</span>
|
||||
<span>next loan</span>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
Loading…
Reference in New Issue
Block a user