    {-|

    Convert amounts to some related value in various ways. This involves
    looking up historical market prices (exchange rates) between commodities.

    -}

    {-# LANGUAGE LambdaCase #-}
    {-# LANGUAGE NamedFieldPuns #-}
    {-# LANGUAGE OverloadedStrings #-}
    {-# LANGUAGE RecordWildCards #-}
    {-# LANGUAGE ScopedTypeVariables #-}
    {-# LANGUAGE DeriveGeneric #-}

    module Hledger.Data.Valuation (
       ConversionOp(..)
      ,ValuationType(..)
      ,PriceOracle
      ,journalPriceOracle
      ,mixedAmountToCost
      ,mixedAmountApplyValuation
      ,mixedAmountValueAtDate
      ,mixedAmountApplyGain
      ,mixedAmountGainAtDate
      ,marketPriceReverse
      ,priceDirectiveToMarketPrice
      ,amountPriceDirectiveFromCost
      ,valuationTypeValuationCommodity
      -- ,priceLookup
      ,tests_Valuation
    )
    where

    import Control.Applicative ((<|>))
    import Data.Function ((&))
    import Data.List (partition, intercalate, sortBy)
    import Data.Map qualified as M
    import Data.Set qualified as S
    import Data.Text qualified as T
    import Data.Time.Calendar (Day, fromGregorian)
    import Data.Maybe (fromMaybe, mapMaybe)
    import Data.MemoUgly (memo)
    import GHC.Generics (Generic)
    import Safe (headMay, lastMay)

    import Hledger.Utils
    import Hledger.Data.Types
    import Hledger.Data.Amount
    import Hledger.Data.Dates (nulldate)
    import Text.Printf (printf)
    import Data.Decimal (decimalPlaces, roundTo, Decimal)
    import Data.Word (Word8)

    ------------------------------------------------------------------------------
    -- Types

    -- | Which operation to perform on conversion transactions.
    -- (There was also an "infer equity postings" operation, but that is now done
    -- earlier, in journal finalisation.)
    data ConversionOp = NoConversionOp | ToCost
      deriving (Int -> ConversionOp -> ShowS
    [ConversionOp] -> ShowS
    ConversionOp -> [Char]
    (Int -> ConversionOp -> ShowS)
    -> (ConversionOp -> [Char])
    -> ([ConversionOp] -> ShowS)
    -> Show ConversionOp
    forall a.
    (Int -> a -> ShowS) -> (a -> [Char]) -> ([a] -> ShowS) -> Show a
    $cshowsPrec :: Int -> ConversionOp -> ShowS
    showsPrec :: Int -> ConversionOp -> ShowS
    $cshow :: ConversionOp -> [Char]
    show :: ConversionOp -> [Char]
    $cshowList :: [ConversionOp] -> ShowS
    showList :: [ConversionOp] -> ShowS
    Show,ConversionOp -> ConversionOp -> Bool
    (ConversionOp -> ConversionOp -> Bool)
    -> (ConversionOp -> ConversionOp -> Bool) -> Eq ConversionOp
    forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
    $c== :: ConversionOp -> ConversionOp -> Bool
    == :: ConversionOp -> ConversionOp -> Bool
    $c/= :: ConversionOp -> ConversionOp -> Bool
    /= :: ConversionOp -> ConversionOp -> Bool
    Eq)

    -- | What kind of value conversion should be done on amounts ?
    -- CLI: --value=then|end|now|DATE[,COMM]
    data ValuationType =
        AtThen     (Maybe CommoditySymbol)  -- ^ convert to default or given valuation commodity, using market prices at each posting's date
      | AtEnd      (Maybe CommoditySymbol)  -- ^ convert to default or given valuation commodity, using market prices at period end(s)
      | AtNow      (Maybe CommoditySymbol)  -- ^ convert to default or given valuation commodity, using current market prices
      | AtDate Day (Maybe CommoditySymbol)  -- ^ convert to default or given valuation commodity, using market prices on some date
      deriving (Int -> ValuationType -> ShowS
    [ValuationType] -> ShowS
    ValuationType -> [Char]
    (Int -> ValuationType -> ShowS)
    -> (ValuationType -> [Char])
    -> ([ValuationType] -> ShowS)
    -> Show ValuationType
    forall a.
    (Int -> a -> ShowS) -> (a -> [Char]) -> ([a] -> ShowS) -> Show a
    $cshowsPrec :: Int -> ValuationType -> ShowS
    showsPrec :: Int -> ValuationType -> ShowS
    $cshow :: ValuationType -> [Char]
    show :: ValuationType -> [Char]
    $cshowList :: [ValuationType] -> ShowS
    showList :: [ValuationType] -> ShowS
    Show,ValuationType -> ValuationType -> Bool
    (ValuationType -> ValuationType -> Bool)
    -> (ValuationType -> ValuationType -> Bool) -> Eq ValuationType
    forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
    $c== :: ValuationType -> ValuationType -> Bool
    == :: ValuationType -> ValuationType -> Bool
    $c/= :: ValuationType -> ValuationType -> Bool
    /= :: ValuationType -> ValuationType -> Bool
    Eq)

    valuationTypeValuationCommodity :: ValuationType -> Maybe CommoditySymbol
    valuationTypeValuationCommodity :: ValuationType -> Maybe CommoditySymbol
    valuationTypeValuationCommodity = \case
        AtThen   (Just CommoditySymbol
    c) -> CommoditySymbol -> Maybe CommoditySymbol
    forall a. a -> Maybe a
    Just CommoditySymbol
    c
        AtEnd    (Just CommoditySymbol
    c) -> CommoditySymbol -> Maybe CommoditySymbol
    forall a. a -> Maybe a
    Just CommoditySymbol
    c
        AtNow    (Just CommoditySymbol
    c) -> CommoditySymbol -> Maybe CommoditySymbol
    forall a. a -> Maybe a
    Just CommoditySymbol
    c
        AtDate Day
    _ (Just CommoditySymbol
    c) -> CommoditySymbol -> Maybe CommoditySymbol
    forall a. a -> Maybe a
    Just CommoditySymbol
    c
        ValuationType
    _                 -> Maybe CommoditySymbol
    forall a. Maybe a
    Nothing

    -- | A price oracle is a magic memoising function that efficiently
    -- looks up market prices (exchange rates) from one commodity to
    -- another (or if unspecified, to a default valuation commodity) on a
    -- given date.
    type PriceOracle = (Day, CommoditySymbol, Maybe CommoditySymbol) -> Maybe (CommoditySymbol, Quantity)

    -- | An index of market prices for efficient lookup by commodity pair and date.
    -- Maps each (from, to) commodity pair to a Map from date to the effective price,
    -- where declared prices take precedence over inferred prices on the same day.
    -- This allows O(log n) lookup per pair via M.lookupLE.
    type PriceIndex = M.Map (CommoditySymbol, CommoditySymbol) (M.Map Day MarketPrice)

    -- | Build a price index from declared and inferred market prices.
    -- This is O(n log n) but done only once, enabling fast lookups later.
    buildPriceIndex :: [MarketPrice] -> [MarketPrice] -> PriceIndex
    buildPriceIndex :: [MarketPrice] -> [MarketPrice] -> PriceIndex
    buildPriceIndex [MarketPrice]
    declaredprices [MarketPrice]
    inferredprices =
      let
        -- Label each price with precedence (declared=True > inferred=False) and parse order
        declaredprices' :: [(Day, Bool, Integer, MarketPrice)]
    declaredprices' = [(MarketPrice -> Day
    mpdate MarketPrice
    p, Bool
    True, Integer
    i, MarketPrice
    p) | (Integer
    i, MarketPrice
    p) <- [Integer] -> [MarketPrice] -> [(Integer, MarketPrice)]
    forall a b. [a] -> [b] -> [(a, b)]
    zip [Integer
    1..] [MarketPrice]
    declaredprices]
        inferredprices' :: [(Day, Bool, Integer, MarketPrice)]
    inferredprices' = [(MarketPrice -> Day
    mpdate MarketPrice
    p, Bool
    False, Integer
    i, MarketPrice
    p) | (Integer
    i, MarketPrice
    p) <- [Integer] -> [MarketPrice] -> [(Integer, MarketPrice)]
    forall a b. [a] -> [b] -> [(a, b)]
    zip [Integer
    1..] [MarketPrice]
    inferredprices]
        allprices :: [(Day, Bool, Integer, MarketPrice)]
    allprices = [(Day, Bool, Integer, MarketPrice)]
    declaredprices' [(Day, Bool, Integer, MarketPrice)]
    -> [(Day, Bool, Integer, MarketPrice)]
    -> [(Day, Bool, Integer, MarketPrice)]
    forall a. [a] -> [a] -> [a]
    ++ [(Day, Bool, Integer, MarketPrice)]
    inferredprices'
        -- Group by commodity pair
        grouped :: Map
      (CommoditySymbol, CommoditySymbol)
      [(Day, Bool, Integer, MarketPrice)]
    grouped = ([(Day, Bool, Integer, MarketPrice)]
     -> [(Day, Bool, Integer, MarketPrice)]
     -> [(Day, Bool, Integer, MarketPrice)])
    -> [((CommoditySymbol, CommoditySymbol),
         [(Day, Bool, Integer, MarketPrice)])]
    -> Map
         (CommoditySymbol, CommoditySymbol)
         [(Day, Bool, Integer, MarketPrice)]
    forall k a. Ord k => (a -> a -> a) -> [(k, a)] -> Map k a
    M.fromListWith [(Day, Bool, Integer, MarketPrice)]
    -> [(Day, Bool, Integer, MarketPrice)]
    -> [(Day, Bool, Integer, MarketPrice)]
    forall a. [a] -> [a] -> [a]
    (++)
          [((MarketPrice -> CommoditySymbol
    mpfrom MarketPrice
    p, MarketPrice -> CommoditySymbol
    mpto MarketPrice
    p), [(Day
    d, Bool
    prec, Integer
    order, MarketPrice
    p)]) | (Day
    d, Bool
    prec, Integer
    order, MarketPrice
    p) <- [(Day, Bool, Integer, MarketPrice)]
    allprices]
        -- Build inner Map: sort ascending by (date, prec, order), then M.fromList
        -- keeps the last entry per date (highest precedence/parseorder wins)
        buildInnerMap :: [(a, b, c, b)] -> Map a b
    buildInnerMap [(a, b, c, b)]
    prices =
          [(a, b, c, b)]
    prices
          [(a, b, c, b)]
    -> ([(a, b, c, b)] -> [(a, b, c, b)]) -> [(a, b, c, b)]
    forall a b. a -> (a -> b) -> b
    & ((a, b, c, b) -> (a, b, c, b) -> Ordering)
    -> [(a, b, c, b)] -> [(a, b, c, b)]
    forall a. (a -> a -> Ordering) -> [a] -> [a]
    sortBy (a, b, c, b) -> (a, b, c, b) -> Ordering
    forall a. Ord a => a -> a -> Ordering
    compare
          [(a, b, c, b)] -> ([(a, b, c, b)] -> [(a, b)]) -> [(a, b)]
    forall a b. a -> (a -> b) -> b
    & ((a, b, c, b) -> (a, b)) -> [(a, b, c, b)] -> [(a, b)]
    forall a b. (a -> b) -> [a] -> [b]
    map (\(a
    d, b
    _, c
    _, b
    p) -> (a
    d, b
    p))
          [(a, b)] -> ([(a, b)] -> Map a b) -> Map a b
    forall a b. a -> (a -> b) -> b
    & [(a, b)] -> Map a b
    forall k a. Ord k => [(k, a)] -> Map k a
    M.fromList
      in
        ([(Day, Bool, Integer, MarketPrice)] -> Map Day MarketPrice)
    -> Map
         (CommoditySymbol, CommoditySymbol)
         [(Day, Bool, Integer, MarketPrice)]
    -> PriceIndex
    forall a b k. (a -> b) -> Map k a -> Map k b
    M.map [(Day, Bool, Integer, MarketPrice)] -> Map Day MarketPrice
    forall {a} {b} {c} {b}.
    (Ord a, Ord b, Ord c, Ord b) =>
    [(a, b, c, b)] -> Map a b
    buildInnerMap Map
      (CommoditySymbol, CommoditySymbol)
      [(Day, Bool, Integer, MarketPrice)]
    grouped

    -- | Look up effective prices for all commodity pairs at a given date using the index.
    -- Returns at most one price per commodity pair: the latest price on or before the date.
    -- O(pairs × log n) where n is the number of prices per pair.
    lookupEffectivePricesFromIndex :: Day -> PriceIndex -> [MarketPrice]
    lookupEffectivePricesFromIndex :: Day -> PriceIndex -> [MarketPrice]
    lookupEffectivePricesFromIndex Day
    d PriceIndex
    idx =
      (Map Day MarketPrice -> Maybe MarketPrice)
    -> [Map Day MarketPrice] -> [MarketPrice]
    forall a b. (a -> Maybe b) -> [a] -> [b]
    mapMaybe (((Day, MarketPrice) -> MarketPrice)
    -> Maybe (Day, MarketPrice) -> Maybe MarketPrice
    forall a b. (a -> b) -> Maybe a -> Maybe b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap (Day, MarketPrice) -> MarketPrice
    forall a b. (a, b) -> b
    snd (Maybe (Day, MarketPrice) -> Maybe MarketPrice)
    -> (Map Day MarketPrice -> Maybe (Day, MarketPrice))
    -> Map Day MarketPrice
    -> Maybe MarketPrice
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Day -> Map Day MarketPrice -> Maybe (Day, MarketPrice)
    forall k v. Ord k => k -> Map k v -> Maybe (k, v)
    M.lookupLE Day
    d) (PriceIndex -> [Map Day MarketPrice]
    forall k a. Map k a -> [a]
    M.elems PriceIndex
    idx)

    -- | Index for default valuation commodity lookup.
    -- Maps source commodity to a map of (date -> destination), supporting O(log n) lookup
    -- of the latest destination commodity on or before any given date.
    type DefaultValuationIndex = M.Map CommoditySymbol (M.Map Day CommoditySymbol)

    -- | Build an index for default valuation commodity lookup from a list of market prices.
    buildDefaultValuationIndex :: [MarketPrice] -> DefaultValuationIndex
    buildDefaultValuationIndex :: [MarketPrice] -> DefaultValuationIndex
    buildDefaultValuationIndex [MarketPrice]
    prices =
      let
        -- Label with parse order
        labeled :: [(CommoditySymbol, Day, Integer, CommoditySymbol)]
    labeled = [(MarketPrice -> CommoditySymbol
    mpfrom MarketPrice
    p, MarketPrice -> Day
    mpdate MarketPrice
    p, Integer
    i, MarketPrice -> CommoditySymbol
    mpto MarketPrice
    p) | (Integer
    i, MarketPrice
    p) <- [Integer] -> [MarketPrice] -> [(Integer, MarketPrice)]
    forall a b. [a] -> [b] -> [(a, b)]
    zip [Integer
    1..] [MarketPrice]
    prices]
        -- Group by source commodity
        grouped :: Map CommoditySymbol [(Day, Integer, CommoditySymbol)]
    grouped = ([(Day, Integer, CommoditySymbol)]
     -> [(Day, Integer, CommoditySymbol)]
     -> [(Day, Integer, CommoditySymbol)])
    -> [(CommoditySymbol, [(Day, Integer, CommoditySymbol)])]
    -> Map CommoditySymbol [(Day, Integer, CommoditySymbol)]
    forall k a. Ord k => (a -> a -> a) -> [(k, a)] -> Map k a
    M.fromListWith [(Day, Integer, CommoditySymbol)]
    -> [(Day, Integer, CommoditySymbol)]
    -> [(Day, Integer, CommoditySymbol)]
    forall a. [a] -> [a] -> [a]
    (++) [(CommoditySymbol
    from, [(Day
    d, Integer
    ord, CommoditySymbol
    to)]) | (CommoditySymbol
    from, Day
    d, Integer
    ord, CommoditySymbol
    to) <- [(CommoditySymbol, Day, Integer, CommoditySymbol)]
    labeled]
        -- Build inner Map: sort by (date, parseorder), then M.fromList keeps last (highest parseorder per date)
        buildInnerMap :: [(k, b, a)] -> Map k a
    buildInnerMap [(k, b, a)]
    entries = [(k, a)] -> Map k a
    forall k a. Ord k => [(k, a)] -> Map k a
    M.fromList [(k
    d, a
    to) | (k
    d, b
    _, a
    to) <- ((k, b, a) -> (k, b, a) -> Ordering) -> [(k, b, a)] -> [(k, b, a)]
    forall a. (a -> a -> Ordering) -> [a] -> [a]
    sortBy (k, b, a) -> (k, b, a) -> Ordering
    forall a. Ord a => a -> a -> Ordering
    compare [(k, b, a)]
    entries]
      in
        ([(Day, Integer, CommoditySymbol)] -> Map Day CommoditySymbol)
    -> Map CommoditySymbol [(Day, Integer, CommoditySymbol)]
    -> DefaultValuationIndex
    forall a b k. (a -> b) -> Map k a -> Map k b
    M.map [(Day, Integer, CommoditySymbol)] -> Map Day CommoditySymbol
    forall {k} {b} {a}. (Ord k, Ord b, Ord a) => [(k, b, a)] -> Map k a
    buildInnerMap Map CommoditySymbol [(Day, Integer, CommoditySymbol)]
    grouped

    -- | Combined indexes for efficient price lookup.
    data PriceIndexes = PriceIndexes
      { PriceIndexes -> PriceIndex
    piForward :: !PriceIndex                    -- ^ Index for forward prices (declared + inferred)
      , PriceIndexes -> DefaultValuationIndex
    piDeclaredDefault :: !DefaultValuationIndex -- ^ Index for declared prices (for default valuation)
      , PriceIndexes -> DefaultValuationIndex
    piInferredDefault :: !DefaultValuationIndex -- ^ Index for inferred prices (fallback for default valuation)
      }

    -- | Build all price indexes from declared and inferred market prices.
    -- This is O(n log n) but done only once.
    buildPriceIndexes :: [MarketPrice] -> [MarketPrice] -> PriceIndexes
    buildPriceIndexes :: [MarketPrice] -> [MarketPrice] -> PriceIndexes
    buildPriceIndexes [MarketPrice]
    declaredprices [MarketPrice]
    inferredprices = PriceIndexes
      { piForward :: PriceIndex
    piForward = [MarketPrice] -> [MarketPrice] -> PriceIndex
    buildPriceIndex [MarketPrice]
    declaredprices [MarketPrice]
    inferredprices
      , piDeclaredDefault :: DefaultValuationIndex
    piDeclaredDefault = [MarketPrice] -> DefaultValuationIndex
    buildDefaultValuationIndex [MarketPrice]
    declaredprices
      , piInferredDefault :: DefaultValuationIndex
    piInferredDefault = [MarketPrice] -> DefaultValuationIndex
    buildDefaultValuationIndex [MarketPrice]
    inferredprices
      }

    -- | Look up default valuation commodities for all source commodities at a given date.
    -- Fallback logic: declared at date d, then declared at any date, then inferred at date d.
    lookupDefaultValuations :: Day -> PriceIndexes -> M.Map CommoditySymbol CommoditySymbol
    lookupDefaultValuations :: Day -> PriceIndexes -> Map CommoditySymbol CommoditySymbol
    lookupDefaultValuations Day
    d PriceIndexes{PriceIndex
    DefaultValuationIndex
    piForward :: PriceIndexes -> PriceIndex
    piDeclaredDefault :: PriceIndexes -> DefaultValuationIndex
    piInferredDefault :: PriceIndexes -> DefaultValuationIndex
    piForward :: PriceIndex
    piDeclaredDefault :: DefaultValuationIndex
    piInferredDefault :: DefaultValuationIndex
    ..} =
        Map CommoditySymbol CommoditySymbol
    -> Maybe (Map CommoditySymbol CommoditySymbol)
    -> Map CommoditySymbol CommoditySymbol
    forall a. a -> Maybe a -> a
    fromMaybe Map CommoditySymbol CommoditySymbol
    fallback (Maybe (Map CommoditySymbol CommoditySymbol)
    tryDeclaredAtDate Maybe (Map CommoditySymbol CommoditySymbol)
    -> Maybe (Map CommoditySymbol CommoditySymbol)
    -> Maybe (Map CommoditySymbol CommoditySymbol)
    forall a. Maybe a -> Maybe a -> Maybe a
    forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
    <|> Maybe (Map CommoditySymbol CommoditySymbol)
    tryDeclaredLatest)
      where
        nonEmpty :: Map k a -> Maybe (Map k a)
    nonEmpty Map k a
    m = if Map k a -> Bool
    forall k a. Map k a -> Bool
    M.null Map k a
    m then Maybe (Map k a)
    forall a. Maybe a
    Nothing else Map k a -> Maybe (Map k a)
    forall a. a -> Maybe a
    Just Map k a
    m

        tryDeclaredAtDate :: Maybe (Map CommoditySymbol CommoditySymbol)
    tryDeclaredAtDate = Map CommoditySymbol CommoditySymbol
    -> Maybe (Map CommoditySymbol CommoditySymbol)
    forall {k} {a}. Map k a -> Maybe (Map k a)
    nonEmpty (Map CommoditySymbol CommoditySymbol
     -> Maybe (Map CommoditySymbol CommoditySymbol))
    -> Map CommoditySymbol CommoditySymbol
    -> Maybe (Map CommoditySymbol CommoditySymbol)
    forall a b. (a -> b) -> a -> b
    $ Maybe Day
    -> DefaultValuationIndex -> Map CommoditySymbol CommoditySymbol
    forall {a} {k} {b}. Ord a => Maybe a -> Map k (Map a b) -> Map k b
    lookupDefaults (Day -> Maybe Day
    forall a. a -> Maybe a
    Just Day
    d) DefaultValuationIndex
    piDeclaredDefault
        tryDeclaredLatest :: Maybe (Map CommoditySymbol CommoditySymbol)
    tryDeclaredLatest = Map CommoditySymbol CommoditySymbol
    -> Maybe (Map CommoditySymbol CommoditySymbol)
    forall {k} {a}. Map k a -> Maybe (Map k a)
    nonEmpty (Map CommoditySymbol CommoditySymbol
     -> Maybe (Map CommoditySymbol CommoditySymbol))
    -> Map CommoditySymbol CommoditySymbol
    -> Maybe (Map CommoditySymbol CommoditySymbol)
    forall a b. (a -> b) -> a -> b
    $ Maybe Day
    -> DefaultValuationIndex -> Map CommoditySymbol CommoditySymbol
    forall {a} {k} {b}. Ord a => Maybe a -> Map k (Map a b) -> Map k b
    lookupDefaults Maybe Day
    forall a. Maybe a
    Nothing  DefaultValuationIndex
    piDeclaredDefault
        fallback :: Map CommoditySymbol CommoditySymbol
    fallback          = Maybe Day
    -> DefaultValuationIndex -> Map CommoditySymbol CommoditySymbol
    forall {a} {k} {b}. Ord a => Maybe a -> Map k (Map a b) -> Map k b
    lookupDefaults (Day -> Maybe Day
    forall a. a -> Maybe a
    Just Day
    d) DefaultValuationIndex
    piInferredDefault

        lookupDefaults :: Maybe a -> Map k (Map a b) -> Map k b
    lookupDefaults Maybe a
    mdate = (Map a b -> Maybe b) -> Map k (Map a b) -> Map k b
    forall a b k. (a -> Maybe b) -> Map k a -> Map k b
    M.mapMaybe ((Map a b -> Maybe b) -> Map k (Map a b) -> Map k b)
    -> (Map a b -> Maybe b) -> Map k (Map a b) -> Map k b
    forall a b. (a -> b) -> a -> b
    $ \Map a b
    innerMap ->
          case Maybe a
    mdate of
            Maybe a
    Nothing -> (a, b) -> b
    forall a b. (a, b) -> b
    snd ((a, b) -> b) -> Maybe (a, b) -> Maybe b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> Map a b -> Maybe (a, b)
    forall k a. Map k a -> Maybe (k, a)
    M.lookupMax Map a b
    innerMap
            Just a
    dt -> (a, b) -> b
    forall a b. (a, b) -> b
    snd ((a, b) -> b) -> Maybe (a, b) -> Maybe b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> a -> Map a b -> Maybe (a, b)
    forall k v. Ord k => k -> Map k v -> Maybe (k, v)
    M.lookupLE a
    dt Map a b
    innerMap

    -- | Generate a price oracle (memoising price lookup function) from a
    -- journal's directive-declared and transaction-inferred market
    -- prices. For best performance, generate this only once per journal,
    -- reusing it across reports if there are more than one, as
    -- compoundBalanceCommand does.
    -- The boolean argument is whether to infer market prices from
    -- transactions or not.
    journalPriceOracle :: Bool -> Journal -> PriceOracle
    journalPriceOracle :: Bool -> Journal -> PriceOracle
    journalPriceOracle Bool
    infer Journal{[PriceDirective]
    jpricedirectives :: [PriceDirective]
    jpricedirectives :: Journal -> [PriceDirective]
    jpricedirectives, [MarketPrice]
    jinferredmarketprices :: [MarketPrice]
    jinferredmarketprices :: Journal -> [MarketPrice]
    jinferredmarketprices} =
      let
        declaredprices :: [MarketPrice]
    declaredprices = (PriceDirective -> MarketPrice)
    -> [PriceDirective] -> [MarketPrice]
    forall a b. (a -> b) -> [a] -> [b]
    map PriceDirective -> MarketPrice
    priceDirectiveToMarketPrice [PriceDirective]
    jpricedirectives
        inferredprices :: [MarketPrice]
    inferredprices =
          (if Bool
    infer then [MarketPrice]
    jinferredmarketprices else [])
          [MarketPrice] -> ([MarketPrice] -> [MarketPrice]) -> [MarketPrice]
    forall a b. a -> (a -> b) -> b
    & [Char] -> [MarketPrice] -> [MarketPrice]
    forall a. [Char] -> a -> a
    dbg2Msg ([Char]
    "use prices inferred from costs? " [Char] -> ShowS
    forall a. Semigroup a => a -> a -> a
    <> if Bool
    infer then [Char]
    "yes" else [Char]
    "no")
        -- Build indexes once for all lookups
        indexes :: PriceIndexes
    indexes = [MarketPrice] -> [MarketPrice] -> PriceIndexes
    buildPriceIndexes [MarketPrice]
    declaredprices [MarketPrice]
    inferredprices
        makepricegraph :: Day -> PriceGraph
    makepricegraph = (Day -> PriceGraph) -> Day -> PriceGraph
    forall a b. Ord a => (a -> b) -> a -> b
    memo ((Day -> PriceGraph) -> Day -> PriceGraph)
    -> (Day -> PriceGraph) -> Day -> PriceGraph
    forall a b. (a -> b) -> a -> b
    $ PriceIndexes -> Day -> PriceGraph
    makePriceGraph PriceIndexes
    indexes
      in
        PriceOracle -> PriceOracle
    forall a b. Ord a => (a -> b) -> a -> b
    memo (PriceOracle -> PriceOracle) -> PriceOracle -> PriceOracle
    forall a b. (a -> b) -> a -> b
    $ (Day
     -> CommoditySymbol
     -> Maybe CommoditySymbol
     -> Maybe (CommoditySymbol, Quantity))
    -> PriceOracle
    forall a b c d. (a -> b -> c -> d) -> (a, b, c) -> d
    uncurry3 ((Day
      -> CommoditySymbol
      -> Maybe CommoditySymbol
      -> Maybe (CommoditySymbol, Quantity))
     -> PriceOracle)
    -> (Day
        -> CommoditySymbol
        -> Maybe CommoditySymbol
        -> Maybe (CommoditySymbol, Quantity))
    -> PriceOracle
    forall a b. (a -> b) -> a -> b
    $ (Day -> PriceGraph)
    -> Day
    -> CommoditySymbol
    -> Maybe CommoditySymbol
    -> Maybe (CommoditySymbol, Quantity)
    priceLookup Day -> PriceGraph
    makepricegraph

    priceDirectiveToMarketPrice :: PriceDirective -> MarketPrice
    priceDirectiveToMarketPrice :: PriceDirective -> MarketPrice
    priceDirectiveToMarketPrice PriceDirective{CommoditySymbol
    SourcePos
    Day
    Amount
    pdsourcepos :: SourcePos
    pddate :: Day
    pdcommodity :: CommoditySymbol
    pdamount :: Amount
    pdamount :: PriceDirective -> Amount
    pdcommodity :: PriceDirective -> CommoditySymbol
    pddate :: PriceDirective -> Day
    pdsourcepos :: PriceDirective -> SourcePos
    ..} =
      MarketPrice{ mpdate :: Day
    mpdate = Day
    pddate
                 , mpfrom :: CommoditySymbol
    mpfrom = CommoditySymbol
    pdcommodity
                 , mpto :: CommoditySymbol
    mpto   = Amount -> CommoditySymbol
    acommodity Amount
    pdamount
                 , mprate :: Quantity
    mprate = Amount -> Quantity
    aquantity Amount
    pdamount
                 }

    -- | Infer a market price from the given amount and its cost (if any),
    -- and make a corresponding price directive on the given date.
    -- The price's display precision will be set to show all significant
    -- decimal digits; or if they seem to be infinite, defaultPrecisionLimit.
    amountPriceDirectiveFromCost :: Day -> Amount -> Maybe PriceDirective
    amountPriceDirectiveFromCost :: Day -> Amount -> Maybe PriceDirective
    amountPriceDirectiveFromCost Day
    d amt :: Amount
    amt@Amount{acommodity :: Amount -> CommoditySymbol
    acommodity=CommoditySymbol
    fromcomm, aquantity :: Amount -> Quantity
    aquantity=Quantity
    n} = case Amount -> Maybe AmountCost
    acost Amount
    amt of
        Just (UnitCost Amount
    u)           -> PriceDirective -> Maybe PriceDirective
    forall a. a -> Maybe a
    Just (PriceDirective -> Maybe PriceDirective)
    -> PriceDirective -> Maybe PriceDirective
    forall a b. (a -> b) -> a -> b
    $ PriceDirective
    pd{pdamount=u}
        Just (TotalCost Amount
    t) | Quantity
    n Quantity -> Quantity -> Bool
    forall a. Eq a => a -> a -> Bool
    /= Quantity
    0 -> PriceDirective -> Maybe PriceDirective
    forall a. a -> Maybe a
    Just (PriceDirective -> Maybe PriceDirective)
    -> PriceDirective -> Maybe PriceDirective
    forall a b. (a -> b) -> a -> b
    $ PriceDirective
    pd{pdamount=u}
          where u :: Amount
    u = Maybe Word8 -> Amount -> Amount
    amountSetFullPrecisionUpTo Maybe Word8
    forall a. Maybe a
    Nothing (Amount -> Amount) -> Amount -> Amount
    forall a b. (a -> b) -> a -> b
    $ Quantity -> Amount -> Amount
    divideAmount Quantity
    n Amount
    t
        Maybe AmountCost
    _                            -> Maybe PriceDirective
    forall a. Maybe a
    Nothing
      where
        pd :: PriceDirective
    pd = PriceDirective{pdsourcepos :: SourcePos
    pdsourcepos=SourcePos
    nullsourcepos, pddate :: Day
    pddate=Day
    d, pdcommodity :: CommoditySymbol
    pdcommodity=CommoditySymbol
    fromcomm, pdamount :: Amount
    pdamount=Amount
    nullamt}

    ------------------------------------------------------------------------------
    -- Converting things to value

    -- | Convert all component amounts to cost/selling price if requested, and style them.
    mixedAmountToCost :: M.Map CommoditySymbol AmountStyle -> ConversionOp -> MixedAmount -> MixedAmount
    mixedAmountToCost :: Map CommoditySymbol AmountStyle
    -> ConversionOp -> MixedAmount -> MixedAmount
    mixedAmountToCost Map CommoditySymbol AmountStyle
    styles ConversionOp
    cost = (Amount -> Amount) -> MixedAmount -> MixedAmount
    mapMixedAmount (Map CommoditySymbol AmountStyle -> ConversionOp -> Amount -> Amount
    amountToCost Map CommoditySymbol AmountStyle
    styles ConversionOp
    cost)

    -- | Apply a specified valuation to this mixed amount, using the
    -- provided price oracle, commodity styles, and reference dates.
    -- See amountApplyValuation.
    mixedAmountApplyValuation :: PriceOracle -> M.Map CommoditySymbol AmountStyle -> Day -> Day -> Day -> ValuationType -> MixedAmount -> MixedAmount
    mixedAmountApplyValuation :: PriceOracle
    -> Map CommoditySymbol AmountStyle
    -> Day
    -> Day
    -> Day
    -> ValuationType
    -> MixedAmount
    -> MixedAmount
    mixedAmountApplyValuation PriceOracle
    priceoracle Map CommoditySymbol AmountStyle
    styles Day
    periodlast Day
    today Day
    postingdate ValuationType
    v =
      (Amount -> Amount) -> MixedAmount -> MixedAmount
    mapMixedAmount (PriceOracle
    -> Map CommoditySymbol AmountStyle
    -> Day
    -> Day
    -> Day
    -> ValuationType
    -> Amount
    -> Amount
    amountApplyValuation PriceOracle
    priceoracle Map CommoditySymbol AmountStyle
    styles Day
    periodlast Day
    today Day
    postingdate ValuationType
    v)

    -- | Convert an Amount to its cost if requested, and style it appropriately.
    amountToCost :: M.Map CommoditySymbol AmountStyle -> ConversionOp -> Amount -> Amount
    amountToCost :: Map CommoditySymbol AmountStyle -> ConversionOp -> Amount -> Amount
    amountToCost Map CommoditySymbol AmountStyle
    styles ConversionOp
    ToCost         = Map CommoditySymbol AmountStyle -> Amount -> Amount
    forall a. HasAmounts a => Map CommoditySymbol AmountStyle -> a -> a
    styleAmounts Map CommoditySymbol AmountStyle
    styles (Amount -> Amount) -> (Amount -> Amount) -> Amount -> Amount
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Amount -> Amount
    amountCost
    amountToCost Map CommoditySymbol AmountStyle
    _      ConversionOp
    NoConversionOp = Amount -> Amount
    forall a. a -> a
    id

    -- | Apply a specified valuation to this amount, using the provided
    -- price oracle, and reference dates. Also fix up its display style
    -- using the provided commodity styles.
    --
    -- When the valuation requires converting to another commodity, a
    -- valuation (conversion) date is chosen based on the valuation type
    -- and the provided reference dates. It will be one of:
    --
    -- - the date of the posting itself (--value=then)
    --
    -- - the provided "period end" date - this is typically the last day
    --   of a subperiod (--value=end with a multi-period report), or of
    --   the specified report period or the journal (--value=end with a
    --   single-period report).
    --
    -- - the provided "today" date (--value=now).
    --
    -- - a fixed date specified by the ValuationType itself
    --   (--value=DATE).
    --
    -- This is all a bit complicated. See the reference doc at
    -- https://hledger.org/hledger.html#effect-of-valuation-on-reports
    -- (hledger_options.m4.md "Effect of valuation on reports"), and #1083.
    --
    amountApplyValuation :: PriceOracle -> M.Map CommoditySymbol AmountStyle -> Day -> Day -> Day -> ValuationType -> Amount -> Amount
    amountApplyValuation :: PriceOracle
    -> Map CommoditySymbol AmountStyle
    -> Day
    -> Day
    -> Day
    -> ValuationType
    -> Amount
    -> Amount
    amountApplyValuation PriceOracle
    priceoracle Map CommoditySymbol AmountStyle
    styles Day
    periodlast Day
    today Day
    postingdate ValuationType
    v Amount
    a =
      case ValuationType
    v of
        AtThen    Maybe CommoditySymbol
    mc      -> PriceOracle
    -> Map CommoditySymbol AmountStyle
    -> Maybe CommoditySymbol
    -> Day
    -> Amount
    -> Amount
    amountValueAtDate PriceOracle
    priceoracle Map CommoditySymbol AmountStyle
    styles Maybe CommoditySymbol
    mc Day
    postingdate Amount
    a
        AtEnd     Maybe CommoditySymbol
    mc      -> PriceOracle
    -> Map CommoditySymbol AmountStyle
    -> Maybe CommoditySymbol
    -> Day
    -> Amount
    -> Amount
    amountValueAtDate PriceOracle
    priceoracle Map CommoditySymbol AmountStyle
    styles Maybe CommoditySymbol
    mc Day
    periodlast Amount
    a
        AtNow     Maybe CommoditySymbol
    mc      -> PriceOracle
    -> Map CommoditySymbol AmountStyle
    -> Maybe CommoditySymbol
    -> Day
    -> Amount
    -> Amount
    amountValueAtDate PriceOracle
    priceoracle Map CommoditySymbol AmountStyle
    styles Maybe CommoditySymbol
    mc Day
    today Amount
    a
        AtDate Day
    d  Maybe CommoditySymbol
    mc      -> PriceOracle
    -> Map CommoditySymbol AmountStyle
    -> Maybe CommoditySymbol
    -> Day
    -> Amount
    -> Amount
    amountValueAtDate PriceOracle
    priceoracle Map CommoditySymbol AmountStyle
    styles Maybe CommoditySymbol
    mc Day
    d Amount
    a

    -- | Find the market value of each component amount in the given
    -- commodity, or its default valuation commodity, at the given
    -- valuation date, using the given market price oracle.
    -- When market prices available on that date are not sufficient to
    -- calculate the value, amounts are left unchanged.
    mixedAmountValueAtDate :: PriceOracle -> M.Map CommoditySymbol AmountStyle -> Maybe CommoditySymbol -> Day -> MixedAmount -> MixedAmount
    mixedAmountValueAtDate :: PriceOracle
    -> Map CommoditySymbol AmountStyle
    -> Maybe CommoditySymbol
    -> Day
    -> MixedAmount
    -> MixedAmount
    mixedAmountValueAtDate PriceOracle
    priceoracle Map CommoditySymbol AmountStyle
    styles Maybe CommoditySymbol
    mc Day
    d = (Amount -> Amount) -> MixedAmount -> MixedAmount
    mapMixedAmount (PriceOracle
    -> Map CommoditySymbol AmountStyle
    -> Maybe CommoditySymbol
    -> Day
    -> Amount
    -> Amount
    amountValueAtDate PriceOracle
    priceoracle Map CommoditySymbol AmountStyle
    styles Maybe CommoditySymbol
    mc Day
    d)

    -- | Find the market value of this amount in the given valuation
    -- commodity if any, otherwise the default valuation commodity, at the
    -- given valuation date. (The default valuation commodity is the
    -- commodity of the latest applicable market price before the
    -- valuation date.)
    --
    -- The returned amount will have its commodity's canonical style applied,
    -- (with soft display rounding).
    --
    -- If the market prices available on that date are not sufficient to
    -- calculate this value, the amount is left unchanged.
    --
    amountValueAtDate :: PriceOracle -> M.Map CommoditySymbol AmountStyle -> Maybe CommoditySymbol -> Day -> Amount -> Amount
    amountValueAtDate :: PriceOracle
    -> Map CommoditySymbol AmountStyle
    -> Maybe CommoditySymbol
    -> Day
    -> Amount
    -> Amount
    amountValueAtDate PriceOracle
    priceoracle Map CommoditySymbol AmountStyle
    styles Maybe CommoditySymbol
    mto Day
    d Amount
    a =
      let lbl :: [Char] -> ShowS
    lbl = [Char] -> [Char] -> ShowS
    lbl_ [Char]
    "amountValueAtDate" in
      case PriceOracle
    priceoracle (Day
    d, Amount -> CommoditySymbol
    acommodity Amount
    a, Maybe CommoditySymbol
    mto) of
        Maybe (CommoditySymbol, Quantity)
    Nothing           -> Amount
    a
        Just (CommoditySymbol
    comm, Quantity
    rate) ->
          Amount
    nullamt{acommodity=comm, aquantity=rate * aquantity a}

          -- Manage style and precision of the new amount. Initially:
          --  rate is a Decimal with the internal precision of the original market price declaration.
          --  aquantity is a Decimal with a's internal precision.
          --  The calculated value's internal precision may be different from these.
          --  Its display precision will be that of nullamt (0).
          -- Now apply the standard display style for comm (if there is one)
          Amount -> (Amount -> Amount) -> Amount
    forall a b. a -> (a -> b) -> b
    & Map CommoditySymbol AmountStyle -> Amount -> Amount
    forall a. HasAmounts a => Map CommoditySymbol AmountStyle -> a -> a
    styleAmounts Map CommoditySymbol AmountStyle
    styles
          -- set the display precision to match the internal precision (showing all digits),
          -- unnormalised (don't strip trailing zeros);
          -- but if it looks like an infinite decimal, limit the precision to 8.
          Amount -> (Amount -> Amount) -> Amount
    forall a b. a -> (a -> b) -> b
    & Maybe Word8 -> Amount -> Amount
    amountSetFullPrecisionUpTo Maybe Word8
    forall a. Maybe a
    Nothing
          Amount -> (Amount -> Amount) -> Amount
    forall a b. a -> (a -> b) -> b
    & (Amount -> [Char]) -> Amount -> Amount
    forall a. (a -> [Char]) -> a -> a
    dbg9With ([Char] -> ShowS
    lbl [Char]
    "calculated value"ShowS -> (Amount -> [Char]) -> Amount -> [Char]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    .Amount -> [Char]
    showAmount)

    -- | Calculate the gain of each component amount, that is the difference
    -- between the valued amount and the value of the cost basis (see
    -- mixedAmountApplyValuation).
    --
    -- If the commodity we are valuing in is not the same as the commodity of the
    -- cost, this will value the cost at the same date as the primary amount. This
    -- may not be what you want; for example you may want the cost valued at the
    -- posting date. If so, let us know and we can change this behaviour.
    mixedAmountApplyGain :: PriceOracle -> M.Map CommoditySymbol AmountStyle -> Day -> Day -> Day -> ValuationType -> MixedAmount -> MixedAmount
    mixedAmountApplyGain :: PriceOracle
    -> Map CommoditySymbol AmountStyle
    -> Day
    -> Day
    -> Day
    -> ValuationType
    -> MixedAmount
    -> MixedAmount
    mixedAmountApplyGain PriceOracle
    priceoracle Map CommoditySymbol AmountStyle
    styles Day
    periodlast Day
    today Day
    postingdate ValuationType
    v MixedAmount
    ma =
      PriceOracle
    -> Map CommoditySymbol AmountStyle
    -> Day
    -> Day
    -> Day
    -> ValuationType
    -> MixedAmount
    -> MixedAmount
    mixedAmountApplyValuation PriceOracle
    priceoracle Map CommoditySymbol AmountStyle
    styles Day
    periodlast Day
    today Day
    postingdate ValuationType
    v (MixedAmount -> MixedAmount) -> MixedAmount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ MixedAmount
    ma MixedAmount -> MixedAmount -> MixedAmount
    `maMinus` MixedAmount -> MixedAmount
    mixedAmountCost MixedAmount
    ma

    -- | Calculate the gain of each component amount, that is the
    -- difference between the valued amount and the value of the cost basis.
    --
    -- If the commodity we are valuing in is not the same as the commodity of the
    -- cost, this will value the cost at the same date as the primary amount. This
    -- may not be what you want; for example you may want the cost valued at the
    -- posting date. If so, let us know and we can change this behaviour.
    mixedAmountGainAtDate :: PriceOracle -> M.Map CommoditySymbol AmountStyle -> Maybe CommoditySymbol -> Day -> MixedAmount -> MixedAmount
    mixedAmountGainAtDate :: PriceOracle
    -> Map CommoditySymbol AmountStyle
    -> Maybe CommoditySymbol
    -> Day
    -> MixedAmount
    -> MixedAmount
    mixedAmountGainAtDate PriceOracle
    priceoracle Map CommoditySymbol AmountStyle
    styles Maybe CommoditySymbol
    mto Day
    d MixedAmount
    ma =
      PriceOracle
    -> Map CommoditySymbol AmountStyle
    -> Maybe CommoditySymbol
    -> Day
    -> MixedAmount
    -> MixedAmount
    mixedAmountValueAtDate PriceOracle
    priceoracle Map CommoditySymbol AmountStyle
    styles Maybe CommoditySymbol
    mto Day
    d (MixedAmount -> MixedAmount) -> MixedAmount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ MixedAmount
    ma MixedAmount -> MixedAmount -> MixedAmount
    `maMinus` MixedAmount -> MixedAmount
    mixedAmountCost MixedAmount
    ma

    ------------------------------------------------------------------------------
    -- Market price lookup

    -- | Given a memoising price graph generator, a valuation date, a
    -- source commodity and an optional valuation commodity, find the
    -- value on that date of one unit of the source commodity in the
    -- valuation commodity, or in a default valuation commodity. Returns
    -- the valuation commodity that was specified or chosen, and the
    -- quantity of it that one unit of the source commodity is worth. Or
    -- if no applicable market price can be found or calculated, or if the
    -- source commodity and the valuation commodity are the same, returns
    -- Nothing.
    --
    -- See makePriceGraph for how prices are determined.
    -- Note that both market prices and default valuation commodities can
    -- vary with valuation date, since that determines which market prices
    -- are visible.
    --
    priceLookup :: (Day -> PriceGraph) -> Day -> CommoditySymbol -> Maybe CommoditySymbol -> Maybe (CommoditySymbol, Quantity)
    priceLookup :: (Day -> PriceGraph)
    -> Day
    -> CommoditySymbol
    -> Maybe CommoditySymbol
    -> Maybe (CommoditySymbol, Quantity)
    priceLookup Day -> PriceGraph
    makepricegraph Day
    d CommoditySymbol
    from Maybe CommoditySymbol
    mto =
      -- trace ("priceLookup ("++show d++", "++show from++", "++show mto++")") $
      let
        PriceGraph{pgEdges :: PriceGraph -> [MarketPrice]
    pgEdges=[MarketPrice]
    forwardprices
                  ,pgEdgesRev :: PriceGraph -> [MarketPrice]
    pgEdgesRev=[MarketPrice]
    allprices
                  ,pgDefaultValuationCommodities :: PriceGraph -> Map CommoditySymbol CommoditySymbol
    pgDefaultValuationCommodities=Map CommoditySymbol CommoditySymbol
    defaultdests
                  } =
          [Char] -> PriceGraph -> PriceGraph
    forall a. [Char] -> a -> a
    dbg1Msg ([Char]
    "valuation date: "[Char] -> ShowS
    forall a. [a] -> [a] -> [a]
    ++Day -> [Char]
    forall a. Show a => a -> [Char]
    show Day
    d) (PriceGraph -> PriceGraph) -> PriceGraph -> PriceGraph
    forall a b. (a -> b) -> a -> b
    $ Day -> PriceGraph
    makepricegraph Day
    d
        mto' :: Maybe CommoditySymbol
    mto' = Maybe CommoditySymbol
    mto Maybe CommoditySymbol
    -> Maybe CommoditySymbol -> Maybe CommoditySymbol
    forall a. Maybe a -> Maybe a -> Maybe a
    forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
    <|> Maybe CommoditySymbol
    mdefaultto
          where
            mdefaultto :: Maybe CommoditySymbol
    mdefaultto = [Char] -> Maybe CommoditySymbol -> Maybe CommoditySymbol
    forall a. Show a => [Char] -> a -> a
    dbg1 ([Char]
    "default valuation commodity for "[Char] -> ShowS
    forall a. [a] -> [a] -> [a]
    ++CommoditySymbol -> [Char]
    T.unpack CommoditySymbol
    from) (Maybe CommoditySymbol -> Maybe CommoditySymbol)
    -> Maybe CommoditySymbol -> Maybe CommoditySymbol
    forall a b. (a -> b) -> a -> b
    $
                         CommoditySymbol
    -> Map CommoditySymbol CommoditySymbol -> Maybe CommoditySymbol
    forall k a. Ord k => k -> Map k a -> Maybe a
    M.lookup CommoditySymbol
    from Map CommoditySymbol CommoditySymbol
    defaultdests
      in
        case Maybe CommoditySymbol
    mto' of
          Maybe CommoditySymbol
    Nothing            -> Maybe (CommoditySymbol, Quantity)
    forall a. Maybe a
    Nothing
          Just CommoditySymbol
    to | CommoditySymbol
    toCommoditySymbol -> CommoditySymbol -> Bool
    forall a. Eq a => a -> a -> Bool
    ==CommoditySymbol
    from -> Maybe (CommoditySymbol, Quantity)
    forall a. Maybe a
    Nothing
          Just CommoditySymbol
    to            ->
            -- We have a commodity to convert to. Find the most direct price available,
            -- according to the rules described in makePriceGraph.
            let
              msg :: [Char]
    msg = [Char] -> CommoditySymbol -> CommoditySymbol -> [Char]
    forall r. PrintfType r => [Char] -> r
    printf [Char]
    "seeking %s to %s price" (CommoditySymbol -> CommoditySymbol
    showCommoditySymbol CommoditySymbol
    from) (CommoditySymbol -> CommoditySymbol
    showCommoditySymbol CommoditySymbol
    to)
              prices :: Maybe [MarketPrice]
    prices =
                ([Char] -> Maybe [MarketPrice] -> Maybe [MarketPrice]
    forall a. [Char] -> a -> a
    dbg2Msg ([Char]
    msg[Char] -> ShowS
    forall a. [a] -> [a] -> [a]
    ++[Char]
    " using forward prices") (Maybe [MarketPrice] -> Maybe [MarketPrice])
    -> Maybe [MarketPrice] -> Maybe [MarketPrice]
    forall a b. (a -> b) -> a -> b
    $
                 [Char] -> Maybe [MarketPrice] -> Maybe [MarketPrice]
    forall a. [Char] -> a -> a
    dbg2Msg ([Char]
    "forward prices:\n" [Char] -> ShowS
    forall a. Semigroup a => a -> a -> a
    <> [MarketPrice] -> [Char]
    showMarketPrices [MarketPrice]
    forwardprices) (Maybe [MarketPrice] -> Maybe [MarketPrice])
    -> Maybe [MarketPrice] -> Maybe [MarketPrice]
    forall a b. (a -> b) -> a -> b
    $
                 CommoditySymbol
    -> CommoditySymbol -> [MarketPrice] -> Maybe [MarketPrice]
    pricesShortestPath CommoditySymbol
    from CommoditySymbol
    to [MarketPrice]
    forwardprices)
                Maybe [MarketPrice] -> Maybe [MarketPrice] -> Maybe [MarketPrice]
    forall a. Maybe a -> Maybe a -> Maybe a
    forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
    <|>
                ([Char] -> Maybe [MarketPrice] -> Maybe [MarketPrice]
    forall a. [Char] -> a -> a
    dbg2Msg ([Char]
    msg[Char] -> ShowS
    forall a. [a] -> [a] -> [a]
    ++[Char]
    " using forward and reverse prices") (Maybe [MarketPrice] -> Maybe [MarketPrice])
    -> Maybe [MarketPrice] -> Maybe [MarketPrice]
    forall a b. (a -> b) -> a -> b
    $
                 [Char] -> Maybe [MarketPrice] -> Maybe [MarketPrice]
    forall a. [Char] -> a -> a
    dbg2Msg ([Char]
    "forward and reverse prices:\n" [Char] -> ShowS
    forall a. Semigroup a => a -> a -> a
    <> [MarketPrice] -> [Char]
    showMarketPrices [MarketPrice]
    allprices) (Maybe [MarketPrice] -> Maybe [MarketPrice])
    -> Maybe [MarketPrice] -> Maybe [MarketPrice]
    forall a b. (a -> b) -> a -> b
    $
                 CommoditySymbol
    -> CommoditySymbol -> [MarketPrice] -> Maybe [MarketPrice]
    pricesShortestPath CommoditySymbol
    from CommoditySymbol
    to ([MarketPrice] -> Maybe [MarketPrice])
    -> [MarketPrice] -> Maybe [MarketPrice]
    forall a b. (a -> b) -> a -> b
    $ [Char] -> [MarketPrice] -> [MarketPrice]
    forall a. Show a => [Char] -> a -> a
    dbg5 [Char]
    "all forward and reverse prices" [MarketPrice]
    allprices)
            in case Maybe [MarketPrice]
    prices of
              Maybe [MarketPrice]
    Nothing -> Maybe (CommoditySymbol, Quantity)
    forall a. Maybe a
    Nothing
              Just [] -> Maybe (CommoditySymbol, Quantity)
    forall a. Maybe a
    Nothing
              Just [MarketPrice]
    ps -> (CommoditySymbol, Quantity) -> Maybe (CommoditySymbol, Quantity)
    forall a. a -> Maybe a
    Just (MarketPrice -> CommoditySymbol
    mpto (MarketPrice -> CommoditySymbol) -> MarketPrice -> CommoditySymbol
    forall a b. (a -> b) -> a -> b
    $ [MarketPrice] -> MarketPrice
    forall a. HasCallStack => [a] -> a
    last [MarketPrice]
    ps, Quantity
    rate)
                where
                  rates :: [Quantity]
    rates = (MarketPrice -> Quantity) -> [MarketPrice] -> [Quantity]
    forall a b. (a -> b) -> [a] -> [b]
    map MarketPrice -> Quantity
    mprate [MarketPrice]
    ps
                  rate :: Quantity
    rate =
                    -- aggregate all the prices into one
                    [Quantity] -> Quantity
    forall a. Num a => [a] -> a
    forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
    product [Quantity]
    rates
                    -- product (Decimal's Num instance) normalises, stripping trailing zeros.
                    -- But we want to preserve even those, since the number of decimal digits
                    -- here will guide amountValueAtDate in setting the Amount display precision later.
                    -- So we restore them. Or rather, we ensure as many decimal digits as the maximum seen among rates.
                    -- (Some prices might end up more precise than they were, but that seems harmless here.)
                    Quantity -> (Quantity -> Quantity) -> Quantity
    forall a b. a -> (a -> b) -> b
    & Word8 -> Quantity -> Quantity
    setMinDecimalPlaces ([Word8] -> Word8
    forall a. Ord a => [a] -> a
    forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
    maximum ([Word8] -> Word8) -> [Word8] -> Word8
    forall a b. (a -> b) -> a -> b
    $ (Quantity -> Word8) -> [Quantity] -> [Word8]
    forall a b. (a -> b) -> [a] -> [b]
    map Quantity -> Word8
    forall i. DecimalRaw i -> Word8
    decimalPlaces [Quantity]
    rates)

    -- Ensure this Decimal has at least this many decimal places, adding trailing zeros if necessary.
    setMinDecimalPlaces :: Word8 -> Decimal -> Decimal
    setMinDecimalPlaces :: Word8 -> Quantity -> Quantity
    setMinDecimalPlaces Word8
    n Quantity
    d
      | Quantity -> Word8
    forall i. DecimalRaw i -> Word8
    decimalPlaces Quantity
    d Word8 -> Word8 -> Bool
    forall a. Ord a => a -> a -> Bool
    < Word8
    n = Word8 -> Quantity -> Quantity
    forall i. Integral i => Word8 -> DecimalRaw i -> DecimalRaw i
    roundTo Word8
    n Quantity
    d  -- too few, add some zeros
      | Bool
    otherwise           = Quantity
    d            -- more than enough, keep as-is

    tests_priceLookup :: TestTree
    tests_priceLookup =
      let
        p :: Integer
    -> Int
    -> Int
    -> CommoditySymbol
    -> Quantity
    -> CommoditySymbol
    -> MarketPrice
    p Integer
    y Int
    m Int
    d CommoditySymbol
    from Quantity
    q CommoditySymbol
    to = MarketPrice{mpdate :: Day
    mpdate=Integer -> Int -> Int -> Day
    fromGregorian Integer
    y Int
    m Int
    d, mpfrom :: CommoditySymbol
    mpfrom=CommoditySymbol
    from, mpto :: CommoditySymbol
    mpto=CommoditySymbol
    to, mprate :: Quantity
    mprate=Quantity
    q}
        ps1 :: [MarketPrice]
    ps1 = [
           Integer
    -> Int
    -> Int
    -> CommoditySymbol
    -> Quantity
    -> CommoditySymbol
    -> MarketPrice
    p Integer
    2000 Int
    01 Int
    01 CommoditySymbol
    "A" Quantity
    10 CommoditySymbol
    "B"
          ,Integer
    -> Int
    -> Int
    -> CommoditySymbol
    -> Quantity
    -> CommoditySymbol
    -> MarketPrice
    p Integer
    2000 Int
    01 Int
    01 CommoditySymbol
    "B" Quantity
    10 CommoditySymbol
    "C"
          ,Integer
    -> Int
    -> Int
    -> CommoditySymbol
    -> Quantity
    -> CommoditySymbol
    -> MarketPrice
    p Integer
    2000 Int
    01 Int
    01 CommoditySymbol
    "C" Quantity
    10 CommoditySymbol
    "D"
          ,Integer
    -> Int
    -> Int
    -> CommoditySymbol
    -> Quantity
    -> CommoditySymbol
    -> MarketPrice
    p Integer
    2000 Int
    01 Int
    01 CommoditySymbol
    "E"  Quantity
    2 CommoditySymbol
    "D"
          ,Integer
    -> Int
    -> Int
    -> CommoditySymbol
    -> Quantity
    -> CommoditySymbol
    -> MarketPrice
    p Integer
    2001 Int
    01 Int
    01 CommoditySymbol
    "A" Quantity
    11 CommoditySymbol
    "B"
          ]
        makepricegraph :: Day -> PriceGraph
    makepricegraph = PriceIndexes -> Day -> PriceGraph
    makePriceGraph ([MarketPrice] -> [MarketPrice] -> PriceIndexes
    buildPriceIndexes [MarketPrice]
    ps1 [])
      in [Char] -> Assertion -> TestTree
    testCase [Char]
    "priceLookup" (Assertion -> TestTree) -> Assertion -> TestTree
    forall a b. (a -> b) -> a -> b
    $ do
        (Day -> PriceGraph)
    -> Day
    -> CommoditySymbol
    -> Maybe CommoditySymbol
    -> Maybe (CommoditySymbol, Quantity)
    priceLookup Day -> PriceGraph
    makepricegraph (Integer -> Int -> Int -> Day
    fromGregorian Integer
    1999 Int
    01 Int
    01) CommoditySymbol
    "A" Maybe CommoditySymbol
    forall a. Maybe a
    Nothing    Maybe (CommoditySymbol, Quantity)
    -> Maybe (CommoditySymbol, Quantity) -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= Maybe (CommoditySymbol, Quantity)
    forall a. Maybe a
    Nothing
        (Day -> PriceGraph)
    -> Day
    -> CommoditySymbol
    -> Maybe CommoditySymbol
    -> Maybe (CommoditySymbol, Quantity)
    priceLookup Day -> PriceGraph
    makepricegraph (Integer -> Int -> Int -> Day
    fromGregorian Integer
    2000 Int
    01 Int
    01) CommoditySymbol
    "A" Maybe CommoditySymbol
    forall a. Maybe a
    Nothing    Maybe (CommoditySymbol, Quantity)
    -> Maybe (CommoditySymbol, Quantity) -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= (CommoditySymbol, Quantity) -> Maybe (CommoditySymbol, Quantity)
    forall a. a -> Maybe a
    Just (CommoditySymbol
    "B",Quantity
    10)
        (Day -> PriceGraph)
    -> Day
    -> CommoditySymbol
    -> Maybe CommoditySymbol
    -> Maybe (CommoditySymbol, Quantity)
    priceLookup Day -> PriceGraph
    makepricegraph (Integer -> Int -> Int -> Day
    fromGregorian Integer
    2000 Int
    01 Int
    01) CommoditySymbol
    "B" (CommoditySymbol -> Maybe CommoditySymbol
    forall a. a -> Maybe a
    Just CommoditySymbol
    "A") Maybe (CommoditySymbol, Quantity)
    -> Maybe (CommoditySymbol, Quantity) -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= (CommoditySymbol, Quantity) -> Maybe (CommoditySymbol, Quantity)
    forall a. a -> Maybe a
    Just (CommoditySymbol
    "A",Quantity
    0.1)
        (Day -> PriceGraph)
    -> Day
    -> CommoditySymbol
    -> Maybe CommoditySymbol
    -> Maybe (CommoditySymbol, Quantity)
    priceLookup Day -> PriceGraph
    makepricegraph (Integer -> Int -> Int -> Day
    fromGregorian Integer
    2000 Int
    01 Int
    01) CommoditySymbol
    "A" (CommoditySymbol -> Maybe CommoditySymbol
    forall a. a -> Maybe a
    Just CommoditySymbol
    "E") Maybe (CommoditySymbol, Quantity)
    -> Maybe (CommoditySymbol, Quantity) -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= (CommoditySymbol, Quantity) -> Maybe (CommoditySymbol, Quantity)
    forall a. a -> Maybe a
    Just (CommoditySymbol
    "E",Quantity
    500)

    ------------------------------------------------------------------------------
    -- Market price graph
    -- built directly with MarketPrices for now, probably space-inefficient

    type Edge = MarketPrice
    type Path = [Edge]

    data PriceGraph = PriceGraph {
       PriceGraph -> Day
    pgDate :: Day
        -- ^ The date on which these prices are in effect.
      ,PriceGraph -> [MarketPrice]
    pgEdges :: [Edge]
        -- ^ "Forward" exchange rates between commodity pairs, either
        --   declared by P directives or (with --infer-market-prices) inferred from costs,
        --   forming the edges of a directed graph.
      ,PriceGraph -> [MarketPrice]
    pgEdgesRev :: [Edge]
        -- ^ The same edges, plus any additional edges that can be
        --   inferred by reversing them and inverting the rates.
        --
        --   In both of these there will be at most one edge between each
        --   directed pair of commodities, eg there can be one USD->EUR and one EUR->USD.
        --
      ,PriceGraph -> Map CommoditySymbol CommoditySymbol
    pgDefaultValuationCommodities :: M.Map CommoditySymbol CommoditySymbol
        -- ^ The default valuation commodity for each source commodity.
        --   These are used when a valuation commodity is not specified
        --   (-V). They are the destination commodity of each source commodity's
        --   latest (declared or inferred, but not reverse) market price
        --   (on the date of this graph).
      }
      deriving (Int -> PriceGraph -> ShowS
    [PriceGraph] -> ShowS
    PriceGraph -> [Char]
    (Int -> PriceGraph -> ShowS)
    -> (PriceGraph -> [Char])
    -> ([PriceGraph] -> ShowS)
    -> Show PriceGraph
    forall a.
    (Int -> a -> ShowS) -> (a -> [Char]) -> ([a] -> ShowS) -> Show a
    $cshowsPrec :: Int -> PriceGraph -> ShowS
    showsPrec :: Int -> PriceGraph -> ShowS
    $cshow :: PriceGraph -> [Char]
    show :: PriceGraph -> [Char]
    $cshowList :: [PriceGraph] -> ShowS
    showList :: [PriceGraph] -> ShowS
    Show,(forall x. PriceGraph -> Rep PriceGraph x)
    -> (forall x. Rep PriceGraph x -> PriceGraph) -> Generic PriceGraph
    forall x. Rep PriceGraph x -> PriceGraph
    forall x. PriceGraph -> Rep PriceGraph x
    forall a.
    (forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
    $cfrom :: forall x. PriceGraph -> Rep PriceGraph x
    from :: forall x. PriceGraph -> Rep PriceGraph x
    $cto :: forall x. Rep PriceGraph x -> PriceGraph
    to :: forall x. Rep PriceGraph x -> PriceGraph
    Generic)

    -- | Find the shortest path and corresponding conversion rate, if any,
    -- from one commodity to another using the provided market prices which
    -- form the edges of a directed graph. There should be at most one edge
    -- between each directed pair of commodities, eg there can be one
    -- USD->EUR price and one EUR->USD price.
    pricesShortestPath :: CommoditySymbol -> CommoditySymbol -> [Edge] -> Maybe Path
    pricesShortestPath :: CommoditySymbol
    -> CommoditySymbol -> [MarketPrice] -> Maybe [MarketPrice]
    pricesShortestPath CommoditySymbol
    start CommoditySymbol
    end [MarketPrice]
    edges =
      -- at --debug=2 +, print the pretty path and also the detailed prices
      let label :: [Char]
    label = [Char] -> CommoditySymbol -> CommoditySymbol -> [Char]
    forall r. PrintfType r => [Char] -> r
    printf [Char]
    "shortest path from %s to %s: " (CommoditySymbol -> CommoditySymbol
    showCommoditySymbol CommoditySymbol
    start) (CommoditySymbol -> CommoditySymbol
    showCommoditySymbol CommoditySymbol
    end) in
      ([MarketPrice] -> [MarketPrice])
    -> Maybe [MarketPrice] -> Maybe [MarketPrice]
    forall a b. (a -> b) -> Maybe a -> Maybe b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap (([MarketPrice] -> [Char]) -> [MarketPrice] -> [MarketPrice]
    forall a. (a -> [Char]) -> a -> a
    dbg2With (([Char]
    "price chain:\n"[Char] -> ShowS
    forall a. [a] -> [a] -> [a]
    ++)ShowS -> ([MarketPrice] -> [Char]) -> [MarketPrice] -> [Char]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    .[MarketPrice] -> [Char]
    showMarketPrices)) (Maybe [MarketPrice] -> Maybe [MarketPrice])
    -> Maybe [MarketPrice] -> Maybe [MarketPrice]
    forall a b. (a -> b) -> a -> b
    $
      (Maybe [MarketPrice] -> [Char])
    -> Maybe [MarketPrice] -> Maybe [MarketPrice]
    forall a. (a -> [Char]) -> a -> a
    dbg2With (([Char]
    label[Char] -> ShowS
    forall a. [a] -> [a] -> [a]
    ++)ShowS
    -> (Maybe [MarketPrice] -> [Char]) -> Maybe [MarketPrice] -> [Char]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    .([Char]
    -> ([MarketPrice] -> [Char]) -> Maybe [MarketPrice] -> [Char]
    forall b a. b -> (a -> b) -> Maybe a -> b
    maybe [Char]
    "none" ([Char] -> [MarketPrice] -> [Char]
    pshowpath [Char]
    ""))) (Maybe [MarketPrice] -> Maybe [MarketPrice])
    -> Maybe [MarketPrice] -> Maybe [MarketPrice]
    forall a b. (a -> b) -> a -> b
    $
      [([MarketPrice], [MarketPrice])] -> Maybe [MarketPrice]
    find [([],[MarketPrice]
    edges)]

      where
        -- Find the first and shortest complete path using a breadth-first search.
        find :: [(Path,[Edge])] -> Maybe Path
        find :: [([MarketPrice], [MarketPrice])] -> Maybe [MarketPrice]
    find [([MarketPrice], [MarketPrice])]
    paths =
          case (([MarketPrice], [MarketPrice])
     -> [([MarketPrice], [MarketPrice])])
    -> [([MarketPrice], [MarketPrice])]
    -> [([MarketPrice], [MarketPrice])]
    forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
    concatMap ([MarketPrice], [MarketPrice]) -> [([MarketPrice], [MarketPrice])]
    extend [([MarketPrice], [MarketPrice])]
    paths of
            [] -> Maybe [MarketPrice]
    forall a. Maybe a
    Nothing
            [([MarketPrice], [MarketPrice])]
    _ | Int
    pathlength Int -> Int -> Bool
    forall a. Ord a => a -> a -> Bool
    > Int
    maxpathlength ->
              -- Print a non-fatal warning to stderr, something we usually avoid.
              [Char] -> Maybe [MarketPrice] -> Maybe [MarketPrice]
    forall a. [Char] -> a -> a
    warn ([Char]
    "gave up searching for a price chain at length "[Char] -> ShowS
    forall a. [a] -> [a] -> [a]
    ++Int -> [Char]
    forall a. Show a => a -> [Char]
    show Int
    maxpathlength[Char] -> ShowS
    forall a. [a] -> [a] -> [a]
    ++[Char]
    ", please report a bug")
              Maybe [MarketPrice]
    forall a. Maybe a
    Nothing
              where
                pathlength :: Int
    pathlength = Int
    2 Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + Int
    -> (([MarketPrice], [MarketPrice]) -> Int)
    -> Maybe ([MarketPrice], [MarketPrice])
    -> Int
    forall b a. b -> (a -> b) -> Maybe a -> b
    maybe Int
    0 ([MarketPrice] -> Int
    forall a. [a] -> Int
    forall (t :: * -> *) a. Foldable t => t a -> Int
    length ([MarketPrice] -> Int)
    -> (([MarketPrice], [MarketPrice]) -> [MarketPrice])
    -> ([MarketPrice], [MarketPrice])
    -> Int
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ([MarketPrice], [MarketPrice]) -> [MarketPrice]
    forall a b. (a, b) -> a
    fst) ([([MarketPrice], [MarketPrice])]
    -> Maybe ([MarketPrice], [MarketPrice])
    forall a. [a] -> Maybe a
    headMay [([MarketPrice], [MarketPrice])]
    paths)
                maxpathlength :: Int
    maxpathlength = Int
    1000
            [([MarketPrice], [MarketPrice])]
    paths' ->
              case [[MarketPrice]]
    completepaths of
                    [MarketPrice]
    p:[[MarketPrice]]
    _ -> [MarketPrice] -> Maybe [MarketPrice]
    forall a. a -> Maybe a
    Just [MarketPrice]
    p  -- the left-most complete path at this length
                    []  -> [([MarketPrice], [MarketPrice])] -> Maybe [MarketPrice]
    find [([MarketPrice], [MarketPrice])]
    paths'
              where completepaths :: [[MarketPrice]]
    completepaths = [[MarketPrice]
    p | ([MarketPrice]
    p,[MarketPrice]
    _) <- [([MarketPrice], [MarketPrice])]
    paths', (MarketPrice -> CommoditySymbol
    mpto (MarketPrice -> CommoditySymbol)
    -> Maybe MarketPrice -> Maybe CommoditySymbol
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> [MarketPrice] -> Maybe MarketPrice
    forall a. [a] -> Maybe a
    lastMay [MarketPrice]
    p) Maybe CommoditySymbol -> Maybe CommoditySymbol -> Bool
    forall a. Eq a => a -> a -> Bool
    == CommoditySymbol -> Maybe CommoditySymbol
    forall a. a -> Maybe a
    Just CommoditySymbol
    end]

        -- Use all applicable edges from those provided to extend this path by one step,
        -- returning zero or more new (path, remaining edges) pairs.
        extend :: (Path,[Edge]) -> [(Path,[Edge])]
        extend :: ([MarketPrice], [MarketPrice]) -> [([MarketPrice], [MarketPrice])]
    extend ([MarketPrice]
    path,[MarketPrice]
    unusededges) =
          let
            pathnodes :: [CommoditySymbol]
    pathnodes = CommoditySymbol
    start CommoditySymbol -> [CommoditySymbol] -> [CommoditySymbol]
    forall a. a -> [a] -> [a]
    : (MarketPrice -> CommoditySymbol)
    -> [MarketPrice] -> [CommoditySymbol]
    forall a b. (a -> b) -> [a] -> [b]
    map MarketPrice -> CommoditySymbol
    mpto [MarketPrice]
    path
            pathend :: CommoditySymbol
    pathend = CommoditySymbol
    -> (MarketPrice -> CommoditySymbol)
    -> Maybe MarketPrice
    -> CommoditySymbol
    forall b a. b -> (a -> b) -> Maybe a -> b
    maybe CommoditySymbol
    start MarketPrice -> CommoditySymbol
    mpto (Maybe MarketPrice -> CommoditySymbol)
    -> Maybe MarketPrice -> CommoditySymbol
    forall a b. (a -> b) -> a -> b
    $ [MarketPrice] -> Maybe MarketPrice
    forall a. [a] -> Maybe a
    lastMay [MarketPrice]
    path
            ([MarketPrice]
    nextedges,[MarketPrice]
    remainingedges) = (MarketPrice -> Bool)
    -> [MarketPrice] -> ([MarketPrice], [MarketPrice])
    forall a. (a -> Bool) -> [a] -> ([a], [a])
    partition ((CommoditySymbol -> CommoditySymbol -> Bool
    forall a. Eq a => a -> a -> Bool
    ==CommoditySymbol
    pathend)(CommoditySymbol -> Bool)
    -> (MarketPrice -> CommoditySymbol) -> MarketPrice -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    .MarketPrice -> CommoditySymbol
    mpfrom) [MarketPrice]
    unusededges
          in
            [ ([MarketPrice]
    path', [MarketPrice]
    remainingedges')
            | MarketPrice
    e <- [MarketPrice]
    nextedges
            , let path' :: [MarketPrice]
    path' = [Char] -> [MarketPrice] -> [MarketPrice]
    dbgpath [Char]
    "trying" ([MarketPrice] -> [MarketPrice]) -> [MarketPrice] -> [MarketPrice]
    forall a b. (a -> b) -> a -> b
    $ [MarketPrice]
    path [MarketPrice] -> [MarketPrice] -> [MarketPrice]
    forall a. [a] -> [a] -> [a]
    ++ [MarketPrice
    e]  -- PERF prepend ?
            , let pathnodes' :: [CommoditySymbol]
    pathnodes' = MarketPrice -> CommoditySymbol
    mpto MarketPrice
    e CommoditySymbol -> [CommoditySymbol] -> [CommoditySymbol]
    forall a. a -> [a] -> [a]
    : [CommoditySymbol]
    pathnodes
            , let remainingedges' :: [MarketPrice]
    remainingedges' = [MarketPrice
    r | MarketPrice
    r <- [MarketPrice]
    remainingedges, MarketPrice -> CommoditySymbol
    mpto MarketPrice
    r CommoditySymbol -> [CommoditySymbol] -> Bool
    forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
    `notElem` [CommoditySymbol]
    pathnodes' ]
            ]

    -- debug helpers
    dbgpath :: [Char] -> [MarketPrice] -> [MarketPrice]
    dbgpath  [Char]
    label = ([MarketPrice] -> [Char]) -> [MarketPrice] -> [MarketPrice]
    forall a. (a -> [Char]) -> a -> a
    dbg2With ([Char] -> [MarketPrice] -> [Char]
    pshowpath [Char]
    label)
    -- dbgedges label = dbg2With (pshowedges label)
    pshowpath :: [Char] -> [MarketPrice] -> [Char]
    pshowpath [Char]
    label = \case
      []      -> [Char] -> ShowS
    prefix [Char]
    label [Char]
    ""
      p :: [MarketPrice]
    p@(MarketPrice
    e:[MarketPrice]
    _) -> [Char] -> ShowS
    prefix [Char]
    label ShowS -> ShowS
    forall a b. (a -> b) -> a -> b
    $ CommoditySymbol -> [Char]
    pshownode (MarketPrice -> CommoditySymbol
    mpfrom MarketPrice
    e) [Char] -> ShowS
    forall a. [a] -> [a] -> [a]
    ++ [Char]
    ">" [Char] -> ShowS
    forall a. [a] -> [a] -> [a]
    ++ [Char] -> [[Char]] -> [Char]
    forall a. [a] -> [[a]] -> [a]
    intercalate [Char]
    ">" ((MarketPrice -> [Char]) -> [MarketPrice] -> [[Char]]
    forall a b. (a -> b) -> [a] -> [b]
    map (CommoditySymbol -> [Char]
    pshownode (CommoditySymbol -> [Char])
    -> (MarketPrice -> CommoditySymbol) -> MarketPrice -> [Char]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . MarketPrice -> CommoditySymbol
    mpto) [MarketPrice]
    p)
    -- pshowedges label = prefix label . intercalate ", " . map (pshowedge "")
    -- pshowedge label MarketPrice{..} = pshowedge' label mpfrom mpto
    -- pshowedge' label from to = prefix label $ pshownode from ++ ">" ++ pshownode to
    pshownode :: CommoditySymbol -> [Char]
    pshownode = CommoditySymbol -> [Char]
    T.unpack (CommoditySymbol -> [Char])
    -> (CommoditySymbol -> CommoditySymbol)
    -> CommoditySymbol
    -> [Char]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . CommoditySymbol -> CommoditySymbol
    showCommoditySymbol
    prefix :: [Char] -> ShowS
    prefix [Char]
    l = if [Char] -> Bool
    forall a. [a] -> Bool
    forall (t :: * -> *) a. Foldable t => t a -> Bool
    null [Char]
    l then ([Char]
    ""[Char] -> ShowS
    forall a. [a] -> [a] -> [a]
    ++) else (([Char]
    l[Char] -> ShowS
    forall a. [a] -> [a] -> [a]
    ++[Char]
    ": ")[Char] -> ShowS
    forall a. [a] -> [a] -> [a]
    ++)

    -- | A snapshot of the known exchange rates between commodity pairs at a given date.
    -- This is a home-made version, more tailored to our needs.
    -- | Build the graph of commodity conversion prices for a given day.
    -- Converts a list of declared market prices in parse order, and a
    -- list of transaction-inferred market prices in parse order, to:
    --
    -- 1. a graph of all known exchange rates declared or inferred from
    -- one commodity to another in effect on that day
    --
    -- 2. a second graph which includes any additional exchange rates
    -- that can be inferred by reversing known rates
    --
    -- 3. a map of each commodity's default valuation commodity, if any.
    --
    -- These allow price lookup and valuation to be performed as
    -- described in hledger.m4.md -> Valuation:
    --
    -- "hledger looks for a market price (exchange rate) from commodity A
    -- to commodity B in one or more of these ways, in this order of
    -- preference:
    --
    -- 1. A *declared market price* or *inferred market price*:
    --    A's latest market price in B on or before the valuation date
    --    as declared by a P directive, or (with the `--infer-market-prices` flag)
    --    inferred from transaction prices.
    --
    -- 2. A *reverse market price*:
    --    the inverse of a declared or inferred market price from B to A.
    --
    -- 3. A *a forward chain of market prices*:
    --    a synthetic price formed by combining the shortest chain of
    --    "forward" (only 1 above) market prices, leading from A to B.
    --
    -- 4. A *any chain of market prices*:
    --    a chain of any market prices, including both forward and
    --    reverse prices (1 and 2 above), leading from A to B."
    --
    -- and: "For each commodity A, hledger picks a default valuation
    -- commodity as follows, in this order of preference:
    --
    -- 1. The price commodity from the latest declared market price for A
    --    on or before valuation date.
    --
    -- 2. The price commodity from the latest declared market price for A
    --    on any date. (Allows conversion to proceed if there are inferred
    --    prices before the valuation date.)
    --
    -- 3. If there are no P directives at all (any commodity or date), and
    --    the `--infer-market-prices` flag is used, then the price commodity from
    --    the latest transaction price for A on or before valuation date."
    --
    -- | Build the price graph using pre-built indexes for O(pairs × log n) lookup.
    makePriceGraph :: PriceIndexes -> Day -> PriceGraph
    makePriceGraph :: PriceIndexes -> Day -> PriceGraph
    makePriceGraph PriceIndexes
    indexes Day
    d =
      [Char] -> PriceGraph -> PriceGraph
    forall a. Show a => [Char] -> a -> a
    dbg9 ([Char]
    "makePriceGraph "[Char] -> ShowS
    forall a. [a] -> [a] -> [a]
    ++Day -> [Char]
    forall a. Show a => a -> [Char]
    show Day
    d) (PriceGraph -> PriceGraph) -> PriceGraph -> PriceGraph
    forall a b. (a -> b) -> a -> b
    $
      PriceGraph{
         pgDate :: Day
    pgDate = Day
    d
        ,pgEdges :: [MarketPrice]
    pgEdges=[MarketPrice]
    forwardprices
        ,pgEdgesRev :: [MarketPrice]
    pgEdgesRev=[MarketPrice]
    allprices
        ,pgDefaultValuationCommodities :: Map CommoditySymbol CommoditySymbol
    pgDefaultValuationCommodities=Map CommoditySymbol CommoditySymbol
    defaultdests
        }
      where
        -- XXX logic duplicated in Hledger.Cli.Commands.Prices.prices, keep synced

        -- get the latest effective price for each commodity pair on or before date d
        forwardprices :: [MarketPrice]
    forwardprices = [Char] -> [MarketPrice] -> [MarketPrice]
    forall a. Show a => [Char] -> a -> a
    dbg9 [Char]
    "effective forward prices" ([MarketPrice] -> [MarketPrice]) -> [MarketPrice] -> [MarketPrice]
    forall a b. (a -> b) -> a -> b
    $
          Day -> PriceIndex -> [MarketPrice]
    lookupEffectivePricesFromIndex Day
    d (PriceIndexes -> PriceIndex
    piForward PriceIndexes
    indexes)

        -- infer any additional reverse prices not already declared or inferred
        reverseprices :: [MarketPrice]
    reverseprices = [Char] -> [MarketPrice] -> [MarketPrice]
    forall a. Show a => [Char] -> a -> a
    dbg9 [Char]
    "additional reverse prices" ([MarketPrice] -> [MarketPrice]) -> [MarketPrice] -> [MarketPrice]
    forall a b. (a -> b) -> a -> b
    $
          [MarketPrice
    p | p :: MarketPrice
    p@MarketPrice{Quantity
    CommoditySymbol
    Day
    mpdate :: MarketPrice -> Day
    mpfrom :: MarketPrice -> CommoditySymbol
    mpto :: MarketPrice -> CommoditySymbol
    mprate :: MarketPrice -> Quantity
    mpdate :: Day
    mpfrom :: CommoditySymbol
    mpto :: CommoditySymbol
    mprate :: Quantity
    ..} <- (MarketPrice -> MarketPrice) -> [MarketPrice] -> [MarketPrice]
    forall a b. (a -> b) -> [a] -> [b]
    map MarketPrice -> MarketPrice
    marketPriceReverse [MarketPrice]
    forwardprices
             , Bool -> Bool
    not (Bool -> Bool) -> Bool -> Bool
    forall a b. (a -> b) -> a -> b
    $ (CommoditySymbol
    mpfrom,CommoditySymbol
    mpto) (CommoditySymbol, CommoditySymbol)
    -> Set (CommoditySymbol, CommoditySymbol) -> Bool
    forall a. Ord a => a -> Set a -> Bool
    `S.member` Set (CommoditySymbol, CommoditySymbol)
    forwardpairs
          ]
          where
            forwardpairs :: Set (CommoditySymbol, CommoditySymbol)
    forwardpairs = [(CommoditySymbol, CommoditySymbol)]
    -> Set (CommoditySymbol, CommoditySymbol)
    forall a. Ord a => [a] -> Set a
    S.fromList [(CommoditySymbol
    mpfrom,CommoditySymbol
    mpto) | MarketPrice{Quantity
    CommoditySymbol
    Day
    mpdate :: MarketPrice -> Day
    mpfrom :: MarketPrice -> CommoditySymbol
    mpto :: MarketPrice -> CommoditySymbol
    mprate :: MarketPrice -> Quantity
    mpfrom :: CommoditySymbol
    mpto :: CommoditySymbol
    mpdate :: Day
    mprate :: Quantity
    ..} <- [MarketPrice]
    forwardprices]
        allprices :: [MarketPrice]
    allprices = [MarketPrice]
    forwardprices [MarketPrice] -> [MarketPrice] -> [MarketPrice]
    forall a. [a] -> [a] -> [a]
    ++ [MarketPrice]
    reverseprices

        -- use indexed lookup for default valuation commodities
        defaultdests :: Map CommoditySymbol CommoditySymbol
    defaultdests = [Char]
    -> Map CommoditySymbol CommoditySymbol
    -> Map CommoditySymbol CommoditySymbol
    forall a. Show a => [Char] -> a -> a
    dbg9 [Char]
    "default valuation commodities" (Map CommoditySymbol CommoditySymbol
     -> Map CommoditySymbol CommoditySymbol)
    -> Map CommoditySymbol CommoditySymbol
    -> Map CommoditySymbol CommoditySymbol
    forall a b. (a -> b) -> a -> b
    $
          Day -> PriceIndexes -> Map CommoditySymbol CommoditySymbol
    lookupDefaultValuations Day
    d PriceIndexes
    indexes

    marketPriceReverse :: MarketPrice -> MarketPrice
    marketPriceReverse :: MarketPrice -> MarketPrice
    marketPriceReverse mp :: MarketPrice
    mp@MarketPrice{Quantity
    CommoditySymbol
    Day
    mpdate :: MarketPrice -> Day
    mpfrom :: MarketPrice -> CommoditySymbol
    mpto :: MarketPrice -> CommoditySymbol
    mprate :: MarketPrice -> Quantity
    mpdate :: Day
    mpfrom :: CommoditySymbol
    mpto :: CommoditySymbol
    mprate :: Quantity
    ..} =
      MarketPrice
    mp{mpfrom=mpto, mpto=mpfrom, mprate=if mprate==0 then 0 else 1/mprate}  -- PARTIAL: /

    nullmarketprice :: MarketPrice
    nullmarketprice :: MarketPrice
    nullmarketprice = MarketPrice {
       mpdate :: Day
    mpdate=Day
    nulldate
      ,mpfrom :: CommoditySymbol
    mpfrom=CommoditySymbol
    ""
      ,mpto :: CommoditySymbol
    mpto=CommoditySymbol
    ""
      ,mprate :: Quantity
    mprate=Quantity
    0
      }

    ------------------------------------------------------------------------------

    tests_Valuation :: TestTree
    tests_Valuation = [Char] -> [TestTree] -> TestTree
    testGroup [Char]
    "Valuation" [
       TestTree
    tests_priceLookup
      ,[Char] -> Assertion -> TestTree
    testCase [Char]
    "marketPriceReverse" (Assertion -> TestTree) -> Assertion -> TestTree
    forall a b. (a -> b) -> a -> b
    $ do
        MarketPrice -> MarketPrice
    marketPriceReverse MarketPrice
    nullmarketprice{mprate=2} MarketPrice -> MarketPrice -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= MarketPrice
    nullmarketprice{mprate=0.5}
        MarketPrice -> MarketPrice
    marketPriceReverse MarketPrice
    nullmarketprice MarketPrice -> MarketPrice -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= MarketPrice
    nullmarketprice  -- the reverse of a 0 price is a 0 price

      ]