{-|

Print some statistics for the journal.

-}

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}

module Hledger.Cli.Commands.Stats (
  statsmode
 ,stats
)
where

import Control.Monad (when)
import Data.Default (def)
import Data.List (intercalate, nub, sortOn)
import Data.List.Extra (nubSort)
import Data.Map qualified as Map
import Data.Maybe (fromMaybe)
import Data.HashSet (size, fromList)
import Data.Text qualified as T
import Data.Text.Lazy qualified as TL
import Data.Time.Calendar (Day, addDays, diffDays)
import Data.Time.Clock.POSIX (getPOSIXTime)
import GHC.Stats
import GitHash (tGitInfoCwdTry)
import System.Console.CmdArgs.Explicit hiding (Group)
import System.FilePath (takeFileName)
import System.Mem (performMajorGC)
import Text.Printf (printf)
import Text.Tabular.AsciiWide

import Hledger
import Hledger.Cli.CliOptions
import Hledger.Cli.Utils (writeOutput)
import Hledger.Cli.Version (packageversion, versionStringWith)


statsmode :: Mode RawOpts
statsmode = String
-> [Flag RawOpts]
-> [(String, [Flag RawOpts])]
-> [Flag RawOpts]
-> ([Arg RawOpts], Maybe (Arg RawOpts))
-> Mode RawOpts
hledgerCommandMode
  $(embedFileRelative "Hledger/Cli/Commands/Stats.txt")
  [ [String] -> (RawOpts -> RawOpts) -> String -> Flag RawOpts
forall a. [String] -> (a -> a) -> String -> Flag a
flagNone [String
"1"] (String -> RawOpts -> RawOpts
setboolopt String
"") String
"show a single line of output"
      -- Cli.hs converts -1 to --depth=1, no point giving it another name here
  , [String] -> (RawOpts -> RawOpts) -> String -> Flag RawOpts
forall a. [String] -> (a -> a) -> String -> Flag a
flagNone [String
"verbose",String
"v"] (String -> RawOpts -> RawOpts
setboolopt String
"verbose") String
"show more detailed output"
  ,[String] -> Update RawOpts -> String -> String -> Flag RawOpts
forall a. [String] -> Update a -> String -> String -> Flag a
flagReq  [String
"output-file",String
"o"] (\String
s RawOpts
opts -> RawOpts -> Either String RawOpts
forall a b. b -> Either a b
Right (RawOpts -> Either String RawOpts)
-> RawOpts -> Either String RawOpts
forall a b. (a -> b) -> a -> b
$ String -> String -> RawOpts -> RawOpts
setopt String
"output-file" String
s RawOpts
opts) String
"FILE" String
"write output to FILE."
  ]
  [(String, [Flag RawOpts])]
cligeneralflagsgroups1
  [Flag RawOpts]
hiddenflags
  ([], Arg RawOpts -> Maybe (Arg RawOpts)
forall a. a -> Maybe a
Just (Arg RawOpts -> Maybe (Arg RawOpts))
-> Arg RawOpts -> Maybe (Arg RawOpts)
forall a b. (a -> b) -> a -> b
$ String -> Arg RawOpts
argsFlag String
"[QUERY]")

-- like Register.summarisePostings
-- | Print various statistics for the journal.
stats :: CliOpts -> Journal -> IO ()
stats :: CliOpts -> Journal -> IO ()
stats opts :: CliOpts
opts@CliOpts{rawopts_ :: CliOpts -> RawOpts
rawopts_=RawOpts
rawopts, reportspec_ :: CliOpts -> ReportSpec
reportspec_=ReportSpec
rspec, POSIXTime
progstarttime_ :: POSIXTime
progstarttime_ :: CliOpts -> POSIXTime
progstarttime_} Journal
j = do
  t <- IO POSIXTime
getPOSIXTime
  -- the first lines - general journal stats for one or more periods
  let
    today = ReportSpec -> Day
_rsDay ReportSpec
rspec
    oneline = String -> RawOpts -> Int
