owlplanner 2025.9.15__tar.gz → 2025.11.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/INSTALL.md +1 -1
  2. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/PKG-INFO +19 -13
  3. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/README.md +18 -12
  4. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/USER_GUIDE.md +14 -14
  5. owlplanner-2025.9.15/docker/Dockerfile.build → owlplanner-2025.11.3/docker/Dockerfile.bare +2 -8
  6. owlplanner-2025.9.15/docker/Dockerfile.run → owlplanner-2025.11.3/docker/Dockerfile.static +2 -8
  7. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/docker/README.md +23 -11
  8. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/pyproject.toml +1 -1
  9. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/requirements.txt +2 -2
  10. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/src/owlplanner/plan.py +2 -2
  11. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/src/owlplanner/tax2025.py +2 -2
  12. owlplanner-2025.11.3/src/owlplanner/tax2026.py +339 -0
  13. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/src/owlplanner/timelists.py +1 -1
  14. owlplanner-2025.11.3/src/owlplanner/version.py +1 -0
  15. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/tests/test_ui_asset_allocation.py +3 -3
  16. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/tests/test_ui_compare_summaries.py +1 -1
  17. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/tests/test_ui_sskeys.py +2 -2
  18. owlplanner-2025.11.3/ui/.owlbridge.py.swo +0 -0
  19. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/ui/About_Owl.py +5 -2
  20. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/ui/Asset_Allocation.py +19 -19
  21. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/ui/Create_Case.py +19 -18
  22. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/ui/Current_Assets.py +13 -13
  23. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/ui/Documentation.py +4 -4
  24. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/ui/Fixed_Income.py +13 -12
  25. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/ui/Graphs.py +2 -2
  26. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/ui/Historical_Range.py +12 -12
  27. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/ui/Logs.py +1 -1
  28. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/ui/Monte_Carlo.py +4 -4
  29. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/ui/Optimization_Parameters.py +31 -31
  30. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/ui/Output_Files.py +3 -3
  31. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/ui/Quick_Start.py +12 -6
  32. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/ui/Rates_Selection.py +45 -45
  33. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/ui/Settings.py +5 -11
  34. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/ui/Wages_and_Contributions.py +17 -17
  35. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/ui/Worksheets.py +1 -1
  36. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/ui/main.py +1 -1
  37. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/ui/owlbridge.py +90 -90
  38. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/ui/sskeys.py +104 -73
  39. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/ui/tomlexamples.py +2 -2
  40. owlplanner-2025.9.15/src/owlplanner/version.py +0 -1
  41. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/.devcontainer/devcontainer.json +0 -0
  42. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/.flake8 +0 -0
  43. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/.gitattributes +0 -0
  44. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/.github/workflows/github-actions-runtests.yml +0 -0
  45. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/.gitignore +0 -0
  46. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/.streamlit/config.toml +0 -0
  47. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/.streamlit/fullconfig.toml +0 -0
  48. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/LICENSE +0 -0
  49. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/RELEASE_NOTES.md +0 -0
  50. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/docker/buildentrypoint.sh +0 -0
  51. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/docker/docker-compose.yml +0 -0
  52. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/docker/runentrypoint.sh +0 -0
  53. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/docs/images/AD-taxDef.png +0 -0
  54. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/docs/images/AD-taxFree.png +0 -0
  55. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/docs/images/AD-taxable.png +0 -0
  56. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/docs/images/Hist_Bequest.png +0 -0
  57. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/docs/images/Hist_Spending.png +0 -0
  58. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/docs/images/MC-tutorial2a.png +0 -0
  59. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/docs/images/MC-tutorial2b.png +0 -0
  60. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/docs/images/OwlUI.png +0 -0
  61. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/docs/images/allocations.png +0 -0
  62. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/docs/images/owl.png +0 -0
  63. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/docs/images/piecewiseConstant.png +0 -0
  64. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/docs/images/profile.png +0 -0
  65. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/docs/images/ratesCorrelations.png +0 -0
  66. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/docs/images/ratesPlot.png +0 -0
  67. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/docs/images/savingsPlot.png +0 -0
  68. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/docs/images/sourcesPlot.png +0 -0
  69. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/docs/images/spendingPlot.png +0 -0
  70. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/docs/images/taxIncomePlot.png +0 -0
  71. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/docs/images/taxesPlot.png +0 -0
  72. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/docs/owl.pdf +0 -0
  73. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/docs/owl.tex +0 -0
  74. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/examples/case_drawdowncalc-comparison-1.toml +0 -0
  75. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/examples/case_jack+jill.toml +0 -0
  76. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/examples/case_joe.toml +0 -0
  77. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/examples/case_john+sally.toml +0 -0
  78. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/examples/case_jon+jane.toml +0 -0
  79. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/examples/case_kim+sam-bequest.toml +0 -0
  80. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/examples/case_kim+sam-spending.toml +0 -0
  81. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/examples/jack+jill.xlsx +0 -0
  82. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/examples/joe.xlsx +0 -0
  83. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/examples/john+sally.xlsx +0 -0
  84. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/examples/jon+jane.xlsx +0 -0
  85. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/examples/kim+sam.xlsx +0 -0
  86. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/examples/template.xlsx +0 -0
  87. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/notebooks/john+sally.ipynb +0 -0
  88. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/notebooks/kim+sam.ipynb +0 -0
  89. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/notebooks/template.ipynb +0 -0
  90. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/notebooks/tutorial_1.ipynb +0 -0
  91. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/notebooks/tutorial_2.ipynb +0 -0
  92. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/notebooks/tutorial_3.ipynb +0 -0
  93. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/owlplanner.cmd +0 -0
  94. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/owlplanner.sh +0 -0
  95. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/pytest.ini +0 -0
  96. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/src/owlplanner/__init__.py +0 -0
  97. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/src/owlplanner/abcapi.py +0 -0
  98. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/src/owlplanner/config.py +0 -0
  99. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/src/owlplanner/data/__init__.py +0 -0
  100. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/src/owlplanner/data/rates.csv +0 -0
  101. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/src/owlplanner/mylogging.py +0 -0
  102. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/src/owlplanner/plotting/__init__.py +0 -0
  103. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/src/owlplanner/plotting/base.py +0 -0
  104. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/src/owlplanner/plotting/factory.py +0 -0
  105. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/src/owlplanner/plotting/matplotlib_backend.py +0 -0
  106. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/src/owlplanner/plotting/plotly_backend.py +0 -0
  107. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/src/owlplanner/progress.py +0 -0
  108. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/src/owlplanner/rates.py +0 -0
  109. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/src/owlplanner/utils.py +0 -0
  110. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/tests/test_logger.py +0 -0
  111. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/tests/test_regressions.py +0 -0
  112. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/tests/test_repro.py +0 -0
  113. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/tests/test_toml_cases.py +0 -0
  114. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/tests/test_units.py +0 -0
  115. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/ui/README.md +0 -0
  116. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/ui/__init__.py +0 -0
  117. {owlplanner-2025.9.15 → owlplanner-2025.11.3}/ui/progress.py +0 -0
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## A retirement exploration tool based on linear programming
4
4
 
