+++ /dev/null
-#!/usr/bin/env runhaskell
-{-|
-hledger-range-voting [-f JOURNALFILE]
-
-Perform range voting calculus
-on the default or specified journal.
-|-}
-
-import Data.List
---import Data.List (mapAccumL)
-import Data.Maybe
-import Data.Ord
-import Data.Time.Calendar
-import System.Console.CmdArgs.Explicit
-import Text.Printf
-import qualified Data.Map as Map
-
-import Hledger
-import Hledger.Cli
-import Hledger.Cli.Options
--- import Prelude hiding (putStr)
-import Hledger.Utils.UTF8IOCompat (putStr)
-
-
-argsmode :: Mode RawOpts
-argsmode = (defCommandMode ["range-voting"])
- { modeHelp = "perform range voting"
- , modeGroupFlags = Group
- { groupNamed =
- [ ("Input",inputflags)
- , ("Reporting",reportflags)
- , ("Misc",helpflags)
- ]
- , groupUnnamed = []
- , groupHidden = []
- }
- }
-
-
--- like Register.summarisePostings
--- | Print various statistics for the journal.
-main :: IO ()
-main = do
- opts <- getCliOpts argsmode
- withJournalDo opts $
- \CliOpts{reportopts_=reportopts_} j -> do
- d <- getCurrentDay
- let q = queryFromOpts d reportopts_
- let l = ledgerFromJournal q j
- let reportspan = (ledgerDateSpan l) `spanDefaultsFrom` (queryDateSpan False q)
- let intervalspans = splitSpan (intervalFromOpts reportopts_) reportspan
- let s = intercalate "\n" $ map (showRangeVoting reportopts_{empty_=True} l d) intervalspans
- putStr s
-
-data Stats =
- Stats
- { stats_choice :: String
- , stats_sum :: Double
- , stats_blank_count :: Int
- , stats_null_count :: Int
- , stats_null_value :: Double
- , stats_values :: [(Int,Int)]
- }
-stats :: Stats
-stats = Stats
- { stats_choice = ""
- , stats_sum = 0.0
- , stats_blank_count = 0
- , stats_null_count = 0
- , stats_null_value = 0.0
- , stats_values = []
- }
-
-
-showRangeVoting :: ReportOpts -> Ledger -> Day -> DateSpan -> String
-showRangeVoting ropts l today span =
- unlines $ map (\(label,value) -> concatBottomPadded [printf fmt1 label, value]) main
- where
- fmt1 = "%-" ++ show w1 ++ "s: "
- -- fmt2 = "%-" ++ show w2 ++ "s"
- w1 = maximum $ map (length . fst) main
- -- w2 = maximum $ map (length . show . snd) main
- main =
- concat $
- [ [("Span vote (official)", printf "%s to %s (%d days)" (show $ (tdate opening)) (show $ (tdate2 opening)) votedays)
- ,("Span vote (actual)", printf "%s to %s (%d days)" (start span) (end span) days)
- ,("Cardinal", printf "%d" cardinal)
- ,("Values", "{"++(intercalate "," $ map (printf "%d") values)++"}")
- ,("Quorum", printf "%d/%d (%f%%)" votesnum votersnum (fromIntegral votesnum*100.0/fromIntegral votersnum::Double))
- ,("Blanks", printf "%d" $ foldl (\acc t -> acc + stats_blank_count t) 0 stats)
- ,("Nulls", printf "%d" $ foldl (\acc t -> acc + stats_null_count t) 0 stats)
- ,("Medians", "")
- ]
- , map (\t ->
- ( printf " - %s" (stats_choice t)
- , printf "sum: %+.f" (stats_sum t)
- ) ) medians
- , [("Choices", printf "%d" nchoices)
- ]
- , map
- (\t ->
- ( printf " - %s" (stats_choice t)
- , printf "sum: %+f = %d*(0:blank) + %d*(%+.2f:null) + %s"
- (stats_sum t)
- (stats_blank_count t)
- (stats_null_count t)
- (stats_null_value t)
- (intercalate " + " $ map (\(value,sum) -> printf "%d*(%+d)" sum value) (stats_values t))
- )
- ) stats
- --,("Commodities", printf "%s (%s)" (show $ length cs) (intercalate ", " cs))
- ]
- where
- j = ljournal l
- ts = sortBy (comparing tdate) $ filter (spanContainsDate span . tdate) $ jtxns j
- as = nub $ map paccount $ concatMap tpostings ts
- cs = Map.keys $ canonicalStyles $ concatMap amounts $ map pamount $ concatMap tpostings ts
- showelapsed Nothing = ""
- showelapsed (Just days) = printf " (%d %s)" days' direction
- where days' = abs days
- direction | days >= 0 = "days ago" :: String
- | otherwise = "days from now"
- start (DateSpan (Just d) _) = show d
- start _ = ""
- end (DateSpan _ (Just d)) = show d
- end _ = ""
- days = fromMaybe 0 $ daysInSpan span
- votedays = fromMaybe 0 $ daysInSpan
- (DateSpan
- (Just (tdate opening))
- (maybe Nothing Just (tdate2 opening)))
- acctnum = length as
- openings = filter ((== "Opening") . tdescription) $ ts
- opening | null openings = error' "\"Opening\" transaction is missing"
- | otherwise = head $ openings
- cardinals = filter ((== "Cardinal") . fst) $ ttags opening
- cardinal | null cardinals = error' "\"Cardinal\" tag is missing on \"Opening\" transaction"
- | otherwise = read $ snd $ head $ cardinals :: Int
- values | (cardinal `mod` 2 == 0) = delete 0 [-cardinal`div`2..cardinal`div`2]
- | otherwise = [-(cardinal-1)`div`2..(cardinal-1)`div`2]
- choices =
- map paccount $
- filter (isPrefixOf "Choice:" . paccount) $
- tpostings opening
-
- votes = filter ((== "Vote") . tdescription) $ ts
- stats =
- sortBy (\a b -> (stats_sum b) `compare` (stats_sum a)) $
- map (\s ->
- let mean = fromIntegral (foldl (\acc (v,c) -> acc + (v * c)) 0 (stats_values s)) / fromIntegral (length (stats_values s)) in
- s
- { stats_null_value = mean
- , stats_sum = (stats_sum s) + (mean * fromIntegral (stats_null_count s))
- }) $
- foldl (\s vote ->
- map (\s ->
- case filter ((== stats_choice s) . paccount) (tpostings vote) of
- [] -> s { stats_blank_count = stats_blank_count s + 1 }
- [choice_posting] ->
- case pamount choice_posting of
- Mixed [Amount{acommodity="", aquantity=q, aprice=NoPrice}] ->
- let i = floor q in
- if snd (properFraction q) == 0.0 && i `elem` values
- then s
- { stats_sum = stats_sum s + fromIntegral i
- , stats_values = map (\(v,c) -> if v == i then (v,c+1) else (v,c)) (stats_values s)
- }
- else s { stats_null_count = stats_null_count s + 1 } -- error' $ printf "TODO: null vote: invalid value: %f" q
- _ -> s { stats_null_count = stats_null_count s + 1 } -- error' "TODO: null vote: too much informations"
- _ -> s { stats_null_count = stats_null_count s + 1 }
- )
- s
- )
- [ Stats
- { stats_choice = choice
- , stats_sum = 0.0
- , stats_blank_count = 0
- , stats_null_count = 0
- , stats_null_value = 0.0
- , stats_values = [(value,0) | value<-values]
- }
- | choice<-choices ]
- votes
-
- medians =
- filter (\c -> stats_sum c `elem` medians) stats
- where medians =
- if length choices `mod` 2 == 0
- then map (stats_sum . (!!) stats) [nchoices `div` 2 - 1, nchoices `div` 2 + 1]
- else map (stats_sum . (!!) stats) [(nchoices - 1) `div` 2]
-
- nchoices = length choices
-
- -- choicesbalancesropts = ropts{query_="Choice:"}
- -- choicesbalances = tail $ fst $ balanceReport ropts (queryFromOpts today choicesbalancesropts) j
-
- voteras = filter (isPrefixOf "Voter:" . paccount) $ tpostings opening
- votests = filter ((== "Vote") . tdescription) $ ts
- votesnum = length votests
- votersnum = length voteras
- votechoicesps = filter (isPrefixOf "Choice:" . paccount) $ tpostings opening