intopt String
"depth" RawOpts
rawopts Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
1
    verbose = String -> RawOpts -> Bool
boolopt String
"verbose" RawOpts
rawopts
    q = ReportSpec -> Query
_rsQuery ReportSpec
rspec
    l = Query -> Journal -> Ledger
ledgerFromJournal Query
q Journal
j
    intervalspans = (DateSpan, Maybe DayPartition) -> Maybe DayPartition
forall a b. (a, b) -> b
snd ((DateSpan, Maybe DayPartition) -> Maybe DayPartition)
-> (DateSpan, Maybe DayPartition) -> Maybe DayPartition
forall a b. (a -> b) -> a -> b
$ Journal -> ReportSpec -> (DateSpan, Maybe DayPartition)
reportSpanBothDates Journal
j ReportSpec
rspec
    ismultiperiod = Maybe DayPartition -> Int
forall a. Maybe a -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length Maybe DayPartition
intervalspans Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
1
    (txts, tnums) = unzip . map (showLedgerStats verbose l today) $ maybeDayPartitionToDateSpans intervalspans
    out1 = (if Bool
ismultiperiod then String -> String
forall a. a -> a
id else String -> String
forall a. HasCallStack => [a] -> [a]
init) (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ [String] -> String
unlines [String]
txts

  -- the last line - overall performance stats, with memory info if available,
  -- in human-friendly or machine-friendly format
  -- normal:
  --  Runtime stats       : 0.14 s elapsed, 7606 txns/s
  --  Runtime stats       : 0.14 s elapsed, 7606 txns/s, 6 MB live, 18 MB alloc
  -- oneline:
  --  SHORTVERSION(<SPC><TAB>VALUE[<SPC>DESC])+
  --  1.50.99<SPC><TAB>hledger 1.50.99-g0835a2485-20251119, mac-aarch64<SPC><TAB>2025.journal<SPC><TAB>1.99 s elapsed<SPC><TAB>524 txns/s
  --  1.50.99<SPC><TAB>hledger 1.50.99-g0835a2485-20251119, mac-aarch64<SPC><TAB>2025.journal<SPC><TAB>1.99 s elapsed<SPC><TAB>524 txns/s<SPC><TAB>788 MB live<SPC><TAB>2172 MB alloc
  -- 
  rtsstats <- getRTSStatsEnabled
  (maxlivemb, maxinusemb) <- if rtsstats
  then do
    -- do one last garbage collection; probably little effect, hopefully little wasted time
    performMajorGC
    RTSStats{..} <- getRTSStats
    return (toMegabytes max_live_bytes, toMegabytes max_mem_in_use_bytes)
  else
    return (0,0)
  let
    (label, sep)
      | oneline   = (lstrip $ versionStringWith $$tGitInfoCwdTry False "" packageversion <> "\t", "\t")
      | otherwise = ("Runtime stats       : ", ", ")
    dt = POSIXTime
t POSIXTime -> POSIXTime -> POSIXTime
forall a. Num a => a -> a -> a
- POSIXTime
progstarttime_
    tnum = [Int] -> Int
forall a. Num a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum [Int]
tnums
    ss =
      [ String -> String
takeFileName (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ Journal -> String
journalFilePath Journal
j | Bool
oneline ]
      [String] -> [String] -> [String]
forall a. Semigroup a => a -> a -> a
<> [
       String -> Float -> String
forall r. PrintfType r => String -> r
printf String
"%.2f s elapsed" (POSIXTime -> Float
forall a b. (Real a, Fractional b) => a -> b
realToFrac POSIXTime
dt :: Float)
      ,String -> Float -> String
forall r. PrintfType r => String -> r
printf String
"%.0f txns/s" (Int -> Float
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
tnum Float -> Float -> Float
forall a. Fractional a => a -> a -> a
/ POSIXTime -> Float
forall a b. (Real a, Fractional b) => a -> b
realToFrac POSIXTime
dt :: Float)
      ]
      [String] -> [String] -> [String]
forall a. Semigroup a => a -> a -> a
<> if Bool
rtsstats then [
       String -> Float -> String
forall r. PrintfType r => String -> r
printf String
"%0.0f MB live" Float
maxlivemb
      ,String -> Float -> String
forall r. PrintfType r => String -> r
printf String
"%0.0f MB alloc" Float
maxinusemb
      -- printf "%0.0f MB avg live" (toMegabytes $ fromIntegral cumulative_live_bytes / fromIntegral major_gcs)
      ]
      else [
       String
"(add +RTS -T -RTS for more)"
      ]
    out2 = String
label String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate String
sep [String]
ss String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"\n"

  when (not oneline) $ writeOutput opts out1
  when (oneline && debugLevel>0) $ do
    let tabstops = String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate (Int -> Char -> String
forall a. Int -> a -> [a]
replicate Int
7 Char
' ') (Int -> String -> [String]
forall a. Int -> a -> [a]
replicate Int
21 String
".") String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"\n"
    writeOutput opts tabstops
  writeOutput opts $ (if ismultiperiod then "\n" else "") <> out2

toMegabytes :: a -> Float
toMegabytes a
n = a -> Float
forall a b. (Real a, Fractional b) => a -> b
realToFrac a
n Float -> Float -> Float
forall a. Fractional a => a -> a -> a
/ Float
1000000 ::Float  -- SI preferred definition, 10^6
-- toMebibytes n = realToFrac n / 1048576 ::Float  -- traditional computing definition, 2^20

-- | Generate multiline stats output, possibly verbose,
-- for the given ledger and date period and current date.
-- Also return the number of transactions in the period.
showLedgerStats :: Bool -> Ledger -> Day -> DateSpan -> (String, Int)
showLedgerStats :: Bool -> Ledger -> Day -> DateSpan -> (String, Int)
showLedgerStats Bool
verbose Ledger
l Day
today DateSpan
spn =
    ([String] -> String
unlines ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ ((Text, String) -> String) -> [(Text, String)] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (Text -> String
TL.unpack (Text -> String)
-> ((Text, String) -> Text) -> (Text, String) -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TableOpts -> Header Cell -> Text
renderRow TableOpts
forall a. Default a => a
def{tableBorders=False, borderSpaces=False} (Header Cell -> Text)
-> ((Text, String) -> Header Cell) -> (Text, String) -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Text, String) -> Header Cell
showRow) [(Text, String)]
stts
    ,Int
tnum)
  where
    showRow :: (Text, String) -> Header Cell
showRow (Text
label, String
val) = Properties -> [Header Cell] -> Header Cell
forall h. Properties -> [Header h] -> Header h
Group Properties
NoLine ([Header Cell] -> Header Cell) -> [Header Cell] -> Header Cell
forall a b. (a -> b) -> a -> b
$ (Text -> Header Cell) -> [Text] -> [Header Cell]
forall a b. (a -> b) -> [a] -> [b]
map (Cell -> Header Cell
forall h. h -> Header h
Header (Cell -> Header Cell) -> (Text -> Cell) -> Text -> Header Cell
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Align -> Text -> Cell
textCell Align
TopLeft)
      [Maybe Int -> Maybe Int -> Bool -> Bool -> Text -> Text
fitText (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
w) (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
w) Bool
False Bool
True Text
label Text -> Text -> Text
`T.append` Text
": ", String -> Text
T.pack String
val]
    w :: Int
w = Int
20  -- keep synced with labels above
    -- w = maximum $ map (T.length . fst) stts
    ([(Text, String)]
stts, Int
tnum) = ([
       (Text
"Main file", String
path' :: String) -- ++ " (from " ++ source ++ ")")
      ,(Text
"Included files", if Bool
verbose then [String] -> String
unlines [String]
includedpaths else Int -> String
forall a. Show a => a -> String
show ([String] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [String]
includedpaths))
      ,(Text
"Txns span", String -> String -> String -> Integer -> String
forall r. PrintfType r => String -> r
printf String
"%s to %s (%d days)" (DateSpan -> String
showstart DateSpan
spn) (DateSpan -> String
showend DateSpan
spn) Integer
days)
      ,(Text
"Last txn", String -> (Day -> String) -> Maybe Day -> String
forall b a. b -> (a -> b) -> Maybe a -> b
maybe String
"none" Day -> String
forall a. Show a => a -> String
show Maybe Day
lastdate String -> String -> String
forall a. [a] -> [a] -> [a]
++ Maybe Integer -> String
forall {a} {t}.
(IsString a, PrintfArg t, PrintfType a, Ord t, Num t) =>
Maybe t -> a
showelapsed Maybe Integer
lastelapsed)
      ,(Text
"Txns", String -> Int -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%d (%0.1f per day)" Int
tnum Double
txnrate)
      ,(Text
"Txns last 30 days", String -> Int -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%d (%0.1f per day)" Int
tnum30 Double
txnrate30)
      ,(Text
"Txns last 7 days", String -> Int -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%d (%0.1f per day)" Int
tnum7 Double
txnrate7)
      ,(Text
"Payees/descriptions", Int -> String
forall a. Show a => a -> String
show (Int -> String) -> Int -> String
forall a b. (a -> b) -> a -> b
$ HashSet Text -> Int
forall a. HashSet a -> Int
size (HashSet Text -> Int) -> HashSet Text -> Int
forall a b. (a -> b) -> a -> b
$ [Text] -> HashSet Text
forall a. (Eq a, Hashable a) => [a] -> HashSet a
fromList ([Text] -> HashSet Text) -> [Text] -> HashSet Text
forall a b. (a -> b) -> a -> b
$ (Transaction -> Text) -> [Transaction] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map (Transaction -> Text
tdescription) [Transaction]
ts)
      ,(Text
"Accounts", String -> Int -> Int -> String
forall r. PrintfType r => String -> r
printf String
"%d (depth %d)" Int
acctnum Int
acctdepth)
      ,(Text
"Commodities",   String -> String -> Text -> String
forall r. PrintfType r => String -> r
printf String
"%s%s" (Int -> String
forall a. Show a => a -> String
show (Int -> String) -> Int -> String
forall a b. (a -> b) -> a -> b
$ [Text] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Text]
cs)        (if Bool
verbose then Text
" (" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> [Text] -> Text
T.intercalate Text
", " [Text]
cs Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
")" else Text
""))
      ,(Text
"Market prices", String -> String -> Text -> String
forall r. PrintfType r => String -> r
printf String
"%s%s" (Int -> String
forall a. Show a => a -> String
show (Int -> String) -> Int -> String
forall a b. (a -> b) -> a -> b
$ [PriceDirective] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [PriceDirective]
mktprices) (if Bool
verbose then Text
" (" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> [Text] -> Text
T.intercalate Text
", " [Text]
mktpricecommodities Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
")" else Text
""))
    -- Txns this month     : %(monthtxns)s (last month in the same period: %(lastmonthtxns)s)
    -- Unmarked txns      : %(unmarked)s
    -- Days since reconciliation   : %(reconcileelapsed)s
    -- Days since last txn : %(recentelapsed)s
     ]
     ,Int
tnum1)
       where
         j :: Journal
j = Ledger -> Journal
ljournal Ledger
l
         path' :: String
path' = if Bool
verbose then String
path else String
".../" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String -> String
takeFileName String
path where path :: String
path = Journal -> String
journalFilePath Journal
j
         includedpaths :: [String]
includedpaths = Int -> [String] -> [String]
forall a. Int -> [a] -> [a]
drop Int
1 ([String] -> [String]) -> [String] -> [String]
forall a b. (a -> b) -> a -> b
$ Journal -> [String]
journalFilePaths Journal
j
         ts :: [Transaction]
ts = (Transaction -> Day) -> [Transaction] -> [Transaction]
forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn Transaction -> Day
tdate ([Transaction] -> [Transaction]) -> [Transaction] -> [Transaction]
forall a b. (a -> b) -> a -> b
$ (Transaction -> Bool) -> [Transaction] -> [Transaction]
forall a. (a -> Bool) -> [a] -> [a]
filter (DateSpan -> Day -> Bool
spanContainsDate DateSpan
spn (Day -> Bool) -> (Transaction -> Day) -> Transaction -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Transaction -> Day
tdate) ([Transaction] -> [Transaction]) -> [Transaction] -> [Transaction]
forall a b. (a -> b) -> a -> b
$ Journal -> [Transaction]
jtxns Journal
j
         as :: [Text]
as = [Text] -> [Text]
forall a. Eq a => [a] -> [a]
nub ([Text] -> [Text]) -> [Text] -> [Text]
forall a b. (a -> b) -> a -> b
$ (Posting -> Text) -> [Posting] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Posting -> Text
paccount ([Posting] -> [Text]) -> [Posting] -> [Text]
forall a b. (a -> b) -> a -> b
$ (Transaction -> [Posting]) -> [Transaction] -> [Posting]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Transaction -> [Posting]
tpostings [Transaction]
ts
         cs :: [Text]
cs = (String -> [Text])
-> (Map Text AmountStyle -> [Text])
-> Either String (Map Text AmountStyle)
-> [Text]
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either String -> [Text]
forall a. String -> a
error' Map Text AmountStyle -> [Text]
forall k a. Map k a -> [k]
Map.keys (Either String (Map Text AmountStyle) -> [Text])
-> Either String (Map Text AmountStyle) -> [Text]
forall a b. (a -> b) -> a -> b
$ [Amount] -> Either String (Map Text AmountStyle)
commodityStylesFromAmounts ([Amount] -> Either String (Map Text AmountStyle))
-> [Amount] -> Either String (Map Text AmountStyle)
forall a b. (a -> b) -> a -> b
$ (Posting -> [Amount]) -> [Posting] -> [Amount]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (MixedAmount -> [Amount]
amountsRaw (MixedAmount -> [Amount])
-> (Posting -> MixedAmount) -> Posting -> [Amount]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Posting -> MixedAmount
pamount) ([Posting] -> [Amount]) -> [Posting] -> [Amount]
forall a b. (a -> b) -> a -> b
$ (Transaction -> [Posting]) -> [Transaction] -> [Posting]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Transaction -> [Posting]
tpostings [Transaction]
ts  -- PARTIAL:
         lastdate :: Maybe Day
lastdate | [Transaction] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Transaction]
ts = Maybe Day
forall a. Maybe a
Nothing
                  | Bool
