1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-09 07:25:19 +02:00

Merge remote-tracking branch 'upstream/main' into dev-tools-fix

This commit is contained in:
Tyler Myracle 2024-01-21 15:36:50 -06:00
commit 02eaf93c02
14 changed files with 152 additions and 55 deletions

14
apps/client/.babelrc.json Normal file
View file

@ -0,0 +1,14 @@
{
"presets": [
"@babel/preset-typescript",
"@babel/preset-env",
[
"@nrwl/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}

View file

@ -0,0 +1,18 @@
const rootMain = require('../../../.storybook/main')
module.exports = {
...rootMain,
core: { ...rootMain.core, builder: 'webpack5' },
stories: ['../**/*.stories.@(js|jsx|ts|tsx)'],
addons: [...rootMain.addons, '@nrwl/react/plugins/storybook'],
webpackFinal: async (config, { configType }) => {
// apply any global webpack configs that might have been specified in .storybook/main.js
if (rootMain.webpackFinal) {
config = await rootMain.webpackFinal(config, { configType })
}
// add your own webpack tweaks if needed
return config
},
}

View file

@ -0,0 +1,5 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {},
"include": ["**/*.ts", "**/*.tsx", "**/**/*.ts", "**/**/*.tsx"]
}

View file

@ -0,0 +1,18 @@
import type { Story, Meta } from '@storybook/react'
import Maintenance from './Maintenance.tsx'
import React from 'react'
export default {
title: 'components/Maintenance.tsx',
component: Maintenance,
} as Meta
const Template: Story = () => {
return (
<>
<Maintenance />
</>
)
}
export const Base = Template.bind({})

View file

@ -27,5 +27,17 @@
"next-env.d.ts", "next-env.d.ts",
".next/types/**/*.ts" ".next/types/**/*.ts"
], ],
"exclude": ["node_modules", "jest.config.ts"] "exclude": [
"node_modules",
"jest.config.ts",
"**/*.stories.ts",
"**/*.stories.js",
"**/*.stories.jsx",
"**/*.stories.tsx"
],
"references": [
{
"path": "./.storybook/tsconfig.json"
}
]
} }

View file