5
- <img align=right src="https://raw.github.com/mdlacasse/Owl/main/docs/images/owl.png" width="250">
5
+ <img align=right src="https://github.com/mdlacasse/Owl/blob/main/docs/images/owl.png?raw=true" width="250">
6
6
 
7
7
  ------------------------------------------------------------------------------------
8
8
  ### About
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: owlplanner
3
- Version: 2025.9.15
3
+ Version: 2025.11.3
4
4
  Summary: Owl: Retirement planner with great wisdom
5
5
  Project-URL: HomePage, https://github.com/mdlacasse/owl
6
6
  Project-URL: Repository, https://github.com/mdlacasse/owl
@@ -709,7 +709,7 @@ Description-Content-Type: text/markdown
709
709
 
710
710
  ## A retirement exploration tool based on linear programming
711
711
 
712
- <img align=right src="https://raw.github.com/mdlacasse/Owl/main/docs/images/owl.png" width="250">
712
+ <img align=right src="https://github.com/mdlacasse/Owl/blob/main/docs/images/owl.png?raw=true" width="250">
713
713
 
714
714
  -------------------------------------------------------------------------------------
715
715
 
@@ -739,7 +739,7 @@ Strictly speaking, it is not a planning tool, but more an environment for explor
739
739
  It provides different realizations of a financial strategy through the rigorous
740
740
  mathematical optimization of relevant decision variables. Two major objective goals can be set: either