otherwise = Day -> Maybe Day
forall a. a -> Maybe a
Just (Day -> Maybe Day) -> Day -> Maybe Day
forall a b. (a -> b) -> a -> b
$ Transaction -> Day
tdate (Transaction -> Day) -> Transaction -> Day
forall a b. (a -> b) -> a -> b
$ [Transaction] -> Transaction
forall a. HasCallStack => [a] -> a
last [Transaction]
ts
         lastelapsed :: Maybe Integer
lastelapsed = (Day -> Integer) -> Maybe Day -> Maybe Integer
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Day -> Day -> Integer
diffDays Day
today) Maybe Day
lastdate
         showelapsed :: Maybe t -> a
showelapsed Maybe t
Nothing = a
""
         showelapsed (Just t
dys) = String -> t -> String -> a
forall r. PrintfType r => String -> r
printf String
" (%d %s)" t
dys' String
direction
                                   where dys' :: t
dys' = t -> t
forall a. Num a => a -> a
abs t
dys
                                         direction :: String
direction | t
dys t -> t -> Bool
forall a. Ord a => a -> a -> Bool
>= t
0 = String
"days ago" :: String
                                                   | Bool
otherwise = String
"days from now"
         tnum1 :: Int
tnum1 = [Transaction] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Transaction]
ts  -- Integer would be better
         showstart :: DateSpan -> String
