1205 lines
31 KiB
Plaintext
1205 lines
31 KiB
Plaintext
let Map =
|
|
https://prelude.dhall-lang.org/v21.1.0/Map/Type
|
|
sha256:210c7a9eba71efbb0f7a66b3dcf8b9d3976ffc2bc0e907aadfb6aa29c333e8ed
|
|
|
|
let List/map =
|
|
https://prelude.dhall-lang.org/v21.1.0/List/map
|
|
sha256:dd845ffb4568d40327f2a817eb42d1c6138b929ca758d50bc33112ef3c885680
|
|
|
|
let AccountTree
|
|
: Type
|
|
=
|
|
{-
|
|
Recursive type representing a tree of accounts.
|
|
|
|
A node in the tree can either be an account (leaf) which has a name
|
|
and description, or a placeholder (branch) which has name, description,
|
|
and a non-empty list of accounts or other placeholders.
|
|
-}
|
|
forall (a : Type) ->
|
|
forall ( Fix
|
|
: < AccountF : { _1 : Text, _2 : Text }
|
|
| PlaceholderF :
|
|
{ _1 : Text
|
|
, _2 : Text
|
|
, _3 :
|
|
{- TODO nonempty? -}
|
|
List a
|
|
}
|
|
> ->
|
|
a
|
|
) ->
|
|
a
|
|
|
|
let AccountTreeF =
|
|
{-
|
|
Fixed type abstraction for an account tree.
|
|
-}
|
|
\(a : Type) ->
|
|
< AccountF : { _1 : Text, _2 : Text }
|
|
| PlaceholderF : { _1 : Text, _2 : Text, _3 : List a }
|
|
>
|
|
|
|
let Account
|
|
: Text -> Text -> AccountTree
|
|
=
|
|
{-
|
|
Smart constructor to build an account node in an account tree.
|
|
-}
|
|
\(desc : Text) ->
|
|
\(name : Text) ->
|
|
\(a : Type) ->
|
|
let f = AccountTreeF a
|
|
|
|
in \(Fix : f -> a) -> Fix (f.AccountF { _1 = desc, _2 = name })
|
|
|
|
let Placeholder
|
|
: Text -> Text -> List AccountTree -> AccountTree
|
|
=
|
|
{-
|
|
Smart constructor to build a placeholder node in an account tree.
|
|
-}
|
|
\(desc : Text) ->
|
|
\(name : Text) ->
|
|
\(children : List AccountTree) ->
|
|
\(a : Type) ->
|
|
let f = AccountTreeF a
|
|
|
|
in \(Fix : f -> a) ->
|
|
let apply = \(x : AccountTree) -> x a Fix
|
|
|
|
in Fix
|
|
( f.PlaceholderF
|
|
{ _1 = desc
|
|
, _2 = name
|
|
, _3 = List/map AccountTree a apply children
|
|
}
|
|
)
|
|
|
|
let AcntID =
|
|
{-
|
|
A unique ID for an account; the exact ID associated with each account
|
|
depends on the path in which each account exists in a tree. If the leaf
|
|
node of an account's path is totally unique, this ID will be that leaf node.
|
|
If not, then branch nodes will be appended to the front (delimited by
|
|
underscores) until the ID is unique.
|
|
|
|
This ID will be used throughout the config to refer to a specific account.
|
|
-}
|
|
Text
|
|
|
|
let CurID =
|
|
{-
|
|
A unique, short (usually three uppercase characters) ID for a currency;
|
|
this symbol will be used throughout the configuration to signify a
|
|
particular currency
|
|
-}
|
|
Text
|
|
|
|
let Currency =
|
|
{-
|
|
A unit of exchange.
|
|
-}
|
|
{ curSymbol : CurID
|
|
, curFullname :
|
|
{-
|
|
The full description of this currency (eg "Yugoslavian Twitcoin")
|
|
-}
|
|
Text
|
|
, curPrecision :
|
|
{-
|
|
The number of decimal places for this currency
|
|
-}
|
|
Natural
|
|
}
|
|
|
|
let TagID =
|
|
{-
|
|
A unique ID for a tag. This ID will be used throughout the configuration
|
|
to refer to a specific tag.
|
|
-}
|
|
Text
|
|
|
|
let Tag =
|
|
{-
|
|
A short metadata identifier associated with an account or entry.
|
|
-}
|
|
{ tagID : TagID
|
|
, tagDesc :
|
|
{-
|
|
A description to convey the meaning of this tag.
|
|
-}
|
|
Text
|
|
}
|
|
|
|
let SqlConfig {- TODO pgsql -} =
|
|
{-
|
|
How to connect to a SQL database. Only SQLite is supported, in which case
|
|
the only parameter is the output path of the db file.
|
|
-}
|
|
< Sqlite : Text | Postgres >
|
|
|
|
let Gregorian =
|
|
{-
|
|
A full date like 1976-04-01
|
|
-}
|
|
{ gYear : Natural, gMonth : Natural, gDay : Natural }
|
|
|
|
let GregorianM =
|
|
{-
|
|
Like a Gregorian but without the month
|
|
-}
|
|
{ gmYear : Natural, gmMonth : Natural }
|
|
|
|
let Interval =
|
|
{-
|
|
An interval in time. If end is None, the interval ends at 'forever'
|
|
-}
|
|
{ intStart : Gregorian, intEnd : Optional Gregorian }
|
|
|
|
let TemporalScope =
|
|
{-
|
|
The range of times that will be considered when computing transactions.
|
|
-}
|
|
{ budgetInterval : Interval, statementInterval : Interval }
|
|
|
|
let TimeUnit = < Day | Week | Month | Year >
|
|
|
|
let Weekday = < Mon | Tue | Wed | Thu | Fri | Sat | Sun >
|
|
|
|
let RepeatPat =
|
|
{-
|
|
Means to match a repeated set of numeric values.
|
|
-}
|
|
{ rpStart :
|
|
{-
|
|
Initial value to match
|
|
-}
|
|
Natural
|
|
, rpBy :
|
|
{-
|
|
Distance between each repeated value
|
|
-}
|
|
Natural
|
|
, rpRepeats :
|
|
{-
|
|
Number of repeats after initial value to match. If not given, this
|
|
number continues until an upper bound determined from context.
|
|
-}
|
|
Optional Natural
|
|
}
|
|
|
|
let MDYPat =
|
|
{-
|
|
Means to match either a year, month, or day in a date (the matched component
|
|
is determined by context)
|
|
|
|
Single: match a single number
|
|
Multi: match several numbers
|
|
Repeat: match a repeated pattern
|
|
After: match any number greater than a given value
|
|
Before: match any number less than a given value
|
|
Between: match any number between two values
|
|
-}
|
|
< Single : Natural
|
|
| Multi : List Natural
|
|
| Repeat : RepeatPat
|
|
| After : Natural
|
|
| Before : Natural
|
|
| Between : { _between1 : Natural, _between2 : Natural }
|
|
>
|
|
|
|
let ModPat =
|
|
{-
|
|
Means to match a date using modular arithmetic.
|
|
-}
|
|
{ Type =
|
|
{ mpStart :
|
|
{-
|
|
The starting date to begin matching. If not given, start at the
|
|
beginning of whatever global time window is active.
|
|
-}
|
|
Optional Gregorian
|
|
, mpBy :
|
|
{-
|
|
Numeric number of temporal units to repeat before next match.
|
|
-}
|
|
Natural
|
|
, mpUnit :
|
|
{-
|
|
Unit of each interval
|
|
-}
|
|
TimeUnit
|
|
, mpRepeats :
|
|
{-
|
|
Number of repeats to match. If not given, match all repeats until
|
|
the end of the active global interval
|
|
-}
|
|
Optional Natural
|
|
}
|
|
, default = { mpStart = None Gregorian, mpRepeats = None Natural }
|
|
}
|
|
|
|
let WeekdayPat =
|
|
{-
|
|
Means to match a given day of week
|
|
|
|
OnDay: Match a single weekday
|
|
OnDays: Match multiple weekdays
|
|
-}
|
|
< OnDay : Weekday | OnDays : List Weekday >
|
|
|
|
let CronPat =
|
|
{-
|
|
Means of matching dates according to their component parts.
|
|
|
|
This is similar to 'cron' patterns in unix-like systems.
|
|
-}
|
|
{ Type =
|
|
{ cpWeekly : Optional WeekdayPat
|
|
, cpYear : Optional MDYPat
|
|
, cpMonth : Optional MDYPat
|
|
, cpDay : Optional MDYPat
|
|
}
|
|
, default =
|
|
{ cpWeekly = None WeekdayPat
|
|
, cpYear = None MDYPat
|
|
, cpMonth = None MDYPat
|
|
, cpDay = None MDYPat
|
|
}
|
|
}
|
|
|
|
let DatePat =
|
|
{-
|
|
Means of matching dates
|
|
|
|
Cron: use cron-like date matching
|
|
Mod: use modular temporal arithmetic matching
|
|
-}
|
|
< Cron : CronPat.Type | Mod : ModPat.Type >
|
|
|
|
let TxAmount1 =
|
|
\(re : Type) ->
|
|
{ a1Column : Text
|
|
, a1Fmt :
|
|
{-
|
|
Format of the amount field. Must include three fields for the
|
|
sign, numerator, and denominator of the amount.
|
|
-}
|
|
re
|
|
}
|
|
|
|
let TxAmount2 =
|
|
\(re : Type) ->
|
|
{ a2Positive : Text
|
|
, a2Negative : Text
|
|
, a2Fmt :
|
|
{-
|
|
Format of the amount field. Must include two fields for the
|
|
numerator and denominator of the amount.
|
|
-}
|
|
re
|
|
}
|
|
|
|
let TxAmountSpec =
|
|
\(re : Type) ->
|
|
< AmountSingle : TxAmount1 re | AmountDual : TxAmount2 re >
|
|
|
|
let TxOpts_ =
|
|
{-
|
|
Additional metadata to use when parsing a statement
|
|
-}
|
|
\(re : Type) ->
|
|
{ toDate :
|
|
{-
|
|
Column title for date
|
|
-}
|
|
Text
|
|
, toAmount :
|
|
{-
|
|
Column title for amount
|
|
-}
|
|
TxAmountSpec re
|
|
, toDesc :
|
|
{-
|
|
Column title for description
|
|
-}
|
|
Text
|
|
, toOther :
|
|
{-
|
|
Titles of other columns to include; these will be available in
|
|
a map for use in downstream processing (see 'Field')
|
|
-}
|
|
List Text
|
|
, toDateFmt :
|
|
{-
|
|
Format of the date field as specified in the
|
|
Data.Time.Format.formattime Haskell function.
|
|
-}
|
|
Text
|
|
, toSkipBlankDate : Bool
|
|
, toSkipBlankAmount : Bool
|
|
, toSkipBlankDescription : Bool
|
|
, toSkipBlankOther : List Text
|
|
}
|
|
|
|
let TxOpts =
|
|
{ Type = TxOpts_ Text
|
|
, default =
|
|
{ toDate = "Date"
|
|
, toAmount =
|
|
(TxAmountSpec Text).AmountSingle
|
|
{ a1Column = "Amount", a1Fmt = "([-+])?([0-9\\.]+)" }
|
|
, toDesc = "Description"
|
|
, toOther = [] : List Text
|
|
, toDateFmt = "%0m/%0d/%Y"
|
|
, toSkipBlankDate = False
|
|
, toSkipBlankAmount = False
|
|
, toSkipBlankDescription = False
|
|
, toSkipBlankOther = [] : List Text
|
|
}
|
|
}
|
|
|
|
let Field =
|
|
{-
|
|
General key-value type
|
|
-}
|
|
\(k : Type) -> \(v : Type) -> { fKey : k, fVal : v }
|
|
|
|
let FieldMap =
|
|
{-
|
|
Key-value type where key maps to a Map with key of the same type
|
|
-}
|
|
\(k : Type) -> \(v : Type) -> Field k (Map k v)
|
|
|
|
let ValMatcher =
|
|
{-
|
|
Means to match a decimal value.
|
|
-}
|
|
{ Type =
|
|
{ vmSign :
|
|
{-
|
|
Sign of value.
|
|
True -> positive,
|
|
False -> negative,
|
|
None -> do not consider.
|
|
-}
|
|
Optional Bool
|
|
, vmNum :
|
|
{-
|
|
Value of numerator to match. Do not consider numerator if none
|
|
-}
|
|
Optional Natural
|
|
, vmDen :
|
|
{-
|
|
Value of denominator to match. Do not consider numerator if none
|
|
-}
|
|
Optional Natural
|
|
, vmPrec :
|
|
{-
|
|
Precision of decimal to use when matching. This only affects the
|
|
denominator, such that a query of '10.1' with a precision of 2
|
|
will have a denominator of '10'
|
|
-}
|
|
Natural
|
|
}
|
|
, default =
|
|
{ vmSign = None Bool
|
|
, vmNum = None Natural
|
|
, vmDen = None Natural
|
|
, vmPrec = 2
|
|
}
|
|
}
|
|
|
|
let YMDMatcher =
|
|
{-
|
|
Means to match a given date with varying precision
|
|
-}
|
|
< Y : Natural | YM : GregorianM | YMD : Gregorian >
|
|
|
|
let DateMatcher =
|
|
{-
|
|
Means to match either one discrete date or a range of dates
|
|
-}
|
|
< On : YMDMatcher | In : { _1 : YMDMatcher, _2 : Natural } >
|
|
|
|
let FieldMatcher_ =
|
|
{-
|
|
Means to match a given field (either textual or numeric)
|
|
-}
|
|
\(re : Type) ->
|
|
< Desc : Field Text re | Val : Field Text ValMatcher.Type >
|
|
|
|
let FieldMatcher = FieldMatcher_ Text
|
|
|
|
let EntryNumGetter =
|
|
{-
|
|
Means to get a numeric value from a statement row.
|
|
|
|
LookupN: lookup the value from a field
|
|
ConstN: a constant value
|
|
AmountN: the value of the 'Amount' column times a scaling factor
|
|
BalanceN: the amount required to make the target account reach a balance
|
|
PercentN: the amount required to make an account reach a given percentage
|
|
-}
|
|
< LookupN : Text
|
|
| ConstN : Double
|
|
| AmountN : Double
|
|
| BalanceN : Double
|
|
| PercentN : Double
|
|
>
|
|
|
|
let LinkedNumGetter =
|
|
{-
|
|
Means to get a numeric value from another entry
|
|
-}
|
|
{ Type =
|
|
{ lngIndex :
|
|
{-
|
|
Index of the entry to link.
|
|
-}
|
|
Natural
|
|
, lngScale :
|
|
{-
|
|
Factor by which to multiply the value of the linked entry.
|
|
-}
|
|
Double
|
|
}
|
|
, default = { lngScale = 1.0, lngIndex = 0 }
|
|
}
|
|
|
|
let LinkedEntryNumGetter =
|
|
{-
|
|
Means to get a numeric value from a statement row or another entry getter.
|
|
|
|
Linked: a number referring to the entry on the 'from' side of the
|
|
transaction (with 0 being the primary entry)
|
|
Getter: a normal getter
|
|
-}
|
|
< Linked : LinkedNumGetter.Type | Getter : EntryNumGetter >
|
|
|
|
let EntryTextGetter =
|
|
{-
|
|
Means to get a textual value from a statement row.
|
|
|
|
ConstT: a constant value
|
|
LookupT: lookup the value of a field
|
|
MapT: use the value of a column as the key for a map
|
|
Map2T: use the paired value of 2 columns as the key for a map
|
|
-}
|
|
\(t : Type) ->
|
|
< ConstT : t
|
|
| LookupT : Text
|
|
| MapT : FieldMap Text t
|
|
| Map2T : FieldMap { _1 : Text, _2 : Text } t
|
|
>
|
|
|
|
let EntryCurGetter =
|
|
{-
|
|
Means to get a currency ID from a statement row.
|
|
-}
|
|
EntryTextGetter CurID
|
|
|
|
let EntryAcntGetter =
|
|
{-
|
|
Means to get an account ID from a statement row.
|
|
-}
|
|
EntryTextGetter AcntID
|
|
|
|
let Entry =
|
|
{-
|
|
General type describing a single line item in an account.
|
|
|
|
The polymorphism of this type allows representation of an actual line
|
|
item itself as well as the means to get a line item from other data.
|
|
-}
|
|
\(a : Type) ->
|
|
\(v : Type) ->
|
|
\(t : Type) ->
|
|
{ eAcnt :
|
|
{-
|
|
Pertains to account for this entry.
|
|
-}
|
|
a
|
|
, eValue :
|
|
{-
|
|
Pertains to value for this entry.
|
|
-}
|
|
v
|
|
, eComment :
|
|
{-
|
|
A short description of this entry (if none, use a blank string)
|
|
-}
|
|
Text
|
|
, eTags :
|
|
{-
|
|
Pertains to the tags to describe this entry.
|
|
-}
|
|
List t
|
|
}
|
|
|
|
let EntryGetter =
|
|
{-
|
|
Means for getting an entry from a given row in a statement (debit side)
|
|
-}
|
|
\(n : Type) ->
|
|
{ Type = Entry EntryAcntGetter n TagID
|
|
, default = { eComment = "", eTags = [] : List TagID }
|
|
}
|
|
|
|
let FromEntryGetter =
|
|
{-
|
|
Means for getting an entry from a given row in a statement (debit side)
|
|
-}
|
|
EntryGetter EntryNumGetter
|
|
|
|
let ToEntryGetter =
|
|
{-
|
|
Means for getting an entry from a given row in a statement (credit side)
|
|
-}
|
|
EntryGetter LinkedEntryNumGetter
|
|
|
|
let TxHalfGetter =
|
|
{-
|
|
Means of transforming one row in a statement to either the credit or debit
|
|
half of a transaction
|
|
-}
|
|
\(e : Type) ->
|
|
{ Type =
|
|
{ thgAcnt :
|
|
{-
|
|
Account from which this transaction will be balanced. The value
|
|
of the transaction will be assigned to this account unless
|
|
other entries are specified (see below).
|
|
|
|
This account (and its associated entry) will be denoted
|
|
'primary'.
|
|
-}
|
|
EntryAcntGetter
|
|
, thgEntries :
|
|
{-
|
|
Means of getting additional entries from which this transaction
|
|
will be balanced. If this list is empty, the total value of the
|
|
transaction will be assigned to the value defined by 'tsgAcnt'.
|
|
Otherwise, the entries specified here will be added to this side
|
|
of this transaction, and their sum value will be subtracted from
|
|
the total value of the transaction and assigned to 'tsgAcnt'.
|
|
|
|
This is useful for situations where a particular transaction
|
|
denotes values that come from multiple subaccounts.
|
|
-}
|
|
List e
|
|
, thgComment :
|
|
{-
|
|
Comment for the primary entry
|
|
-}
|
|
Text
|
|
, thgTags :
|
|
{-
|
|
Tags for the primary entry
|
|
-}
|
|
List TagID
|
|
}
|
|
, default =
|
|
{ thgTags = [] : List TagID
|
|
, thgComment = ""
|
|
, thgEntries = [] : List e
|
|
}
|
|
}
|
|
|
|
let FromTxHalfGetter = TxHalfGetter FromEntryGetter.Type
|
|
|
|
let ToTxHalfGetter = TxHalfGetter ToEntryGetter.Type
|
|
|
|
let TxSubGetter =
|
|
{-
|
|
A means for transforming one row in a statement to a transaction
|
|
-}
|
|
{ Type =
|
|
{ tsgValue : EntryNumGetter
|
|
, tsgCurrency : EntryCurGetter
|
|
, tsgFrom : (TxHalfGetter FromEntryGetter.Type).Type
|
|
, tsgTo : (TxHalfGetter ToEntryGetter.Type).Type
|
|
}
|
|
, default = { tsgFrom = TxHalfGetter, tsgTo = TxHalfGetter }
|
|
}
|
|
|
|
let TxGetter =
|
|
{-
|
|
A means for transforming one row in a statement to a transaction
|
|
-}
|
|
{ Type =
|
|
{ tgFrom : (TxHalfGetter FromEntryGetter.Type).Type
|
|
, tgTo : (TxHalfGetter ToEntryGetter.Type).Type
|
|
, tgScale : Double
|
|
, tgCurrency : EntryCurGetter
|
|
, tgOtherEntries : List TxSubGetter.Type
|
|
}
|
|
, default =
|
|
{ tgOtherEntries = [] : List TxSubGetter.Type
|
|
, tgFrom = TxHalfGetter
|
|
, tgTo = TxHalfGetter
|
|
, tgScale = 1.0
|
|
}
|
|
}
|
|
|
|
let StatementParser_ =
|
|
{-
|
|
A recipe to match and transform a given entry in a statement to a
|
|
transaction between 2 or more accounts.
|
|
|
|
Polymorphism allows regular expressions to be computed and cached within
|
|
the type during parsing.
|
|
-}
|
|
\(re : Type) ->
|
|
{ Type =
|
|
{ spDate :
|
|
{-
|
|
How to match the date column; if none match any date
|
|
-}
|
|
Optional DateMatcher
|
|
, spVal :
|
|
{-
|
|
How to match the value column; if none match any value
|
|
-}
|
|
ValMatcher.Type
|
|
, spDesc :
|
|
{-
|
|
Regular expression to match the description;
|
|
if none match anythingS
|
|
-}
|
|
Optional re
|
|
, spOther :
|
|
{-
|
|
How to match additional columns if present
|
|
-}
|
|
List (FieldMatcher_ re)
|
|
, spTx :
|
|
{-
|
|
How to translate the matched statement row into entries for
|
|
a transaction. If none, don't make a transaction (eg 'skip'
|
|
this row in the statement).
|
|
-}
|
|
Optional TxGetter.Type
|
|
, spTimes :
|
|
{-
|
|
Match at most this many rows; if none there is no limit
|
|
-}
|
|
Optional Natural
|
|
, spPriority :
|
|
{-
|
|
In case of multiple matches, higher priority gets precedence.
|
|
-}
|
|
Integer
|
|
}
|
|
, default =
|
|
{ spDate = None DateMatcher
|
|
, spVal = ValMatcher::{=}
|
|
, spDesc = None Text
|
|
, spOther = [] : List (FieldMatcher_ re)
|
|
, spTx = None TxGetter.Type
|
|
, spTimes = None Natural
|
|
, spPriority = +0
|
|
}
|
|
}
|
|
|
|
let StatementParser =
|
|
{-
|
|
A statement parser specialized to raw regular expressions.
|
|
-}
|
|
StatementParser_ Text
|
|
|
|
let Amount =
|
|
{-
|
|
A quantify of currency at a given time.
|
|
-}
|
|
\(w : Type) ->
|
|
\(v : Type) ->
|
|
{ Type =
|
|
{ amtWhen : w, amtValue : v, amtDesc : Text, amtPriority : Integer }
|
|
, default.amtPriority = +0
|
|
}
|
|
|
|
let TransferType =
|
|
{-
|
|
The type of a budget transfer.
|
|
|
|
BTFixed: Tranfer a fixed amount
|
|
BTPercent: Transfer a percent of the source account to destination
|
|
BTTarget: Transfer an amount such that the destination has a given target
|
|
value
|
|
-}
|
|
< TPercent | TBalance | TFixed >
|
|
|
|
let TransferValue =
|
|
{-
|
|
Means to determine the value of a budget transfer.
|
|
-}
|
|
{ Type = { tvVal : Double, tvType : TransferType }
|
|
, default.tvType = TransferType.TFixed
|
|
}
|
|
|
|
let Transfer =
|
|
{-
|
|
1-1 transaction(s) between two accounts.
|
|
-}
|
|
\(a : Type) ->
|
|
\(c : Type) ->
|
|
\(w : Type) ->
|
|
\(v : Type) ->
|
|
{ transFrom : a
|
|
, transTo : a
|
|
, transCurrency : c
|
|
, transAmounts : List (Amount w v).Type
|
|
}
|
|
|
|
let TaggedAcnt =
|
|
{-
|
|
An account with a tag
|
|
-}
|
|
{ Type = { taAcnt : AcntID, taTags : List TagID }
|
|
, default.taTags = [] : List TagID
|
|
}
|
|
|
|
let HistTransfer =
|
|
{-
|
|
A manually specified historical transfer
|
|
-}
|
|
Transfer TaggedAcnt.Type CurID DatePat TransferValue.Type
|
|
|
|
let TransferAmount = Amount DatePat TransferValue.Type
|
|
|
|
let Statement =
|
|
{-
|
|
How to import a statement from local file(s). Statements are assumed to be
|
|
tabular with one statement per row.
|
|
-}
|
|
{ stmtPaths
|
|
{-
|
|
paths to statement files
|
|
-}
|
|
: List Text
|
|
, stmtParsers
|
|
{-
|
|
parsers to match statements
|
|
-}
|
|
: List StatementParser.Type
|
|
, stmtDelim
|
|
{-
|
|
file delimiter as a numeric char, usually either tab (9) or comma (44)
|
|
-}
|
|
: Natural
|
|
, stmtTxOpts : TxOpts.Type
|
|
, stmtSkipLines
|
|
{-
|
|
how many lines to skip before parsing statement
|
|
-}
|
|
: Natural
|
|
}
|
|
|
|
let History =
|
|
{-
|
|
How to generate historical transactions; either specified as manual
|
|
transfers or via statements in files on local disk
|
|
-}
|
|
< HistTransfer : HistTransfer | HistStatement : Statement >
|
|
|
|
let Allocation =
|
|
{-
|
|
How to allocate a given budget stream. This can be thought of as a Transfer
|
|
without an incoming account specified.
|
|
-}
|
|
\(w : Type) ->
|
|
\(v : Type) ->
|
|
{ alloTo : TaggedAcnt.Type, alloAmts : List (Amount w v).Type }
|
|
|
|
let PretaxValue =
|
|
{-
|
|
How to determine value of a pretax allocation.
|
|
-}
|
|
{ preValue :
|
|
{-
|
|
The value to be deducted from gross income
|
|
-}
|
|
Double
|
|
, prePercent :
|
|
{-
|
|
If true, value is interpreted as a percent of gross income instead of
|
|
a fixed amount.
|
|
-}
|
|
Bool
|
|
, preCategory :
|
|
{-
|
|
A category for this allocation. This is used when calculating taxes,
|
|
which match against this to determine how much to deduct from the
|
|
gross income stream.
|
|
-}
|
|
Text
|
|
}
|
|
|
|
let TaxBracket =
|
|
{-
|
|
A single tax bracket. Read as "every unit above limit is taxed at this
|
|
percentage".
|
|
-}
|
|
{ tbLowerLimit : Double, tbPercent : Double }
|
|
|
|
let TaxProgression =
|
|
{-
|
|
A tax progression using a deductible and a series of tax brackets.
|
|
This will cover simple cases of the US income tax as of 2017 and similar.
|
|
-}
|
|
{ tpDeductible :
|
|
{-
|
|
Initial amount to subtract from after-pretax-deductions
|
|
-}
|
|
Double
|
|
, tpBrackets :
|
|
{-
|
|
Tax brackets to apply after deductions (order does not matter, each
|
|
entry will be sorted by limit)
|
|
-}
|
|
List TaxBracket
|
|
}
|
|
|
|
let TaxMethod =
|
|
{-
|
|
How to implement a given tax (either a progressive tax or a fixed percent)
|
|
-}
|
|
< TMBracket : TaxProgression | TMPercent : Double >
|
|
|
|
let TaxValue =
|
|
{-
|
|
Means to determine value of an income tax allocation.
|
|
-}
|
|
{ tvCategories :
|
|
{-
|
|
A list of categories corresponding to pretax allocations. Taxable
|
|
income (from the perspective of this type) will be determined by
|
|
subtracting matching allocations from gross income.
|
|
-}
|
|
List Text
|
|
, tvMethod : TaxMethod
|
|
}
|
|
|
|
let PosttaxValue =
|
|
{-
|
|
Means to determine value of a post tax allocation.
|
|
-}
|
|
{ postValue :
|
|
{-
|
|
The value to be deducted from income remaining after taxes.
|
|
-}
|
|
Double
|
|
, postPercent :
|
|
{-
|
|
If true, subtract a percentage from the after-tax remainder instead
|
|
of a fixed value.
|
|
-}
|
|
Bool
|
|
}
|
|
|
|
let SingleAllocation =
|
|
{-
|
|
An allocation specialized to an income stream (which means the timing is
|
|
dictated by the income stream)
|
|
-}
|
|
Allocation {}
|
|
|
|
let SingleAlloAmount = \(v : Type) -> Amount {} v
|
|
|
|
let MultiAllocation =
|
|
{-
|
|
An allocation specialized to capturing multiple income streams within a given
|
|
time period (useful for instances where an allocation might change independent
|
|
of a change in income)
|
|
-}
|
|
Allocation Interval
|
|
|
|
let MultiAlloAmount = \(v : Type) -> Amount Interval v
|
|
|
|
let HourlyPeriod =
|
|
{-
|
|
Definition for a pay period denominated in hours
|
|
-}
|
|
{ hpAnnualHours :
|
|
{-
|
|
Number of hours in one year
|
|
-}
|
|
Natural
|
|
, hpDailyHours :
|
|
{-
|
|
Number of hours in one working day
|
|
-}
|
|
Natural
|
|
, hpWorkingDays :
|
|
{-
|
|
Days which count as working days
|
|
-}
|
|
List Weekday
|
|
}
|
|
|
|
let PeriodType =
|
|
{-
|
|
Type of pay period.
|
|
|
|
Hourly: pay period is denominated in hours
|
|
Daily: pay period is denominated in working days (specified in a list)
|
|
-}
|
|
< Hourly : HourlyPeriod | Daily : List Weekday >
|
|
|
|
let Period =
|
|
{-
|
|
Definition of a pay period
|
|
-} { pType :
|
|
{-
|
|
Type of pay period
|
|
-}
|
|
PeriodType
|
|
, pStart :
|
|
{-
|
|
Start date of the pay period. Must occur before first payment
|
|
in this income stream is dispersed.
|
|
-}
|
|
Gregorian
|
|
}
|
|
|
|
let Income =
|
|
{-
|
|
Means to compute an income stream and how to allocate it
|
|
-}
|
|
{ Type =
|
|
{ incGross :
|
|
{-
|
|
The value of the income stream.
|
|
-}
|
|
Double
|
|
, incCurrency :
|
|
{-
|
|
The currency in which the income stream is denominated.
|
|
-}
|
|
CurID
|
|
, incWhen :
|
|
{-
|
|
The dates on which the income stream is distributed.
|
|
-}
|
|
DatePat
|
|
, incPayPeriod :
|
|
{-
|
|
Defines the period of time over which this income was earned
|
|
(mostly used for taxes)
|
|
-}
|
|
Period
|
|
, incPretax : List (SingleAllocation PretaxValue)
|
|
, incTaxes : List (SingleAllocation TaxValue)
|
|
, incPosttax : List (SingleAllocation PosttaxValue)
|
|
, incFrom :
|
|
{-
|
|
The account in which the income is recorded.
|
|
|
|
This must be an income AcntID, and is the only place income
|
|
accounts may be specified in the entire budget.
|
|
-}
|
|
TaggedAcnt.Type
|
|
, incToBal :
|
|
{-
|
|
The account to which to send the remainder of the income stream
|
|
(if any) after all allocations have been applied.
|
|
-}
|
|
TaggedAcnt.Type
|
|
, incPriority : Integer
|
|
}
|
|
, default =
|
|
{ incPretax = [] : List (SingleAllocation PretaxValue)
|
|
, incTaxes = [] : List (SingleAllocation TaxValue)
|
|
, incPosttaxx = [] : List (SingleAllocation PosttaxValue)
|
|
, incPriority = +0
|
|
}
|
|
}
|
|
|
|
let AcntMatcher_ =
|
|
{-
|
|
Regex pattern by which matching account ids will be identified
|
|
-}
|
|
\(re : Type) ->
|
|
{ Type = { amPat : re, amInvert : Bool }, default.amInvert = False }
|
|
|
|
let AcntMatcher = AcntMatcher_ Text
|
|
|
|
let TransferMatcher_ =
|
|
{-
|
|
Means to match a transfer (which will be used to "clone" it in some
|
|
fashion)
|
|
-}
|
|
\(re : Type) ->
|
|
{ tmFrom : Optional (AcntMatcher_ re).Type
|
|
, tmTo : Optional (AcntMatcher_ re).Type
|
|
, tmDate :
|
|
{-
|
|
If given, means to match the date of a transfer.
|
|
-}
|
|
Optional DateMatcher
|
|
, tmVal :
|
|
{-
|
|
If given, means to match the value of a transfer.
|
|
-}
|
|
ValMatcher.Type
|
|
}
|
|
|
|
let TransferMatcher =
|
|
{ Type = TransferMatcher_ Text
|
|
, default =
|
|
{ tmFrom = None AcntMatcher.Type
|
|
, tmTo = None AcntMatcher.Type
|
|
, tmDate = None DateMatcher
|
|
, tmVal = ValMatcher.default
|
|
}
|
|
}
|
|
|
|
let ShadowTransfer =
|
|
{-
|
|
A transaction analogous to another transfer with given properties.
|
|
-}
|
|
{ stFrom :
|
|
{-
|
|
Source of this transfer
|
|
-}
|
|
TaggedAcnt.Type
|
|
, stTo :
|
|
{-
|
|
Destination of this transfer.
|
|
-}
|
|
TaggedAcnt.Type
|
|
, stCurrency :
|
|
{-
|
|
Currency of this transfer.
|
|
-}
|
|
CurID
|
|
, stDesc :
|
|
{-
|
|
Description of this transfer.
|
|
-}
|
|
Text
|
|
, stMatch :
|
|
{-
|
|
Means to match other transfers which will be used as the basis to
|
|
compute this transfer. The date is taken as-is, the value is
|
|
multiplied by a constant (see 'stRatio') and everything else is
|
|
specified in other fields of this type.
|
|
-}
|
|
TransferMatcher.Type
|
|
, stRatio :
|
|
{-
|
|
Fixed multipler to translate value of matched transfer to this one.
|
|
-}
|
|
Double
|
|
}
|
|
|
|
let BudgetTransfer =
|
|
{-
|
|
A manually specified transaction for a budget
|
|
-}
|
|
HistTransfer
|
|
|
|
let Budget =
|
|
{-
|
|
A hypothetical set of transactions (eg a "budget") to be generated
|
|
and inserted into the database.
|
|
-}
|
|
{ bgtLabel :
|
|
{-
|
|
A unique label for this budget.
|
|
|
|
Can be useful to compare multiple potential futures.
|
|
-}
|
|
Text
|
|
, bgtIncomes : List Income.Type
|
|
, bgtPretax : List (MultiAllocation PretaxValue)
|
|
, bgtTax : List (MultiAllocation TaxValue)
|
|
, bgtPosttax : List (MultiAllocation PosttaxValue)
|
|
, bgtTransfers : List BudgetTransfer
|
|
, bgtShadowTransfers : List ShadowTransfer
|
|
, bgtInterval : Optional Interval
|
|
}
|
|
|
|
in { CurID
|
|
, AcntID
|
|
, SqlConfig
|
|
, Currency
|
|
, Tag
|
|
, TagID
|
|
, Interval
|
|
, TemporalScope
|
|
, Gregorian
|
|
, GregorianM
|
|
, TimeUnit
|
|
, Weekday
|
|
, RepeatPat
|
|
, MDYPat
|
|
, ModPat
|
|
, WeekdayPat
|
|
, CronPat
|
|
, DatePat
|
|
, TxOpts
|
|
, TxOpts_
|
|
, StatementParser
|
|
, StatementParser_
|
|
, ValMatcher
|
|
, YMDMatcher
|
|
, DateMatcher
|
|
, FieldMatcher
|
|
, FieldMatcher_
|
|
, EntryNumGetter
|
|
, LinkedEntryNumGetter
|
|
, LinkedNumGetter
|
|
, Field
|
|
, FieldMap
|
|
, Entry
|
|
, FromEntryGetter
|
|
, ToEntryGetter
|
|
, EntryTextGetter
|
|
, EntryCurGetter
|
|
, EntryAcntGetter
|
|
, Statement
|
|
, History
|
|
, Transfer
|
|
, Income
|
|
, Budget
|
|
, Allocation
|
|
, Amount
|
|
, TransferMatcher_
|
|
, TransferMatcher
|
|
, ShadowTransfer
|
|
, TaggedAcnt
|
|
, AccountTree
|
|
, Account
|
|
, Placeholder
|
|
, PretaxValue
|
|
, PosttaxValue
|
|
, TaxBracket
|
|
, TaxProgression
|
|
, TaxMethod
|
|
, TaxValue
|
|
, TransferValue
|
|
, TransferType
|
|
, TxGetter
|
|
, TxSubGetter
|
|
, TxHalfGetter
|
|
, FromTxHalfGetter
|
|
, ToTxHalfGetter
|
|
, HistTransfer
|
|
, SingleAllocation
|
|
, MultiAllocation
|
|
, HourlyPeriod
|
|
, Period
|
|
, PeriodType
|
|
, TransferAmount
|
|
, MultiAlloAmount
|
|
, SingleAlloAmount
|
|
, AcntMatcher_
|
|
, AcntMatcher
|
|
, TxAmountSpec
|
|
, TxAmount1
|
|
, TxAmount2
|
|
}
|