Files
backend/prisma/schema.prisma
T
codex-bot fee61537b6
Backend verify / commits (push) Skipped
Backend verify / security (push) Successful in 1m7s
Backend verify / verify (push) Failing after 7m19s
feat(performance): investment performance vs Moscow Exchange index (WealthLedger/main#54) (#200)
2026-11-08 16:45:29 +03:00

367 lines
10 KiB
Plaintext

generator client {
provider = "prisma-client"
output = "../generated/prisma"
moduleFormat = "cjs"
}
datasource db {
provider = "sqlite"
}
// Public MOEX index closes contain no user holdings or credentials.
model MarketIndexDailyClose {
secid String
tradeDate String
close String
createdAt DateTime @default(now())
@@id([secid, tradeDate])
}
model MarketIndexSyncState {
secid String @id
lastAttemptAt DateTime?
lastSuccessAt DateTime?
coveredFrom String?
coveredTo String?
}
model User {
id String @id
email String @unique
passwordHash String
bankCards BankCard[]
expenseCategories ExpenseCategory[]
bankCardOperations BankCardOperation[]
sessions Session[]
tbankConnection TbankConnection?
tbankValueHistory TbankPortfolioValueHistory[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model TbankConnection {
userId String @unique
encryptedToken String
connectedAt DateTime
revision String @default(uuid())
invalidToken Boolean @default(false)
membershipAt DateTime?
membershipRevision Int @default(0)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
accounts TbankAccount[]
forecasts TbankForecast[]
work TbankSyncWork[]
@@unique([userId, revision])
}
model TbankAccount {
userId String
revision String
accountId String
type String
name String
openedOn String?
upstreamOrder Int
operationsRevision Int @default(0)
historyFrom DateTime?
historyBoundary String @default("unknown")
connection TbankConnection @relation(fields: [userId, revision], references: [userId, revision], onDelete: Cascade, onUpdate: Restrict)
portfolio TbankPortfolio?
operations TbankOperation[]
coverage TbankOperationCoverage[]
@@id([userId, revision, accountId])
}
model TbankPortfolio {
userId String
revision String
accountId String
payload Json
mappingVersion Int @default(1)
fetchedAt DateTime
dataRevision Int @default(1)
account TbankAccount @relation(fields: [userId, revision, accountId], references: [userId, revision, accountId], onDelete: Cascade, onUpdate: Restrict)
@@id([userId, revision, accountId])
}
// Account valuation history survives connection replacement and stays hidden
// until the current connection confirms the account again.
model TbankPortfolioValueHistory {
userId String
accountId String
deletedAt DateTime?
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
points TbankPortfolioValuePoint[]
@@id([userId, accountId])
}
// One row per currency of a fully valued successful portfolio attempt.
model TbankPortfolioValuePoint {
userId String
accountId String
attemptId String
currency String
value String
capturedAt DateTime
createdAt DateTime @default(now())
history TbankPortfolioValueHistory @relation(fields: [userId, accountId], references: [userId, accountId], onDelete: Cascade, onUpdate: Restrict)
@@id([userId, accountId, attemptId, currency])
@@index([userId, accountId, capturedAt])
}
model TbankOperation {
userId String
revision String
accountId String
brokerOperationId String
occurredAt DateTime
instrumentUid String?
category String
payload Json
mappingVersion Int @default(1)
account TbankAccount @relation(fields: [userId, revision, accountId], references: [userId, revision, accountId], onDelete: Cascade, onUpdate: Restrict)
@@id([userId, revision, accountId, brokerOperationId])
@@index([userId, revision, accountId, occurredAt, brokerOperationId])
@@index([userId, revision, accountId, instrumentUid, occurredAt])
@@index([userId, revision, accountId, category, occurredAt])
}
model TbankOperationCoverage {
userId String
revision String
accountId String
instrumentPredicate String @default("")
from DateTime
to DateTime
fetchedAt DateTime
asOf DateTime
account TbankAccount @relation(fields: [userId, revision, accountId], references: [userId, revision, accountId], onDelete: Cascade, onUpdate: Restrict)
@@id([userId, revision, accountId, instrumentPredicate, from, to])
}
model TbankForecast {
userId String
revision String
scopeKey String
membershipRevision Int
payload Json
dependencyRevisions Json
sourceTimestamps Json
calculatedAt DateTime
dataRevision Int @default(1)
connection TbankConnection @relation(fields: [userId, revision], references: [userId, revision], onDelete: Cascade, onUpdate: Restrict)
@@id([userId, revision, scopeKey])
}
model TbankInstrument {
provider String
uid String
figi String?
ticker String?
instrumentType String
metadata Json
fetchedAt DateTime
dataRevision Int @default(1)
sources TbankInstrumentSource[]
candles TbankCandle[]
candleCoverage TbankCandleCoverage[]
@@id([provider, uid])
@@index([provider, figi])
@@index([provider, ticker, instrumentType])
}
model TbankCatalogDiscovery {
provider String
instrumentType String
query String
instrumentUids Json
fetchedAt DateTime
dataRevision Int @default(1)
@@id([provider, instrumentType, query])
}
// Empty and ambiguous successful UID/FIGI/ticker lookups are distinct from failures.
model TbankInstrumentLookup {
provider String
identifierType String
identifier String
matchedUids Json
fetchedAt DateTime
dataRevision Int @default(1)
@@id([provider, identifierType, identifier])
}
// Public per-instrument classification; no holdings or credentials.
model TbankInstrumentClassification {
provider String
instrumentKey String
sector String?
sectorFetchedAt DateTime?
assetUid String?
brandUid String?
brandName String?
brandFetchedAt DateTime?
@@id([provider, instrumentKey])
}
// Source records contain no holdings, personal estimates or credentials errors.
model TbankInstrumentSource {
provider String
instrumentUid String
source String
kind String
rangeKey String
payload Json
fetchedAt DateTime
dataRevision Int @default(1)
instrument TbankInstrument @relation(fields: [provider, instrumentUid], references: [provider, uid], onDelete: Cascade)
@@id([provider, instrumentUid, source, kind, rangeKey])
}
model TbankCandle {
provider String
instrumentUid String
interval String
time DateTime
close String
payload Json
isComplete Boolean
fetchedAt DateTime
instrument TbankInstrument @relation(fields: [provider, instrumentUid], references: [provider, uid], onDelete: Cascade)
@@id([provider, instrumentUid, interval, time])
}
model TbankCandleCoverage {
provider String
instrumentUid String
interval String
from DateTime
to DateTime
isComplete Boolean
fetchedAt DateTime
dataRevision Int @default(1)
instrument TbankInstrument @relation(fields: [provider, instrumentUid], references: [provider, uid], onDelete: Cascade)
@@id([provider, instrumentUid, interval, from, to])
}
model TbankSyncWork {
id String @id
userId String
revision String
scopeKey String
intent Json
state String
attemptId String
processRunId String
priority Int
progress Json
errorCode String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
connection TbankConnection @relation(fields: [userId, revision], references: [userId, revision], onDelete: Cascade, onUpdate: Restrict)
staged TbankSyncStage[]
@@unique([userId, revision, scopeKey])
@@index([userId, revision, state, priority])
@@unique([id, attemptId])
}
model TbankSyncStage {
workId String
attemptId String
sourceKey String
itemKey String
payload Json
work TbankSyncWork @relation(fields: [workId, attemptId], references: [id, attemptId], onDelete: Cascade, onUpdate: Restrict)
@@id([workId, attemptId, sourceKey, itemKey])
}
model Session {
id String @id
userId String
refreshTokenHash String
expiresAt DateTime
revokedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
}
model BankCard {
id String @id
userId String
name String
bankName String?
lastFour String?
paymentSystem String?
openingBalanceKopecks BigInt
openingDate String
version Int @default(1)
archivedAt DateTime?
createFingerprint String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Restrict)
operations BankCardOperation[] @relation("SourceCard")
incomingOperations BankCardOperation[] @relation("TargetCard")
@@index([userId])
}
model ExpenseCategory {
id String @id
userId String
name String
nameKey String
seedKey String?
version Int @default(1)
user User @relation(fields: [userId], references: [id], onDelete: Restrict)
operations BankCardOperation[]
@@unique([userId, nameKey])
@@unique([userId, seedKey])
}
model BankCardOperation {
id String @id
userId String
kind String
cardId String
targetCardId String?
categoryId String?
originalExpenseId String?
amountKopecks BigInt
date String
description String?
version Int @default(1)
deletedAt DateTime?
createFingerprint String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Restrict)
card BankCard @relation("SourceCard", fields: [cardId], references: [id], onDelete: Restrict)
targetCard BankCard? @relation("TargetCard", fields: [targetCardId], references: [id], onDelete: Restrict)
category ExpenseCategory? @relation(fields: [categoryId], references: [id], onDelete: Restrict)
originalExpense BankCardOperation? @relation("RefundExpense", fields: [originalExpenseId], references: [id], onDelete: Restrict, onUpdate: NoAction)
refunds BankCardOperation[] @relation("RefundExpense")
@@index([userId, cardId, date])
@@index([userId, targetCardId, date])
@@index([originalExpenseId])
}