showstart (DateSpan (Just EFDay
efd) Maybe EFDay
_) = Day -> String
forall a. Show a => a -> String
show (Day -> String) -> Day -> String
forall a b. (a -> b) -> a -> b
$ EFDay -> Day
fromEFDay EFDay
efd
         showstart DateSpan
_ = String
""
         showend :: DateSpan -> String
showend (DateSpan Maybe EFDay
_ (Just EFDay
efd)) = Day -> String
forall a. Show a => a -> String
show (Day -> String) -> Day -> String
forall a b. (a -> b) -> a -> b
$ EFDay -> Day
fromEFDay EFDay
efd
         showend DateSpan
_ = String
""
         days :: Integer
days = Integer -> Maybe Integer -> Integer
forall a. a -> Maybe a -> a
fromMaybe Integer
0 (Maybe Integer -> Integer) -> Maybe Integer -> Integer
forall a b. (a -> b) -> a -> b
$ DateSpan -> Maybe Integer
daysInSpan DateSpan
spn
         txnrate :: Double
txnrate | Integer
daysInteger -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
==Integer
0 = Double
0
                 | Bool
otherwise = Int -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
tnum1 Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ Integer -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral Integer
days :: Double
         tnum30 :: Int
tnum30 = [Transaction] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length ([Transaction] -> Int) -> [Transaction] -> Int
forall a b. (a -> b) -> a -> b
$ (Transaction -> Bool) -> [Transaction] -> [Transaction]
forall a. (a -> Bool) -> [a] -> [a]
filter Transaction -> Bool
withinlast30 [Transaction]
ts
         withinlast30 :: Transaction -> Bool
