New issue 184: Inventory averaging with can results in division by zero

https://bitbucket.org/blais/beancount/issues/184/inventory-averaging-with-can-results-in



Stephan Müller:



Not 100% sure whether this is a bug (or just an incoherent inventory), but in 
my mind, averaging should still work even if the inventory is atypical. 
Alternatively, one could assert that `total_units != ZERO` and show a 
meaningful message otherwise.



With this preamble: if there are offsetting amounts of the same currency with 
different costs (eg a different date), the division in `cost = Cost(total_cost 
/ total_units, cost_currency, None, None)` in the method 
[`Inventory.average`](https://bitbucket.org/blais/beancount/src/cbb3f348ec5e23f4d224f2b2f7a5811958d87873/beancount/core/inventory.py?at=default&fileviewer=file-view-default#inventory.py-302)
 (module `beancount.core.inventory`) results in a division by zero.



The following extension to 
[`TestInventory.test_average`](https://bitbucket.org/blais/beancount/src/cbb3f348ec5e23f4d224f2b2f7a5811958d87873/beancount/core/inventory_test.py?at=default&fileviewer=file-view-default#inventory_test.py-278)
 shows the problem (last test fails because the +/- 2 HOOL have different 
costs, so don't offset in the inventory but the overall position is 0):



    def test_average(self):

        # Identity, no aggregation.

        inv = I('40.50 JPY, 40.51 USD {1.01 CAD}, 40.52 CAD')

        self.assertEqual(inv.average(), inv)



        # Identity, no aggregation, with a mix of lots at cost and without

        # cost.

        inv = I('40 USD {1.01 CAD}, 40 USD')

        self.assertEqual(inv.average(), inv)



        # Aggregation.

        inv = I('40 USD {1.01 CAD}, 40 USD {1.02 CAD}')

        self.assertEqual(inv.average(), I('80.00 USD {1.015 CAD}'))



        # Aggregation, more units.

        inv = I('2 HOOL {500 USD}, 3 HOOL {520 USD}, 4 HOOL {530 USD}')

        self.assertEqual(inv.average(), I('9 HOOL {520 USD}'))



        # ADDED--------------------------------------------------------

        # Average on zero amount, same costs

        inv = I('2 HOOL {500 USD}')

        inv.add_amount(A('-2 HOOL'), Cost(D('500'), 'USD', None, None))

        self.assertEqual(inv.average(), I(''))



        # Average on zero amount, different costs

        inv = I('2 HOOL {500 USD}')

        inv.add_amount(A('-2 HOOL'),

                       Cost(D('500'), 'USD', datetime.date(2000, 1, 1), None))

        self.assertEqual(inv.average(), I(''))



A simple fix would be to add the line `if total_units == ZERO: continue` in the 
method `Inventory.average` as per below:



    def average(self):

        """Average all lots of the same currency together.



        Returns:

          An instance of Inventory.

        """

        groups = collections.defaultdict(list)

        for position in self:

            key = (position.units.currency,

                   position.cost.currency if position.cost else None)

            groups[key].append(position)



        average_inventory = Inventory()

        for (currency, cost_currency), positions in groups.items():

            total_units = sum(position.units.number

                              for position in positions)

            if total_units == ZERO: continue  # 
ADDED-----------------------------------

            units_amount = Amount(total_units, currency)



            if cost_currency:

                total_cost = sum(convert.get_cost(position).number

                                 for position in positions)

                cost = Cost(total_cost / total_units,

                            cost_currency, None, None)

            else:

                cost = None



            average_inventory.add_amount(units_amount, cost)



        return average_inventory




-- 
You received this message because you are subscribed to the Google Groups 
"Beancount" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/beancount/20170803170057.1471.25351%40celery-worker-106.ash1.bb-inf.net.
For more options, visit https://groups.google.com/d/optout.

Reply via email to