ChrisCScott / forecaster

A personal finances forecasting tool for Canadian retirement planning
Other
1 stars 2 forks source link

Refactor Forecaster #13

Closed ChrisCScott closed 5 years ago

ChrisCScott commented 6 years ago

Refactor Forecaster to provide generic logic for taking in a mapping of {type: {'argname': defaultval}} and building objects based on that mapping. It would only be necessary to provide a handful of methods to do this, and then we could rely on client code passing suitable object types to the method.

For instance, client code might call add_account(cls=RRSP, *args, **kwargs), which would build an RRSP with the passed args and would fill in any not-provided args if defaults are provided in the mapping. It wouldn't be necessary to implement add_rrsp (although we might need to provide the subclass ForecasterCanada to provide a Canada-specific mapping).

It might be necessary for defaultval to be a string which is executed via eval at the time of building the related object. This lets us accommodate attributes of Forecaster which might not be defined at class-definition (or even init) time, like self.scenario.

Consider whether we can avoid using eval by testing for attribute syntax and then getting the attribute (e.g. using operator.attrgetter to allow for dynamically resolving nested attributes). If we're OK with using eval, this likely isn't necessary.

It will still be necessary to provide, e.g., an add_account method to ensure that the appropriate assets and debts objects are populated, although it might not be necessary to add the various add_* methods for subclasses of Account. It may also be practical to avoid the various set_*_strategy methods, e.g. if the methods return strategy objects, which are then assigned manually to the relevant attribute.

ChrisCScott commented 6 years ago

Alternatively, migrate much of that functionality to Settings by adding a setting-to-arg dict for each class and providing a build_* function for each supported object type that (a) receives args from users and (b) fills in any missing args with Settings defaults.

This logic can be implemented generically, so each specific function could simply consist of referencing the appropriate mapping and calling generic code.

We could then delete Forecaster entirely and require that users build objects in the usual way before building a Forecast. (Consider how this might interact with inflation_adjust; perhaps that should be kept in Scenario and passed as an optional arg on a per-function-call basis? Or perhaps simply at __init__ time - can we build out inflation-adjusted contribution room limits/etc. statically?)