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:
commit
02eaf93c02
14 changed files with 152 additions and 55 deletions
14
apps/client/.babelrc.json
Normal file
14
apps/client/.babelrc.json
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
"@babel/preset-typescript",
|
||||||
|
"@babel/preset-env",
|
||||||
|
[
|
||||||
|
"@nrwl/react/babel",
|
||||||
|
{
|
||||||
|
"runtime": "automatic",
|
||||||
|
"useBuiltIns": "usage"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"plugins": []
|
||||||
|
}
|
18
apps/client/.storybook/main.js
Normal file
18
apps/client/.storybook/main.js
Normal 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
|
||||||
|
},
|
||||||
|
}
|
5
apps/client/.storybook/tsconfig.json
Normal file
5
apps/client/.storybook/tsconfig.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {},
|
||||||
|
"include": ["**/*.ts", "**/*.tsx", "**/**/*.ts", "**/**/*.tsx"]
|
||||||
|
}
|
18
apps/client/components/Maintenance.stories.tsx
Normal file
18
apps/client/components/Maintenance.stories.tsx
Normal 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({})
|
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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'],
|
||||||
|
|
|
@ -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;
|
||||||
`
|
`
|
||||||
})
|
})
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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';
|
|
@ -243,18 +243,27 @@ 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)
|
||||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
|
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
|
||||||
name String?
|
name String?
|
||||||
symbol String?
|
symbol String?
|
||||||
cusip String?
|
cusip String?
|
||||||
isin String?
|
isin String?
|
||||||
sharesPerContract Decimal? @map("shares_per_contract") @db.Decimal(36, 18)
|
sharesPerContract Decimal? @map("shares_per_contract") @db.Decimal(36, 18)
|
||||||
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")
|
||||||
|
|
|
@ -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"]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue