pwncash/lib/Internal/Budget.hs

652 lines
20 KiB
Haskell
Raw Normal View History

2023-05-29 15:56:15 -04:00
module Internal.Budget (insertBudget) where
2023-05-07 20:29:33 -04:00
import Control.Monad.Except
import Data.Foldable
2023-05-07 20:29:33 -04:00
import Database.Persist.Monad
2023-05-29 17:33:59 -04:00
import Internal.Database
2023-06-30 23:54:39 -04:00
import Internal.History
import Internal.Types.Main
import Internal.Utils
import RIO hiding (to)
2023-02-12 16:23:32 -05:00
import qualified RIO.List as L
import qualified RIO.Map as M
2023-02-12 21:52:41 -05:00
import qualified RIO.NonEmpty as NE
import qualified RIO.Text as T
import RIO.Time
2023-05-29 16:11:19 -04:00
-- each budget (designated at the top level by a 'name') is processed in the
-- following steps
-- 1. expand all transactions given the desired date range and date patterns for
-- each directive in the budget
-- 2. sort all transactions by date
-- 3. propagate all balances forward, and while doing so assign values to each
-- transaction (some of which depend on the 'current' balance of the
-- target account)
2023-05-29 16:36:59 -04:00
-- 4. assign shadow transactions
2023-05-29 16:11:19 -04:00
-- 5. insert all transactions
2023-05-13 13:53:43 -04:00
insertBudget
:: (MonadInsertError m, MonadSqlQuery m, MonadFinance m)
=> Budget
2023-05-16 23:12:29 -04:00
-> m ()
insertBudget
b@Budget
2023-04-30 23:28:16 -04:00
{ bgtLabel
, bgtIncomes
, bgtTransfers
, bgtShadowTransfers
, bgtPretax
, bgtTax
, bgtPosttax
2023-05-29 13:09:17 -04:00
, bgtInterval
} =
2023-05-16 23:12:29 -04:00
whenHash CTBudget b () $ \key -> do
2023-05-29 17:14:01 -04:00
(intAllos, _) <- combineError intAlloRes acntRes (,)
2023-05-29 13:09:17 -04:00
let res1 = mapErrors (insertIncome key bgtLabel intAllos bgtInterval) bgtIncomes
let res2 = expandTransfers key bgtLabel bgtInterval bgtTransfers
2023-05-07 20:29:33 -04:00
txs <- combineError (concat <$> res1) res2 (++)
2023-06-30 23:54:39 -04:00
shadow <- addShadowTransfers bgtShadowTransfers txs
(_, toIns) <- balanceTxs $ fmap ToInsert $ txs ++ shadow
void $ insertBudgetTx toIns
where
2023-05-29 17:14:01 -04:00
acntRes = mapErrors isNotIncomeAcnt alloAcnts
intAlloRes = combineError3 pre_ tax_ post_ (,,)
2023-05-07 20:29:33 -04:00
pre_ = sortAllos bgtPretax
tax_ = sortAllos bgtTax
post_ = sortAllos bgtPosttax
2023-05-29 17:14:01 -04:00
sortAllos = liftExcept . mapErrors sortAllo
alloAcnts =
(alloAcnt <$> bgtPretax)
++ (alloAcnt <$> bgtTax)
++ (alloAcnt <$> bgtPosttax)
-- TODO need to systematically make this function match the history version,
-- which will allow me to use the same balancing algorithm for both
2023-06-30 23:54:39 -04:00
-- balanceTransfers :: [Tx BudgetMeta] -> [KeyEntry]
-- balanceTransfers = undefined
-- balanceTransfers = snd . L.mapAccumR go M.empty . reverse . L.sortOn ftWhen
-- where
-- go bals f@FlatTransfer {ftFrom, ftTo, ftValue = UnbalancedValue {cvValue, cvType}} =
-- let balTo = M.findWithDefault 0 ftTo bals
-- x = amtToMove balTo cvType cvValue
-- bals' = mapAdd_ ftTo x $ mapAdd_ ftFrom (-x) bals
-- in (bals', f {ftValue = x})
-- -- TODO might need to query signs to make this intuitive; as it is this will
-- -- probably work, but for credit accounts I might need to supply a negative
-- -- target value
-- amtToMove _ TFixed x = x
-- amtToMove bal TPercent x = -(x / 100 * bal)
-- amtToMove bal TBalance x = x - bal
2023-05-29 15:56:15 -04:00
insertBudgetTx
:: (MonadInsertError m, MonadSqlQuery m, MonadFinance m)
2023-06-30 23:54:39 -04:00
=> [InsertTx BudgetMeta]
2023-05-29 15:56:15 -04:00
-> m ()
2023-06-30 23:54:39 -04:00
insertBudgetTx toInsert = do
forM_ (groupKey (commitRHash . bmCommit) $ (\x -> (itxCommit x, x)) <$> toInsert) $
\(c, ts) -> do
ck <- insert $ bmCommit c
mapM_ (insertTx ck) ts
2023-05-29 15:56:15 -04:00
where
2023-06-30 23:54:39 -04:00
insertTx c InsertTx {itxDate = d, itxDescr = e, itxEntries = ss, itxCommit = BudgetMeta {bmName}} = do
let anyDeferred = any (isJust . feDeferred) ss
k <- insert $ TransactionR c d e anyDeferred
mapM_ (insertBudgetLabel bmName k) ss
insertBudgetLabel n k entry = do
2023-05-29 16:11:19 -04:00
sk <- insertEntry k entry
2023-06-30 23:54:39 -04:00
insert_ $ BudgetLabelR sk n
-- insertBudgetTx
-- :: (MonadInsertError m, MonadSqlQuery m, MonadFinance m)
-- => BalancedTransfer
-- -> m ()
-- insertBudgetTx FlatTransfer {ftFrom, ftTo, ftMeta, ftCur, ftValue, ftDesc, ftWhen} = do
-- ((sFrom, sTo), exchange) <- entryPair ftFrom ftTo ftCur ftValue
-- insertPair sFrom sTo
-- forM_ exchange $ uncurry insertPair
-- where
-- insertPair from to = do
-- k <- insert $ TransactionR (bmCommit ftMeta) ftWhen ftDesc
-- insertBudgetLabel k from
-- insertBudgetLabel k to
-- insertBudgetLabel k entry = do
-- sk <- insertEntry k entry
-- insert_ $ BudgetLabelR sk $ bmName ftMeta
2023-05-29 15:56:15 -04:00
entryPair
:: (MonadInsertError m, MonadFinance m)
=> TaggedAcnt
-> TaggedAcnt
2023-06-30 23:54:39 -04:00
-> CurID
-> T.Text
-> Double
-> m (EntrySet AcntID CurrencyPrec TagID Rational (EntryValue Rational))
entryPair = entryPair_ (fmap (EntryValue TFixed) . roundPrecisionCur)
entryPair_
:: (MonadInsertError m, MonadFinance m)
=> (CurrencyPrec -> v -> v')
-> TaggedAcnt
-> TaggedAcnt
-> CurID
-> T.Text
-> v
-> m (EntrySet AcntID CurrencyPrec TagID Rational v')
entryPair_ f from to curid com val = do
cp <- lookupCurrency curid
return $ pair cp from to (f cp val)
2023-05-29 15:56:15 -04:00
where
2023-06-30 23:54:39 -04:00
halfEntry :: a -> [t] -> HalfEntrySet a c t v
halfEntry a ts =
HalfEntrySet
{ hesPrimary = Entry {eAcnt = a, eValue = (), eComment = com, eTags = ts}
, hesOther = []
}
pair cp (TaggedAcnt fa fts) (TaggedAcnt ta tts) v =
EntrySet
{ esCurrency = cp
, esTotalValue = v
, esFrom = halfEntry fa fts
, esTo = halfEntry ta tts
}
2023-05-29 15:56:15 -04:00
sortAllo :: MultiAllocation v -> InsertExcept (DaySpanAllocation v)
sortAllo a@Allocation {alloAmts = as} = do
bs <- foldSpan [] $ L.sortOn amtWhen as
return $ a {alloAmts = reverse bs}
where
foldSpan acc [] = return acc
foldSpan acc (x : xs) = do
let start = amtWhen x
res <- case xs of
[] -> resolveDaySpan start
(y : _) -> resolveDaySpan_ (intStart $ amtWhen y) start
foldSpan (x {amtWhen = res} : acc) xs
2023-05-29 15:56:15 -04:00
--------------------------------------------------------------------------------
-- Income
2023-02-12 16:52:42 -05:00
2023-05-29 17:06:38 -04:00
-- TODO this will scan the interval allocations fully each time
-- iteration which is a total waste, but the fix requires turning this
-- loop into a fold which I don't feel like doing now :(
insertIncome
2023-05-29 13:09:17 -04:00
:: (MonadInsertError m, MonadFinance m)
=> CommitRId
-> T.Text
-> IntAllocations
2023-05-29 13:09:17 -04:00
-> Maybe Interval
-> Income
2023-06-30 23:54:39 -04:00
-> m [Tx BudgetMeta]
2023-02-12 16:23:32 -05:00
insertIncome
key
2023-02-12 16:23:32 -05:00
name
(intPre, intTax, intPost)
2023-05-29 13:09:17 -04:00
localInterval
2023-05-14 19:20:10 -04:00
Income
{ incWhen
, incCurrency
, incFrom
, incPretax
, incPosttax
, incTaxes
, incToBal
, incGross
, incPayPeriod
2023-05-29 17:06:38 -04:00
} =
combineErrorM
(combineError incRes nonIncRes (,))
(combineError precRes dayRes (,))
$ \_ (precision, days) -> do
let gross = roundPrecision precision incGross
concat <$> foldDays (allocate precision gross) start days
where
2023-05-29 17:06:38 -04:00
incRes = isIncomeAcnt $ taAcnt incFrom
nonIncRes =
mapErrors isNotIncomeAcnt $
taAcnt incToBal
: (alloAcnt <$> incPretax)
++ (alloAcnt <$> incTaxes)
++ (alloAcnt <$> incPosttax)
precRes = lookupCurrencyPrec incCurrency
dayRes = askDays incWhen localInterval
2023-05-14 19:20:10 -04:00
start = fromGregorian' $ pStart incPayPeriod
pType' = pType incPayPeriod
meta = BudgetMeta key name
flatPre = concatMap flattenAllo incPretax
flatTax = concatMap flattenAllo incTaxes
flatPost = concatMap flattenAllo incPosttax
sumAllos = sum . fmap faValue
-- TODO ensure these are all the "correct" accounts
2023-05-14 19:20:10 -04:00
allocate precision gross prevDay day = do
scaler <- liftExcept $ periodScaler pType' prevDay day
let (preDeductions, pre) =
2023-05-04 21:48:21 -04:00
allocatePre precision gross $
flatPre ++ concatMap (selectAllos day) intPre
2023-06-30 23:54:39 -04:00
let tax =
2023-05-14 19:20:10 -04:00
allocateTax precision gross preDeductions scaler $
flatTax ++ concatMap (selectAllos day) intTax
2023-05-18 00:26:55 -04:00
aftertaxGross = gross - sumAllos (tax ++ pre)
2023-06-30 23:54:39 -04:00
let post =
2023-05-04 21:48:21 -04:00
allocatePost precision aftertaxGross $
flatPost ++ concatMap (selectAllos day) intPost
2023-06-30 23:54:39 -04:00
let balance = aftertaxGross - sumAllos post
-- TODO double or rational here?
primary <-
entryPair
incFrom
incToBal
incCurrency
"balance after deductions"
(fromRational balance)
allos <- mapErrors (allo2Trans meta day incFrom) (pre ++ tax ++ post)
let bal =
Tx
{ txCommit = meta
, txDate = day
, txPrimary = primary
, txOther = []
, txDescr = "balance after deductions"
}
2023-06-30 23:54:39 -04:00
if balance < 0
then throwError $ InsertException [IncomeError day name balance]
else return (bal : allos)
2023-05-14 19:20:10 -04:00
periodScaler
:: PeriodType
-> Day
-> Day
-> InsertExcept PeriodScaler
2023-05-29 16:36:59 -04:00
periodScaler pt prev cur = return scale
2023-05-14 19:20:10 -04:00
where
2023-05-29 16:36:59 -04:00
n = fromIntegral $ workingDays wds prev cur
2023-05-16 23:12:29 -04:00
wds = case pt of
Hourly HourlyPeriod {hpWorkingDays} -> hpWorkingDays
Daily ds -> ds
2023-05-29 16:36:59 -04:00
scale precision x = case pt of
2023-05-14 19:20:10 -04:00
Hourly HourlyPeriod {hpAnnualHours, hpDailyHours} ->
fromRational (rnd $ x / fromIntegral hpAnnualHours)
* fromIntegral hpDailyHours
* n
Daily _ -> x * n / 365.25
where
rnd = roundPrecision precision
2023-05-29 16:36:59 -04:00
-- ASSUME start < end
workingDays :: [Weekday] -> Day -> Day -> Natural
workingDays wds start end = fromIntegral $ daysFull + daysTail
2023-05-16 23:12:29 -04:00
where
interval = diffDays end start
2023-05-29 16:36:59 -04:00
(nFull, nPart) = divMod interval 7
daysFull = fromIntegral (length wds') * nFull
daysTail = fromIntegral $ length $ takeWhile (< nPart) wds'
2023-05-16 23:12:29 -04:00
startDay = dayOfWeek start
wds' = L.sort $ (\x -> diff (fromWeekday x) startDay) <$> L.nub wds
diff a b = fromIntegral $ mod (fromEnum a - fromEnum b) 7
-- ASSUME days is a sorted list
2023-05-29 15:56:15 -04:00
foldDays
:: MonadInsertError m
=> (Day -> Day -> m a)
-> Day
-> [Day]
-> m [a]
foldDays f start days = case NE.nonEmpty days of
Nothing -> return []
Just ds
| any (start >) ds ->
throwError $
InsertException [PeriodError start $ minimum ds]
| otherwise ->
combineErrors $
snd $
L.mapAccumL (\prevDay day -> (day, f prevDay day)) start days
2023-05-29 15:56:15 -04:00
2023-05-29 17:06:38 -04:00
isIncomeAcnt :: (MonadInsertError m, MonadFinance m) => AcntID -> m ()
isIncomeAcnt = checkAcntType IncomeT
isNotIncomeAcnt :: (MonadInsertError m, MonadFinance m) => AcntID -> m ()
isNotIncomeAcnt = checkAcntTypes (AssetT :| [EquityT, ExpenseT, LiabilityT])
2023-05-29 15:56:15 -04:00
checkAcntType
:: (MonadInsertError m, MonadFinance m)
=> AcntType
-> AcntID
2023-05-29 17:06:38 -04:00
-> m ()
2023-05-29 15:56:15 -04:00
checkAcntType t = checkAcntTypes (t :| [])
checkAcntTypes
:: (MonadInsertError m, MonadFinance m)
=> NE.NonEmpty AcntType
-> AcntID
2023-05-29 17:06:38 -04:00
-> m ()
checkAcntTypes ts i = void $ go =<< lookupAccountType i
where
2023-05-29 15:56:15 -04:00
go t
| t `L.elem` ts = return i
| otherwise = throwError $ InsertException [AccountError i ts]
flattenAllo :: SingleAllocation v -> [FlatAllocation v]
flattenAllo Allocation {alloAmts, alloCur, alloTo} = fmap go alloAmts
where
go Amount {amtValue, amtDesc} =
FlatAllocation
2023-06-30 23:54:39 -04:00
{ faCur = alloCur
2023-05-29 15:56:15 -04:00
, faTo = alloTo
, faValue = amtValue
, faDesc = amtDesc
}
-- ASSUME allocations are sorted
selectAllos :: Day -> DaySpanAllocation v -> [FlatAllocation v]
selectAllos day Allocation {alloAmts, alloCur, alloTo} =
go <$> filter ((`inDaySpan` day) . amtWhen) alloAmts
where
go Amount {amtValue, amtDesc} =
FlatAllocation
2023-06-30 23:54:39 -04:00
{ faCur = alloCur
2023-05-29 15:56:15 -04:00
, faTo = alloTo
, faValue = amtValue
, faDesc = amtDesc
}
allo2Trans
2023-06-30 23:54:39 -04:00
:: (MonadInsertError m, MonadFinance m)
=> BudgetMeta
-> Day
2023-02-26 22:53:12 -05:00
-> TaggedAcnt
-> FlatAllocation Rational
2023-06-30 23:54:39 -04:00
-> m (Tx BudgetMeta)
allo2Trans meta day from FlatAllocation {faValue, faTo, faDesc, faCur} = do
-- TODO double here?
p <- entryPair from faTo faCur faDesc (fromRational faValue)
return
Tx
{ txCommit = meta
, txDate = day
, txPrimary = p
, txOther = []
, txDescr = faDesc
}
2023-05-29 15:56:15 -04:00
allocatePre
:: Natural
-> Rational
-> [FlatAllocation PretaxValue]
-> (M.Map T.Text Rational, [FlatAllocation Rational])
allocatePre precision gross = L.mapAccumR go M.empty
where
go m f@FlatAllocation {faValue} =
let c = preCategory faValue
p = preValue faValue
v =
if prePercent faValue
then (roundPrecision 3 p / 100) * gross
else roundPrecision precision p
in (mapAdd_ c v m, f {faValue = v})
allocateTax
2023-05-04 21:48:21 -04:00
:: Natural
-> Rational
-> M.Map T.Text Rational
2023-05-14 19:20:10 -04:00
-> PeriodScaler
-> [FlatAllocation TaxValue]
-> [FlatAllocation Rational]
2023-05-14 19:20:10 -04:00
allocateTax precision gross preDeds f = fmap (fmap go)
where
go TaxValue {tvCategories, tvMethod} =
2023-05-14 19:20:10 -04:00
let agi = gross - sum (mapMaybe (`M.lookup` preDeds) tvCategories)
in case tvMethod of
2023-05-16 23:12:29 -04:00
TMPercent p ->
roundPrecision precision $
fromRational $
roundPrecision 3 p / 100 * agi
2023-04-30 23:28:16 -04:00
TMBracket TaxProgression {tpDeductible, tpBrackets} ->
2023-05-14 19:20:10 -04:00
let taxDed = roundPrecision precision $ f precision tpDeductible
in foldBracket f precision (agi - taxDed) tpBrackets
2023-05-16 23:12:29 -04:00
-- | Compute effective tax percentage of a bracket
-- The algorithm can be thought of in three phases:
-- 1. Find the highest tax bracket by looping backward until the AGI is less
-- than the bracket limit
-- 2. Computing the tax in the top bracket by subtracting the AGI from the
-- bracket limit and multiplying by the tax percentage.
-- 3. Adding all lower brackets, which are just the limit of the bracket less
-- the amount of the lower bracket times the percentage.
--
-- In reality, this can all be done with one loop, but it isn't clear these
-- three steps are implemented from this alone.
2023-05-14 19:20:10 -04:00
foldBracket :: PeriodScaler -> Natural -> Rational -> [TaxBracket] -> Rational
foldBracket f precision agi bs = fst $ foldr go (0, agi) $ L.sortOn tbLowerLimit bs
where
2023-05-16 23:12:29 -04:00
go TaxBracket {tbLowerLimit, tbPercent} a@(acc, remain) =
2023-05-14 19:20:10 -04:00
let l = roundPrecision precision $ f precision tbLowerLimit
2023-05-08 00:12:01 -04:00
p = roundPrecision 3 tbPercent / 100
2023-05-16 23:12:29 -04:00
in if remain >= l then (acc + p * (remain - l), l) else a
2023-05-29 15:56:15 -04:00
allocatePost
:: Natural
-> Rational
-> [FlatAllocation PosttaxValue]
-> [FlatAllocation Rational]
allocatePost precision aftertax = fmap (fmap go)
where
2023-05-29 15:56:15 -04:00
go PosttaxValue {postValue, postPercent} =
let v = postValue
in if postPercent
then aftertax * roundPrecision 3 v / 100
else roundPrecision precision v
2022-12-11 17:51:11 -05:00
2023-05-29 15:56:15 -04:00
--------------------------------------------------------------------------------
2023-05-29 16:11:19 -04:00
-- Standalone Transfer
2022-12-11 17:51:11 -05:00
2023-02-12 22:18:31 -05:00
expandTransfers
2023-05-07 20:29:33 -04:00
:: (MonadInsertError m, MonadSqlQuery m, MonadFinance m)
=> CommitRId
-> T.Text
2023-05-29 13:09:17 -04:00
-> Maybe Interval
-> [BudgetTransfer]
2023-06-30 23:54:39 -04:00
-> m [Tx BudgetMeta]
2023-05-29 13:09:17 -04:00
expandTransfers key name localInterval ts = do
txs <-
2023-06-30 23:54:39 -04:00
fmap (L.sortOn txDate . concat) $
2023-05-29 13:09:17 -04:00
combineErrors $
fmap (expandTransfer key name) ts
case localInterval of
Nothing -> return txs
Just i -> do
2023-05-29 15:56:15 -04:00
bounds <- liftExcept $ resolveDaySpan i
2023-06-30 23:54:39 -04:00
return $ filter (inDaySpan bounds . txDate) txs
2023-05-04 21:48:21 -04:00
expandTransfer
2023-05-07 20:29:33 -04:00
:: (MonadInsertError m, MonadSqlQuery m, MonadFinance m)
=> CommitRId
-> T.Text
-> BudgetTransfer
2023-06-30 23:54:39 -04:00
-> m [Tx BudgetMeta]
2023-05-04 21:48:21 -04:00
expandTransfer key name Transfer {transAmounts, transTo, transCurrency, transFrom} = do
2023-06-30 23:54:39 -04:00
fmap concat $ mapErrors go transAmounts
2023-05-07 20:29:33 -04:00
where
go
Amount
{ amtWhen = pat
2023-06-30 23:54:39 -04:00
, amtValue = TransferValue {tvVal = v, tvType = t}
2023-05-07 20:29:33 -04:00
, amtDesc = desc
} =
withDates pat $ \day -> do
let meta = BudgetMeta {bmCommit = key, bmName = name}
2023-06-30 23:54:39 -04:00
p <- entryPair_ (\_ x -> EntryValue t $ toRational x) transFrom transTo transCurrency desc v
2023-05-07 20:29:33 -04:00
return
2023-06-30 23:54:39 -04:00
Tx
{ txCommit = meta
, txDate = day
, txPrimary = p
, txOther = []
, txDescr = desc
2023-05-07 20:29:33 -04:00
}
2023-05-29 15:56:15 -04:00
withDates
:: (MonadSqlQuery m, MonadFinance m, MonadInsertError m)
=> DatePat
-> (Day -> m a)
-> m [a]
withDates dp f = do
bounds <- askDBState kmBudgetInterval
days <- liftExcept $ expandDatePat bounds dp
combineErrors $ fmap f days
2023-02-12 22:18:31 -05:00
2022-12-11 17:51:11 -05:00
--------------------------------------------------------------------------------
2023-05-29 15:56:15 -04:00
-- shadow transfers
2023-05-13 13:53:43 -04:00
2023-05-29 15:56:15 -04:00
-- TODO this is going to be O(n*m), which might be a problem?
addShadowTransfers
2023-06-30 23:54:39 -04:00
:: (MonadInsertError m, MonadFinance m)
=> [ShadowTransfer]
-> [Tx BudgetMeta]
-> m [Tx BudgetMeta]
addShadowTransfers ms txs = mapErrors go txs
where
go tx = do
es <- catMaybes <$> mapErrors (fromShadow tx) ms
return $ tx {txOther = es}
2022-12-11 17:51:11 -05:00
2023-05-29 15:56:15 -04:00
fromShadow
2023-06-30 23:54:39 -04:00
:: (MonadInsertError m, MonadFinance m)
=> Tx BudgetMeta
2023-05-29 15:56:15 -04:00
-> ShadowTransfer
2023-06-30 23:54:39 -04:00
-> m (Maybe (EntrySet AcntID CurrencyPrec TagID Rational (Either Double (EntryValue Rational))))
fromShadow tx ShadowTransfer {stFrom, stTo, stDesc, stRatio, stCurrency, stMatch} = do
res <- liftExcept $ shadowMatches stMatch tx
es <- entryPair_ (\_ v -> Left v) stFrom stTo stCurrency stDesc stRatio
return $ if not res then Nothing else Just es
shadowMatches :: TransferMatcher -> Tx BudgetMeta -> InsertExcept Bool
shadowMatches TransferMatcher {tmFrom, tmTo, tmDate} Tx {txPrimary, txDate} = do
-- NOTE this will only match against the primary entry set since those
-- are what are guaranteed to exist from a transfer
-- valRes <- valMatches tmVal $ esTotalValue $ txPrimary
2023-05-29 15:56:15 -04:00
return $
2023-06-30 23:54:39 -04:00
memberMaybe (eAcnt $ hesPrimary $ esFrom txPrimary) tmFrom
&& memberMaybe (eAcnt $ hesPrimary $ esTo txPrimary) tmTo
&& maybe True (`dateMatches` txDate) tmDate
2022-12-11 17:51:11 -05:00
where
2023-06-30 23:54:39 -04:00
-- && valRes
2023-05-29 15:56:15 -04:00
memberMaybe x AcntSet {asList, asInclude} =
(if asInclude then id else not) $ x `elem` asList
2022-12-11 17:51:11 -05:00
2023-05-29 15:56:15 -04:00
--------------------------------------------------------------------------------
-- random
2023-01-28 22:55:07 -05:00
2023-06-30 23:54:39 -04:00
-- initialCurrency :: TransferCurrency -> CurID
-- initialCurrency (NoX c) = c
-- initialCurrency (X Exchange {xFromCur = c}) = c
2022-12-11 17:51:11 -05:00
2023-05-29 17:06:38 -04:00
alloAcnt :: Allocation w v -> AcntID
alloAcnt = taAcnt . alloTo
2023-05-29 15:56:15 -04:00
data UnbalancedValue = UnbalancedValue
2023-06-30 23:54:39 -04:00
{ cvType :: !TransferType
2023-05-29 15:56:15 -04:00
, cvValue :: !Rational
}
deriving (Show)
2023-01-28 22:55:07 -05:00
-- TODO need to make this into the same ish thing as the Tx/EntrySet structs
-- in the history algorithm, which will entail resolving the budget currency
-- stuff earlier in the chain, and preloading multiple entries into this thing
-- before balancing.
2023-06-30 23:54:39 -04:00
-- type UnbalancedTransfer = FlatTransfer UnbalancedValue
-- ubt2tx :: UnbalancedTransfer -> Tx BudgetMeta
-- ubt2tx
-- FlatTransfer
-- { ftFrom
-- , ftTo
-- , ftValue
-- , ftWhen
-- , ftDesc
-- , ftMeta
-- , ftCur
-- } =
-- Tx
-- { txDescr = ftDesc
-- , txDate = ftWhen
-- , txPrimary = p
-- , txOther = maybeToList os
-- , txCommit = ftMeta
-- }
-- where
-- (p, os) = entries ftCur
-- entries (NoX curid) = (pair curid ftFrom ftTo ftValue, Nothing)
-- entries (X Exchange {xFromCur, xToCur, xAcnt, xRate}) =
-- let middle = TaggedAcnt xAcnt []
-- p1 = pair xFromCur ftFrom middle ftValue
-- p2 = pair xToCur middle ftTo (ftValue * roundPrecision 3 xRate)
-- in (p1, Just p2)
-- pair c (TaggedAcnt fa fts) (TaggedAcnt ta tts) v =
-- EntrySet
-- { esTotalValue = v
-- , esCurrency = c
-- , esFrom =
-- HalfEntrySet
-- { hesPrimary =
-- Entry
-- { eValue = ()
-- , eComment = ""
-- , eAcnt = fa
-- , eTags = fts
-- }
-- , hesOther = []
-- }
-- , esTo =
-- HalfEntrySet
-- { hesPrimary =
-- Entry
-- { eValue = ()
-- , eComment = ""
-- , eAcnt = ta
-- , eTags = tts
-- }
-- , hesOther = []
-- }
-- }
-- type BalancedTransfer = FlatTransfer Rational
-- data FlatTransfer v = FlatTransfer
-- { ftFrom :: !TaggedAcnt
-- , ftTo :: !TaggedAcnt
-- , ftValue :: !v
-- , ftWhen :: !Day
-- , ftDesc :: !T.Text
-- , ftMeta :: !BudgetMeta
-- , ftCur :: !TransferCurrency
-- }
-- deriving (Show)
2023-01-28 22:55:07 -05:00
2023-05-29 15:56:15 -04:00
data BudgetMeta = BudgetMeta
2023-06-30 23:54:39 -04:00
{ bmCommit :: !CommitR
2023-05-29 15:56:15 -04:00
, bmName :: !T.Text
}
deriving (Show)
2023-01-28 22:55:07 -05:00
2023-05-29 15:56:15 -04:00
type IntAllocations =
( [DaySpanAllocation PretaxValue]
, [DaySpanAllocation TaxValue]
, [DaySpanAllocation PosttaxValue]
)
2023-02-12 16:52:42 -05:00
2023-05-29 15:56:15 -04:00
type DaySpanAllocation = Allocation DaySpan
2023-05-04 21:48:21 -04:00
2023-05-29 16:11:19 -04:00
type EntryPair = (KeyEntry, KeyEntry)
2023-05-04 21:48:21 -04:00
2023-05-29 15:56:15 -04:00
type PeriodScaler = Natural -> Double -> Double
2023-05-07 20:29:33 -04:00
2023-05-29 15:56:15 -04:00
data FlatAllocation v = FlatAllocation
{ faValue :: !v
, faDesc :: !T.Text
, faTo :: !TaggedAcnt
2023-06-30 23:54:39 -04:00
, faCur :: !CurID
2023-05-29 15:56:15 -04:00
}
deriving (Functor, Show)