withinlast30 Transaction
t = Day
d Day -> Day -> Bool
forall a. Ord a => a -> a -> Bool
>= Integer -> Day -> Day
addDays (-Integer
30) Day
today Bool -> Bool -> Bool
&& (Day
dDay -> Day -> Bool
forall a. Ord a => a -> a -> Bool
<=Day
today) where d :: Day
d = Transaction -> Day
tdate Transaction
t
         txnrate30 :: Double
txnrate30 = Int -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
tnum30 Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ Double
30 :: Double
         tnum7 :: Int
tnum7 = [Transaction] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length ([Transaction] -> Int) -> [Transaction] -> Int
forall a b. (a -> b) -> a -> b
$ (Transaction -> Bool) -> [Transaction] -> [Transaction]
forall a. (a -> Bool) -> [a] -> [a]
filter Transaction -> Bool
withinlast7 [Transaction]
ts
         withinlast7 :: Transaction -> Bool
withinlast7 Transaction
t = Day
d Day -> Day -> Bool
forall a. Ord a => a -> a -> Bool
>= Integer -> Day -> Day
addDays (-Integer
7) Day
today Bool -> Bool -> Bool
&& (Day
dDay -> Day -> Bool
forall a. Ord a => a -> a -> Bool
<=Day
today) where d :: Day
d = Transaction -> Day
tdate Transaction
t
         txnrate7 :: Double
txnrate7 = Int -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
tnum7 Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ Double
7 :: Double
         acctnum :: Int
acctnum = [Text] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Text]
as
         acctdepth :: Int
acctdepth | [Text] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Text]
as = Int
0
                   | Bool
otherwise = [Int] -> Int
forall a. Ord a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
maximum ([Int] -> Int) -> [Int] -> Int
forall a b. (a -> b) -> a -> b
$ (Text -> Int) -> [Text] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Int
accountNameLevel [Text]
as
         mktprices :: [PriceDirective]
mktprices = Journal -> [PriceDirective]
jpricedirectives Journal
j
         mktpricecommodities :: [Text]
mktpricecommodities = [Text] -> [Text]
forall a. Ord a => [a] -> [a]
nubSort ([Text] -> [Text]) -> [Text] -> [Text]
forall a b. (a -> b) -> a -> b
$ (PriceDirective -> Text) -> [PriceDirective] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map PriceDirective -> Text
pdcommodity [PriceDirective]
mktprices