741
741
  maximize net spending, or after-tax bequest under various constraints.
742
- Look at *Basic capabilities* below for more detail.
742
+ Look at the *Capabilities* section below for more detail.
743
743
 
744
744
  One can certainly have a savings plan, but due to the volatility of financial investments,
745
745
  it is impossible to have a certain asset earnings plan. This does not mean one cannot make decisions.
@@ -758,7 +758,7 @@ collecting your data, or academic papers that share the results without really s
758
758
  the underlying mathematical models.
759
759
  The algorithms in Owl rely on the open-source HiGHS linear programming solver. The complete formulation and
760
760
  detailed description of the underlying
761
- mathematical model can be found [here](https://raw.github.com/mdlacasse/Owl/main/docs/owl.pdf).
761
+ mathematical model can be found [here](https://github.com/mdlacasse/Owl/blob/main/docs/owl.pdf).
762
762
 
763
763
  It is anticipated that most end users will use Owl through the graphical interface
764
764
  either at [owlplanner.streamlit.app](https://owlplanner.streamlit.app)
@@ -768,7 +768,7 @@ as described [here](USER_GUIDE.md).
768
768
 
769
769
  Not every retirement decision strategy can be framed as an easy-to-solve optimization problem.
770
770
  In particular, if one is interested in comparing different withdrawal strategies,
771
- [FI Calc](ficalc.app) is an elegant application that addresses this need.
771
+ [FI Calc](https://ficalc.app) is an elegant application that addresses this need.
772
772
  If, however, you also want to optimize spending, bequest, and Roth conversions, with
773
773
  an approach also considering Medicare and federal income tax over the next few years,
774
774
  then Owl is definitely a tool that can help guide your decisions.
@@ -840,7 +840,9 @@ Tax status covers married filing jointly and single, depending on the number of
840
840
  Maturation rules for Roth contributions and conversions are implemented as constraints
841
841
  limiting withdrawal amounts to cover Roth account balances for 5 years after the events.
842
842
  Medicare and IRMAA calculations are performed through a self-consistent loop on cash flow constraints.
843
- Future values are simple projections of current values with the assumed inflation rates.
843
+ They can also be optimized explicitly as an option, but this choice can lead to longer calculations
844
+ due to the use of the many additional binary variables required by the formulation.
845
+ Future Medicare and IRMAA values are simple projections of current values with the assumed inflation rates.
844
846
 
845
847
  ### Limitations
846
848
  Owl is work in progress. At the current time:
@@ -851,15 +853,16 @@ These cases are detected and will generate an error message.
851
853
  - Social security rule for surviving spouse assumes that benefits were taken at full retirement age.
852
854
  - Current version has no optimization of asset allocations between individuals and/or types of savings accounts.
853
855
  If there is interest, that could be added in the future.
854
- - In the current implementation, social securiy is always taxed at 85%.
855
- - Medicare calculations are done through a self-consistent loop.
856
- This means that the Medicare premiums are calculated after an initial solution is generated,
856
+ - In the current implementation, social securiy is always taxed at 85%, assuming that your taxable income will be larger than 34 k$ (single) or 44 k$ (married filing jointly).
857
+ - When Medicare calculations are done through a self-consistent loop,
858
+ the Medicare premiums are calculated after an initial solution is generated,
857
859
  and then a new solution is re-generated with these premiums as a constraint.
858
860
  In some situations, when the income (MAGI) is near an IRMAA bracket, oscillatory solutions can arise.
859
861
  While the solutions generated are very close to one another, Owl will pick the smallest solution
860
- for being conservative.
861
- - Part D is not included in the IRMAA calculations. Being considerably more significant,
862
- only Part B is taken into account.
862
+ for being conservative. While sometimes computationally costly,
863
+ a comparison with a full Medicare optimization should always be performed.
864
+ - Part D is not included in the IRMAA calculations. Only Part B is taken into account,
865
+ which is considerably more significant.
863
866
  - Future tax brackets are pure speculations derived from the little we know now and projected to the next 30 years.
864
867
  Your guesses are as good as mine.
865
868
 
@@ -886,8 +889,11 @@ assets to support, even with no estate being left.
886
889
  - Optimization solver from [HiGHS](https://highs.dev)
887
890
  - Streamlit Community Cloud [Streamlit](https://streamlit.io)
888
891
  - Contributors: Josh (noimjosh@gmail.com) for Docker image code,
892
+ kg333 for fixing an error in Docker's instructions,
889
893
  Dale Seng (sengsational) for great insights and suggestions,
890
- Robert E. Anderson (NH-RedAnt) for bug fixes and suggestions, Clark Jefcoat (hubcity) for fruitful interactions.
894
+ Robert E. Anderson (NH-RedAnt) for bug fixes and suggestions,
895
+ Clark Jefcoat (hubcity) for fruitful interactions,
896
+ Benjamin Quinn (blquinn) and Gene Wood (gene1wood) for improvements and bug fixes.
891
897
 
892
898
  ---------------------------------------------------------------------
893
899
 
@@ -3,7 +3,7 @@
3
3
 
4
4
  ## A retirement exploration tool based on linear programming
5
5
 
6
- <img align=right src="https://raw.github.com/mdlacasse/Owl/main/docs/images/owl.png" width="250">
6
+ <img align=right src="https://github.com/mdlacasse/Owl/blob/main/docs/images/owl.png?raw=true" width="250">
7
7
 
8
8
  -------------------------------------------------------------------------------------
9
9
 
@@ -33,7 +33,7 @@ Strictly speaking, it is not a planning tool, but more an environment for explor
33
33
  It provides different realizations of a financial strategy through the rigorous
34
34
  mathematical optimization of relevant decision variables. Two major objective goals can be set: either
35
35
  maximize net spending, or after-tax bequest under various constraints.
36
- Look at *Basic capabilities* below for more detail.
36
+ Look at the *Capabilities* section below for more detail.
37
37
 
38
38
  One can certainly have a savings plan, but due to the volatility of financial investments,
39
39
  it is impossible to have a certain asset earnings plan. This does not mean one cannot make decisions.
@@ -52,7 +52,7 @@ collecting your data, or academic papers that share the results without really s
52
52
  the underlying mathematical models.
53
53
  The algorithms in Owl rely on the open-source HiGHS linear programming solver. The complete formulation and
54
54
  detailed description of the underlying
55
- mathematical model can be found [here](https://raw.github.com/mdlacasse/Owl/main/docs/owl.pdf).
55
+ mathematical model can be found [here](https://github.com/mdlacasse/Owl/blob/main/docs/owl.pdf).
56
56
 
57
57
  It is anticipated that most end users will use Owl through the graphical interface
58
58
  either at [owlplanner.streamlit.app](https://owlplanner.streamlit.app)
@@ -62,7 +62,7 @@ as described [here](USER_GUIDE.md).
62
62
 
63
63
  Not every retirement decision strategy can be framed as an easy-to-solve optimization problem.
64
64
  In particular, if one is interested in comparing different withdrawal strategies,
65
- [FI Calc](ficalc.app) is an elegant application that addresses this need.
65
+ [FI Calc](https://ficalc.app) is an elegant application that addresses this need.
66
66
  If, however, you also want to optimize spending, bequest, and Roth conversions, with
67
67
  an approach also considering Medicare and federal income tax over the next few years,
68
68
  then Owl is definitely a tool that can help guide your decisions.
@@ -134,7 +134,9 @@ Tax status covers married filing jointly and single, depending on the number of
134
134
  Maturation rules for Roth contributions and conversions are implemented as constraints
135
135
  limiting withdrawal amounts to cover Roth account balances for 5 years after the events.
136
136
  Medicare and IRMAA calculations are performed through a self-consistent loop on cash flow constraints.
137
- Future values are simple projections of current values with the assumed inflation rates.
137
+ They can also be optimized explicitly as an option, but this choice can lead to longer calculations
138
+ due to the use of the many additional binary variables required by the formulation.
139
+ Future Medicare and IRMAA values are simple projections of current values with the assumed inflation rates.
138
140
 
139
141
  ### Limitations
140
142
  Owl is work in progress. At the current time:
@@ -145,15 +147,16 @@ These cases are detected and will generate an error message.
145
147
  - Social security rule for surviving spouse assumes that benefits were taken at full retirement age.
146
148
  - Current version has no optimization of asset allocations between individuals and/or types of savings accounts.
147
149
  If there is interest, that could be added in the future.
148
- - In the current implementation, social securiy is always taxed at 85%.
149
- - Medicare calculations are done through a self-consistent loop.
150
- This means that the Medicare premiums are calculated after an initial solution is generated,
150
+ - In the current implementation, social securiy is always taxed at 85%, assuming that your taxable income will be larger than 34 k$ (single) or 44 k$ (married filing jointly).
151
+ - When Medicare calculations are done through a self-consistent loop,
152
+ the Medicare premiums are calculated after an initial solution is generated,
151
153
  and then a new solution is re-generated with these premiums as a constraint.
152
154
  In some situations, when the income (MAGI) is near an IRMAA bracket, oscillatory solutions can arise.
153
155
  While the solutions generated are very close to one another, Owl will pick the smallest solution
154
- for being conservative.
155
- - Part D is not included in the IRMAA calculations. Being considerably more significant,
156
- only Part B is taken into account.
156
+ for being conservative. While sometimes computationally costly,
157
+ a comparison with a full Medicare optimization should always be performed.
158
+ - Part D is not included in the IRMAA calculations. Only Part B is taken into account,
159
+ which is considerably more significant.
157
160
  - Future tax brackets are pure speculations derived from the little we know now and projected to the next 30 years.
158
161
  Your guesses are as good as mine.
159
162
 
@@ -180,8 +183,11 @@ assets to support, even with no estate being left.
180
183
  - Optimization solver from [HiGHS](https://highs.dev)
181
184
  - Streamlit Community Cloud [Streamlit](https://streamlit.io)
182
185
  - Contributors: Josh (noimjosh@gmail.com) for Docker image code,
186
+ kg333 for fixing an error in Docker's instructions,
183
187
  Dale Seng (sengsational) for great insights and suggestions,
184
- Robert E. Anderson (NH-RedAnt) for bug fixes and suggestions, Clark Jefcoat (hubcity) for fruitful interactions.
188
+ Robert E. Anderson (NH-RedAnt) for bug fixes and suggestions,
189
+ Clark Jefcoat (hubcity) for fruitful interactions,
190
+ Benjamin Quinn (blquinn) and Gene Wood (gene1wood) for improvements and bug fixes.
185
191
 
186
192
  ---------------------------------------------------------------------
187
193
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## A retirement exploration tool based on linear programming
4
4
 
5
- <img align=right src="https://raw.github.com/mdlacasse/Owl/main/docs/images/owl.png" width="250">
5
+ <img align=right src="https://github.com/mdlacasse/Owl/blob/main/docs/images/owl.png?raw=true" width="250">
6
6
 
7
7
  ------------------------------------------------------------------------------------
8
8
  ### About
@@ -64,7 +64,7 @@ the boundaries of tax brackets.
64
64
  ```python
65
65
  plan.showGrossIncome(value='nominal')
66
66
  ```
67
- <img src="https://raw.github.com/mdlacasse/Owl/main/docs/images/taxIncomePlot.png" width="75%">
67
+ <img src="https://github.com/mdlacasse/Owl/blob/main/docs/images/taxIncomePlot.png?raw=true" width="75%">
68
68
 
69
69
  The optimal spending profile is shown in the next plot (in today's dollars). Notice the drop
70
70
  (recall we selected 60% survivor needs) at the passing of the first spouse.
@@ -72,26 +72,26 @@ The optimal spending profile is shown in the next plot (in today's dollars). Not
72
72
  plan.showProfile('today')
73
73
  ```
74
74
 
75
- <img src="https://raw.github.com/mdlacasse/Owl/main/docs/images/spendingPlot.png" width="75%">
75
+ <img src="https://github.com/mdlacasse/Owl/blob/main/docs/images/spendingPlot.png?raw=true" width="75%">
76
76
 
77
77
  The following plot shows the account balances in nominal value for all savings accounts owned by Jack and Jill.
78
78
  It was generated using
79
79
  ```python
80
80
  plan.showAccounts(value='nominal')
81
81
  ```
82
- <img src="https://raw.github.com/mdlacasse/Owl/main/docs/images/savingsPlot.png" width="75%">
82
+ <img src="https://github.com/mdlacasse/Owl/blob/main/docs/images/savingsPlot.png?raw=true" width="75%">
83
83
 
84
84
  while this plot shows the complex cash flow from all sources, which was generated with
85
85
  ```python
86
86
  plan.showSources(value='nominal')
87
87
  ```
88
- <img src="https://raw.github.com/mdlacasse/Owl/main/docs/images/sourcesPlot.png" width="75%">
88
+ <img src="https://github.com/mdlacasse/Owl/blob/main/docs/images/sourcesPlot.png?raw=true" width="75%">
89
89
 
90
90
  For taxes, the following call will display Medicare premiums (including Part B IRMAA fees) and federal income tax
91
91
  ```python
92
92
  plan.showTaxes(value='nominal')
93
93
  ```
94
- <img src="https://raw.github.com/mdlacasse/Owl/main/docs/images/taxesPlot.png" width="75%">
94
+ <img src="https://github.com/mdlacasse/Owl/blob/main/docs/images/taxesPlot.png?raw=true" width="75%">
95
95
 
96
96
  For the case at hand, recall that asset allocations were selected above through
97
97
 
@@ -103,9 +103,9 @@ Assets distribution in all accounts in today's $ over time can be displayed from
103
103
  ```python
104
104
  plan.showAssetDistribution(value='today')
105
105
  ```
106
- <img src="https://raw.github.com/mdlacasse/Owl/main/docs/images/AD-taxable.png" width="75%">
107
- <img src="https://raw.github.com/mdlacasse/Owl/main/docs/images/AD-taxDef.png" width="75%">
108
- <img src="https://raw.github.com/mdlacasse/Owl/main/docs/images/AD-taxFree.png" width="75%">
106
+ <img src="https://github.com/mdlacasse/Owl/blob/main/docs/images/AD-taxable.png?raw=true" width="75%">
107
+ <img src="https://github.com/mdlacasse/Owl/blob/main/docs/images/AD-taxDef.png?raw=true" width="75%">
108
+ <img src="https://github.com/mdlacasse/Owl/blob/main/docs/images/AD-taxFree.png?raw=true" width="75%">
109
109
 
110
110
  These plots are irregular because we used historical rates from 1969. The volatility of
111
111
  the rates offers Roth conversion benefits which are exploited by the optimizer.
@@ -113,7 +113,7 @@ The rates used can be displayed by:
113
113
  ```python
114
114
  plan.showRates()
115
115
  ```
116
- <img src="https://raw.github.com/mdlacasse/Owl/main/docs/images/ratesPlot.png" width="75%">
116
+ <img src="https://github.com/mdlacasse/Owl/blob/main/docs/images/ratesPlot.png?raw=true" width="75%">
117
117
 
118
118
  Values between brackets <> are the average values and volatility over the selected period.
119
119
 
@@ -121,7 +121,7 @@ For the statisticians, rates distributions and correlations between them can be
121
121
  ```python
122
122
  plan.showRatesCorrelations()
123
123
  ```
124
- <img src="https://raw.github.com/mdlacasse/Owl/main/docs/images/ratesCorrelations.png" width="75%">
124
+ <img src="https://github.com/mdlacasse/Owl/blob/main/docs/images/ratesCorrelations.png?raw=true" width="75%">
125
125
 
126
126
  A short text summary of the outcome of the optimization can be displayed through using:
127
127
  ```python
@@ -169,13 +169,13 @@ by selecting *stochastic* rates and using
169
169
  ```
170
170
  plan.runMC('maxSpending', ...)
171
171
  ```
172
- <img src="https://raw.github.com/mdlacasse/Owl/main/docs/images/MC-tutorial2a.png" width="75%">
172
+ <img src="https://github.com/mdlacasse/Owl/blob/main/docs/images/MC-tutorial2a.png?raw=true" width="75%">
173
173
 
174
174
  Similarly, the next one was generated using
175
175
  ```
176
176
  plan.runMC('maxBequest', ...)
177
177
  ```
178
- <img src="https://raw.github.com/mdlacasse/Owl/main/docs/images/MC-tutorial2b.png" width="75%">
178
+ <img src="https://github.com/mdlacasse/Owl/blob/main/docs/images/MC-tutorial2b.png?raw=true" width="75%">
179
179
 
180
180
 
181
181
  See tutorial notebooks [1](https://github.com/mdlacasse/Owl/blob/main/notebooks/tutorial_1.ipynb),
@@ -195,7 +195,7 @@ The simplest way to get started with Owl is to use the `streamlit` browser-based
195
195
  that is started by the `owlplanner.cmd` script, which will start a user interface on your own browser.
196
196
  Here is a screenshot of one of the multiple tabs of the interface:
197
197
 
198
- <img src="https://raw.github.com/mdlacasse/Owl/main/docs/images/OwlUI.png" width="100%">
198
+ <img src="https://github.com/mdlacasse/Owl/blob/main/docs/images/OwlUI.png?raw=true" width="100%">
199
199
 
200
200
  Alternatively, one can prefer using Owl from Jupyter notebooks. For that purpose, the `examples` directory
201
201
  contains many files as a tutorial. The Jupyter Notebook interface is a browser-based application
@@ -1,16 +1,10 @@
1
1
  # docker/Dockerfile
2
2
 
3
- FROM python:3.13-slim
3
+ FROM python:3.13-alpine
4
4
 
5
5
  WORKDIR /app
6
6
 
7
- RUN apt-get update; \
8
- apt-get upgrade; \
9
- apt-get install -y --no-install-recommends \
10
- curl \
11
- git \
12
- ; \
13
- rm -rf /var/lib/apt/lists/*
7
+ RUN apk update; apk upgrade; apk add curl git bash
14
8
 
15
9
  COPY buildentrypoint.sh /usr/bin/entrypoint.sh
16
10
  COPY runentrypoint.sh /usr/bin/runentrypoint.sh
@@ -1,16 +1,10 @@
1
1
  # docker/Dockerfile
2
2
 
3
- FROM python:3.13-slim
3
+ FROM python:3.13-alpine
4
4
 
5
5
  WORKDIR /app
6
6
 
7
- RUN apt-get update; \
8
- apt-get upgrade; \
9
- apt-get install -y --no-install-recommends \
10
- curl \
11
- git \
12
- ; \
13
- rm -rf /var/lib/apt/lists/*
7
+ RUN apk update; apk upgrade; apk add bash curl git
14
8
 
15
9
  COPY runentrypoint.sh /usr/bin/entrypoint.sh
16
10
 
@@ -14,37 +14,49 @@ Using this approach only requires downloading the Docker image from
14
14
  the [Docker Hub](http://hub.docker.com) and having the [Docker](http://docker.com)
15
15
  application installed on your computer.
16
16
 
17
- Downloading the Docker image from the command line:
17
+ There are two versions of the Docker image: one that has been provisioned
18
+ with all the necessary Python modules
19
+ named 'owldocker.static' and one bare image that self-installs the Owl application from
20
+ GitHub at runtime named 'owldocker.bare'.
21
+ The 'static' version, while being larger than the 'bare' version (1.6 GB vs 96 MB),
22
+ will start much faster as all the required code is contained in the image. However,
23
+ the version of Owl is fixed at the time when the container was built. Conversely,
24
+ the 'bare' version will dynamically clone Owl from GitHub and download/install all its requirements.
25
+ The resulting Owl version is the one available from GitHub at the time when the container is launched.
26
+ This leads to a slower start, but this approach guarantees to have Owl's latest version.
27
+
28
+ For downloading the Docker image from the command line (select one from 'static' or 'bare'):
29
+
18
30
  ```
19
- docker pull owlplanner/owldocker
31
+ docker pull owlplanner/owldocker.{static or bare}
20
32
  ```
21
33
  Then the container can be started (and stopped) from the command line:
22
34
  ```
23
- docker run -p 8501:8501 --rm owlplanner/owldocker
35
+ docker run -p 8501:8501 --rm owlplanner/owldocker.{static or bare}
24
36
  ```
25
37
 
26
38
  Just point your browser to http://localhost:8501 to access the Owl user interface.
27
39
  Owl will run locally and safely through a container on your computer.
28
40
 
29
41
  One can also use the Docker Desktop graphical user interface for performing the same steps.
30
- The image *owlplanner/owldocker* can be searched for and downloaded in the
42
+ The image *owlplanner/owldocker.{static or bare}* can be searched for and downloaded in the
31
43
  *Docker Hub* section of Docker Desktop. Then, on the *Images* section,
32
44
  click on the run icon for the image, and use host port 8501 to map to container port 8501.
33
45
 
34
46
  ------------------------------------------------------------------------------------
35
- ### Building the docker image
47
+ ### Building the docker images
36
48
  This approach requires cloning the Owl package from GitHub,
37
49
  and having both Python and Docker installed on your computer.
38
50
 
39
- ##### Docker image
51
+ ##### Building the Docker images
40
52
  There are two images you can create. One builds Owl at run time, while
41
53
  the other builds it statically in the image.
42
54
  These two approaches trade startup time for image space
43
- (~300 MB vs. 1.8 GB, ~22 vs. 200 sec).
55
+ (~96 MB vs. 1.5 GB, ~22 vs. 200 sec).
44
56
  First build the Docker image from the `docker` directory:
45
57
  ```shell
46
58
  cd docker
47
- docker build --no-cache -f Dockerfile.{build or run} -t owldocker .
59
+ docker build --no-cache -f Dockerfile.{static or bare} -t owlplanner/owldocker.{static or bare}:latest .
48
60
  ```
49
61
 
50
62
  #### Running the container
@@ -52,7 +64,7 @@ The container can be run directly from the command line,
52
64
  with the desired port mapping.
53
65
 
54
66
  ```shell
55
- docker run -p 8501:8501 --rm owldocker
67
+ docker run -p 8501:8501 --rm owlplanner/owldocker.{static or bare}
56
68
  ```
57
69
 
58
70
  #### Running with docker-compose
@@ -64,7 +76,7 @@ The compose file maps the host-side port to the container-side port.
64
76
  ```yml
65
77
  services:
66
78
  owl:
67
- image: owldocker
79
+ image: owldocker.{static or bare}
68
80
  restart: always
69
81
  ports:
70
82
  - 8501:8501
@@ -75,4 +87,4 @@ As before, just point your browser to http://localhost:8501 to access the Owl us
75
87
  ------------------------------------------------------------------------------------
76
88
 
77
89
  #### Credits
78
- Josh (noimjosh@gmail.com)
90
+ Josh (noimjosh@gmail.com), kg333 (matthew@kyengineer.com)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "owlplanner"
7
- version = "2025.09.15"
7
+ version = "2025.11.03"
8
8
  authors = [
9
9
  { name="Martin-D. Lacasse", email="martin.d.lacasse@gmail.com" },
10
10
  ]
@@ -5,8 +5,8 @@ seaborn
5
5
  pandas
6
6
  openpyxl
7
7
  odfpy
8
- plotly
8
+ plotly >= 6.3
9
9
  pulp
10
10
  scipy
11
- streamlit >= 1.49
11
+ streamlit
12
12
  toml
@@ -1223,8 +1223,8 @@ class Plan(object):
1223
1223
 
1224
1224
  for i in range(self.N_i):
1225
1225
  for j in range(self.N_j):
1226
- backTau = 1 - yearSpent * np.sum(self.tau_kn[:, 0] * self.alpha_ijkn[i, j, :, 0])
1227
- rhs = self.beta_ij[i, j] * backTau
1226
+ backTau = 1 + yearSpent * np.sum(self.tau_kn[:, 0] * self.alpha_ijkn[i, j, :, 0])
1227
+ rhs = self.beta_ij[i, j] / backTau
1228
1228
  self.B.setRange(_q3(self.C["b"], i, j, 0, self.N_i, self.N_j, self.N_n + 1), rhs, rhs)
1229
1229
 
1230
1230
  def _add_surplus_deposit_linking(self):
@@ -70,7 +70,7 @@ taxBrackets_preTCJA = np.array(
70
70
  ]
71
71
  )
72
72
 
73
- # These are 2025 current (adjusted for inflation).
73
+ # These are 2025 current.
74
74
  stdDeduction_OBBBA = np.array([15750, 31500]) # Single, MFJ
75
75
  # These are speculated (adjusted for inflation).
76
76
  stdDeduction_preTCJA = np.array([8300, 16600]) # Single, MFJ
@@ -78,7 +78,7 @@ stdDeduction_preTCJA = np.array([8300, 16600]) # Single, MFJ
78
78
  # These are current (adjusted for inflation) per individual.
79
79
  extra65Deduction = np.array([2000, 1600]) # Single, MFJ
80
80
 
81
- # Thresholds for capital gains (adjusted for inflation).
81
+ # Thresholds setting capital gains brackets 0%, 15%, 20% (adjusted for inflation).
82
82
  capGainRates = np.array(
83
83
  [
84
84
  [48350, 533400],