@ -1,5 +1,5 @@
import type { User } from '@prisma/client' import type { User } from '@prisma/client'
import { InvestmentTransactionCategory, Prisma, PrismaClient } from '@prisma/client' import { AssetClass, InvestmentTransactionCategory, Prisma, PrismaClient } from '@prisma/client'
import { createLogger, transports } from 'winston' import { createLogger, transports } from 'winston'
import { DateTime } from 'luxon' import { DateTime } from 'luxon'
import type { import type {
@ -202,19 +202,25 @@ describe('insight service', () => {
holdings: { holdings: {
create: [ create: [
{ {
security: { create: { symbol: 'AAPL', plaidType: 'equity' } }, security: {
create: { symbol: 'AAPL', assetClass: AssetClass.stocks },
},
quantity: 1, quantity: 1,
costBasisUser: 100, costBasisUser: 100,
value: 200, value: 200,
}, },
{ {
security: { create: { symbol: 'NFLX', plaidType: 'equity' } }, security: {
create: { symbol: 'NFLX', assetClass: AssetClass.stocks },
},
quantity: 10, quantity: 10,
costBasisUser: 200, costBasisUser: 200,
value: 300, value: 300,
}, },
{ {
security: { create: { symbol: 'SHOP', plaidType: 'equity' } }, security: {
create: { symbol: 'SHOP', assetClass: AssetClass.stocks },
},
quantity: 2, quantity: 2,
costBasisUser: 100, costBasisUser: 100,
value: 50, value: 50,

View file

@ -641,14 +641,7 @@ export class InsightService implements IInsightService {
INNER JOIN ( INNER JOIN (
SELECT SELECT
id, id,
CASE asset_class
-- plaid
WHEN plaid_type IN ('equity', 'etf', 'mutual fund', 'derivative') THEN 'stocks'
WHEN plaid_type IN ('fixed income') THEN 'fixed_income'
WHEN plaid_type IN ('cash', 'loan') THEN 'cash'
WHEN plaid_type IN ('cryptocurrency') THEN 'crypto'
ELSE 'other'
END AS "asset_class"
FROM FROM
"security" "security"
) s ON s.id = h.security_id ) s ON s.id = h.security_id
@ -694,14 +687,7 @@ export class InsightService implements IInsightService {
INNER JOIN security s ON s.id = h.security_id INNER JOIN security s ON s.id = h.security_id
LEFT JOIN LATERAL ( LEFT JOIN LATERAL (
SELECT SELECT
CASE asset_class AS "category"
-- plaid
WHEN s.plaid_type IN ('equity', 'etf', 'mutual fund', 'derivative') THEN 'stocks'
WHEN s.plaid_type IN ('fixed income') THEN 'fixed_income'
WHEN s.plaid_type IN ('cash', 'loan') THEN 'cash'
WHEN s.plaid_type IN ('cryptocurrency') THEN 'crypto'
ELSE 'other'
END AS "category"
) x ON TRUE ) x ON TRUE
WHERE WHERE
h.account_id IN ${accountIds} h.account_id IN ${accountIds}
@ -828,28 +814,21 @@ export class InsightService implements IInsightService {
UNION ALL UNION ALL
-- investment accounts -- investment accounts
SELECT SELECT
s.asset_type, s.asset_class AS "asset_type",
SUM(h.value) AS "amount" SUM(h.value) AS "amount"
FROM FROM
holdings_enriched h holdings_enriched h
INNER JOIN ( INNER JOIN (
SELECT SELECT
id, id,
CASE asset_class
-- plaid
WHEN plaid_type IN ('equity', 'etf', 'mutual fund', 'derivative') THEN 'stocks'
WHEN plaid_type IN ('fixed income') THEN 'bonds'
WHEN plaid_type IN ('cash', 'loan') THEN 'cash'
WHEN plaid_type IN ('cryptocurrency') THEN 'crypto'
ELSE 'other'
END AS "asset_type"
FROM FROM
"security" "security"
) s ON s.id = h.security_id ) s ON s.id = h.security_id
WHERE WHERE
h.account_id IN ${pAccountIds} h.account_id IN ${pAccountIds}
GROUP BY GROUP BY
s.asset_type s.asset_class
) x ) x
GROUP BY GROUP BY
1 1

View file

@ -33,7 +33,7 @@ const PROJECTION_ASSET_PARAMS: {
[type in SharedType.ProjectionAssetType]: [mean: Decimal.Value, stddev: Decimal.Value] [type in SharedType.ProjectionAssetType]: [mean: Decimal.Value, stddev: Decimal.Value]
} = { } = {
stocks: ['0.05', '0.186'], stocks: ['0.05', '0.186'],
bonds: ['0.02', '0.052'], fixed_income: ['0.02', '0.052'],
cash: ['-0.02', '0.05'], cash: ['-0.02', '0.05'],
crypto: ['1.0', '1.0'], crypto: ['1.0', '1.0'],
property: ['0.1', '0.2'], property: ['0.1', '0.2'],

View file

@ -100,10 +100,7 @@ export class TellerETL implements IETL<Connection, TellerRawData, TellerData> {
const accounts = await this._extractAccounts(accessToken) const accounts = await this._extractAccounts(accessToken)
const transactions = await this._extractTransactions( const transactions = await this._extractTransactions(accessToken, accounts)
accessToken,
accounts.map((a) => a.id)
)
this.logger.info( this.logger.info(
`Extracted Teller data for customer ${user.tellerUserId} accounts=${accounts.length} transactions=${transactions.length}`, `Extracted Teller data for customer ${user.tellerUserId} accounts=${accounts.length} transactions=${transactions.length}`,
@ -196,26 +193,26 @@ export class TellerETL implements IETL<Connection, TellerRawData, TellerData> {
] ]
} }
private async _extractTransactions(accessToken: string, accountIds: string[]) { private async _extractTransactions(
accessToken: string,
tellerAccounts: TellerTypes.GetAccountsResponse
) {
const accountTransactions = await Promise.all( const accountTransactions = await Promise.all(
accountIds.map(async (accountId) => { tellerAccounts.map(async (tellerAccount) => {
const account = await this.prisma.account.findFirst({ const type = TellerUtil.getType(tellerAccount.type)
where: { const classification = AccountUtil.getClassification(type)
tellerAccountId: accountId,
},
})
const transactions = await SharedUtil.withRetry( const transactions = await SharedUtil.withRetry(
() => () =>
this.teller.getTransactions({ this.teller.getTransactions({
accountId, accountId: tellerAccount.id,
accessToken, accessToken,
}), }),
{ {
maxRetries: 3, maxRetries: 3,
} }
) )
if (account!.classification === AccountClassification.asset) { if (classification === AccountClassification.asset) {
transactions.forEach((t) => { transactions.forEach((t) => {
t.amount = String(Number(t.amount) * -1) t.amount = String(Number(t.amount) * -1)
}) })
@ -277,7 +274,7 @@ export class TellerETL implements IETL<Connection, TellerRawData, TellerData> {
pending = EXCLUDED.pending, pending = EXCLUDED.pending,
merchant_name = EXCLUDED.merchant_name, merchant_name = EXCLUDED.merchant_name,
teller_type = EXCLUDED.teller_type, teller_type = EXCLUDED.teller_type,
teller_category = EXCLUDED.teller_category; teller_category = EXCLUDED.teller_category,
category = EXCLUDED.category; category = EXCLUDED.category;
` `
}) })

View file

@ -56,7 +56,13 @@ export type PlanProjectionResponse = {
}[] }[]
} }
export type ProjectionAssetType = 'stocks' | 'bonds' | 'cash' | 'crypto' | 'property' | 'other' export type ProjectionAssetType =
| 'stocks'
| 'fixed_income'
| 'cash'
| 'crypto'
| 'property'
| 'other'
export type ProjectionLiabilityType = 'credit' | 'loan' | 'other' export type ProjectionLiabilityType = 'credit' | 'loan' | 'other'
export type PlanInsights = { export type PlanInsights = {

View file

@ -0,0 +1,6 @@
-- CreateEnum
CREATE TYPE "AssetClass" AS ENUM ('cash', 'crypto', 'fixed_income', 'stocks', 'other');
-- AlterTable
ALTER TABLE "security"
ADD COLUMN "asset_class" "AssetClass" NOT NULL DEFAULT 'other';

View file

@ -243,6 +243,14 @@ model InvestmentTransaction {
@@map("investment_transaction") @@map("investment_transaction")
} }
enum AssetClass {
cash
crypto
fixed_income
stocks
other
}
model Security { model Security {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6) createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
@ -255,6 +263,7 @@ model Security {
currencyCode String @default("USD") @map("currency_code") currencyCode String @default("USD") @map("currency_code")
pricingLastSyncedAt DateTime? @map("pricing_last_synced_at") @db.Timestamptz(6) pricingLastSyncedAt DateTime? @map("pricing_last_synced_at") @db.Timestamptz(6)
isBrokerageCash Boolean @default(false) @map("is_brokerage_cash") isBrokerageCash Boolean @default(false) @map("is_brokerage_cash")
assetClass AssetClass @default(other) @map("asset_class")
// plaid data // plaid data
plaidSecurityId String? @unique @map("plaid_security_id") plaidSecurityId String? @unique @map("plaid_security_id")

View file

@ -66,6 +66,33 @@
"options": { "options": {
"command": "node tools/scripts/triggerClientDeploy.js" "command": "node tools/scripts/triggerClientDeploy.js"
} }
},
"storybook": {
"executor": "@nrwl/storybook:storybook",
"options": {
"uiFramework": "@storybook/react",
"port": 4400,
"configDir": "apps/client/.storybook"
},
"configurations": {
"ci": {
"quiet": true
}
}
},
"build-storybook": {
"executor": "@nrwl/storybook:build",
"outputs": ["{options.outputDir}"],
"options": {
"uiFramework": "@storybook/react",
"outputDir": "dist/storybook/client",
"configDir": "apps/client/.storybook"
},
"configurations": {
"ci": {
"quiet": true
}
}
} }
}, },
"tags": ["scope:app"] "tags": ["scope:app"]