lib: Hledger.Data.Balancing: inferBalancingPrices -> transactionInferBalancingCosts

This commit is contained in:
Simon Michael 2023-01-19 09:30:21 -10:00
parent 7ecf7ef27b
commit c0950c0900

View File

@ -156,7 +156,7 @@ balanceTransactionHelper ::
-> Either String (Transaction, [(AccountName, MixedAmount)]) -> Either String (Transaction, [(AccountName, MixedAmount)])
balanceTransactionHelper bopts t = do balanceTransactionHelper bopts t = do
(t', inferredamtsandaccts) <- transactionInferBalancingAmount (fromMaybe M.empty $ commodity_styles_ bopts) $ (t', inferredamtsandaccts) <- transactionInferBalancingAmount (fromMaybe M.empty $ commodity_styles_ bopts) $
if infer_transaction_prices_ bopts then inferBalancingPrices t else t if infer_transaction_prices_ bopts then transactionInferBalancingCosts t else t
case transactionCheckBalanced bopts t' of case transactionCheckBalanced bopts t' of
[] -> Right (txnTieKnot t', inferredamtsandaccts) [] -> Right (txnTieKnot t', inferredamtsandaccts)
errs -> Left $ transactionBalanceError t' errs' errs -> Left $ transactionBalanceError t' errs'
@ -244,55 +244,55 @@ transactionInferBalancingAmount styles t@Transaction{tpostings=ps}
-- (since the main amount styling pass happened before this balancing pass); -- (since the main amount styling pass happened before this balancing pass);
a' = styleMixedAmount styles . mixedAmountCost $ maNegate a a' = styleMixedAmount styles . mixedAmountCost $ maNegate a
-- | Infer prices for this transaction's posting amounts, if needed to make -- | Infer costs for this transaction's posting amounts, if needed to make
-- the postings balance, and if possible. This is done once for the real -- the postings balance, and if possible. This is done once for the real
-- postings and again (separately) for the balanced virtual postings. When -- postings and again (separately) for the balanced virtual postings. When
-- it's not possible, the transaction is left unchanged. -- it's not possible, the transaction is left unchanged.
-- --
-- The simplest example is a transaction with two postings, each in a -- The simplest example is a transaction with two postings, each in a
-- different commodity, with no prices specified. In this case we'll add a -- different commodity, with no costs specified. In this case we'll add a
-- price to the first posting such that it can be converted to the commodity -- cost to the first posting such that it can be converted to the commodity
-- of the second posting (with -B), and such that the postings balance. -- of the second posting (with -B), and such that the postings balance.
-- --
-- In general, we can infer a conversion price when the sum of posting amounts -- In general, we can infer a cost (conversion rate) when the sum of posting amounts
-- contains exactly two different commodities and no explicit prices. Also -- contains exactly two different commodities and no explicit costs. Also
-- all postings are expected to contain an explicit amount (no missing -- all postings are expected to contain an explicit amount (no missing
-- amounts) in a single commodity. Otherwise no price inferring is attempted. -- amounts) in a single commodity. Otherwise no cost inferring is attempted.
-- --
-- The transaction itself could contain more than two commodities, and/or -- The transaction itself could contain more than two commodities, and/or
-- prices, if they cancel out; what matters is that the sum of posting amounts -- costs, if they cancel out; what matters is that the sum of posting amounts
-- contains exactly two commodities and zero prices. -- contains exactly two commodities and zero costs.
-- --
-- There can also be more than two postings in either of the commodities. -- There can also be more than two postings in either of the commodities.
-- --
-- We want to avoid excessive display of digits when the calculated price is -- We want to avoid excessive display of digits when the calculated cost is
-- an irrational number, while hopefully also ensuring the displayed numbers -- an irrational number, while hopefully also ensuring the displayed numbers
-- make sense if the user does a manual calculation. This is (mostly) achieved -- make sense if the user does a manual calculation. This is (mostly) achieved
-- in two ways: -- in two ways:
-- --
-- - when there is only one posting in the "from" commodity, a total price -- - when there is only one posting in the "from" commodity, a total cost
-- (@@) is used, and all available decimal digits are shown -- (@@) is used, and all available decimal digits are shown
-- --
-- - otherwise, a suitable averaged unit price (@) is applied to the relevant -- - otherwise, a suitable averaged unit cost (@) is applied to the relevant
-- postings, with display precision equal to the summed display precisions -- postings, with display precision equal to the summed display precisions
-- of the two commodities being converted between, or 2, whichever is larger. -- of the two commodities being converted between, or 2, whichever is larger.
-- --
-- (We don't always calculate a good-looking display precision for unit prices -- (We don't always calculate a good-looking display precision for unit costs
-- when the commodity display precisions are low, eg when a journal doesn't -- when the commodity display precisions are low, eg when a journal doesn't
-- use any decimal places. The minimum of 2 helps make the prices shown by the -- use any decimal places. The minimum of 2 helps make the costs shown by the
-- print command a bit less surprising in this case. Could do better.) -- print command a bit less surprising in this case. Could do better.)
-- --
inferBalancingPrices :: Transaction -> Transaction transactionInferBalancingCosts :: Transaction -> Transaction
inferBalancingPrices t@Transaction{tpostings=ps} = t{tpostings=ps'} transactionInferBalancingCosts t@Transaction{tpostings=ps} = t{tpostings=ps'}
where where
ps' = map (priceInferrerFor t BalancedVirtualPosting . priceInferrerFor t RegularPosting) ps ps' = map (costInferrerFor t BalancedVirtualPosting . costInferrerFor t RegularPosting) ps
-- | Generate a posting update function which assigns a suitable balancing -- | Generate a posting update function which assigns a suitable cost to
-- price to the posting, if and as appropriate for the given transaction and -- balance the posting, if and as appropriate for the given transaction and
-- posting type (real or balanced virtual). If we cannot or should not infer -- posting type (real or balanced virtual) (or if we cannot or should not infer
-- prices, just act as the identity on postings. -- costs, leaves the posting unchanged).
priceInferrerFor :: Transaction -> PostingType -> (Posting -> Posting) costInferrerFor :: Transaction -> PostingType -> (Posting -> Posting)
priceInferrerFor t pt = maybe id inferprice inferFromAndTo costInferrerFor t pt = maybe id infercost inferFromAndTo
where where
postings = filter ((==pt).ptype) $ tpostings t postings = filter ((==pt).ptype) $ tpostings t
pcommodities = map acommodity $ concatMap (amounts . pamount) postings pcommodities = map acommodity $ concatMap (amounts . pamount) postings
@ -314,8 +314,8 @@ priceInferrerFor t pt = maybe id inferprice inferFromAndTo
-- For each posting, if the posting type matches, there is only a single amount in the posting, -- For each posting, if the posting type matches, there is only a single amount in the posting,
-- and the commodity of the amount matches the amount we're converting from, -- and the commodity of the amount matches the amount we're converting from,
-- then set its price based on the ratio between fromamount and toamount. -- then set its cost based on the ratio between fromamount and toamount.
inferprice (fromamount, toamount) p infercost (fromamount, toamount) p
| [a] <- amounts (pamount p), ptype p == pt, acommodity a == acommodity fromamount | [a] <- amounts (pamount p), ptype p == pt, acommodity a == acommodity fromamount
= p{ pamount = mixedAmount a{aprice=Just conversionprice} = p{ pamount = mixedAmount a{aprice=Just conversionprice}
, poriginal = Just $ originalPosting p } , poriginal = Just $ originalPosting p }