owlplanner 2025.11.9__tar.gz → 2025.12.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 (121) hide show
  1. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/PKG-INFO +12 -9
  2. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/README.md +11 -8
  3. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/RELEASE_NOTES.md +12 -0
  4. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docker/README.md +1 -1
  5. owlplanner-2025.12.3/docker/build.cmd +7 -0
  6. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/examples/case_drawdowncalc-comparison-1.toml +2 -1
  7. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/examples/case_jack+jill.toml +2 -1
  8. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/examples/case_joe.toml +2 -1
  9. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/examples/case_john+sally.toml +2 -1
  10. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/examples/case_jon+jane.toml +2 -1
  11. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/examples/case_kim+sam-bequest.toml +2 -1
  12. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/examples/case_kim+sam-spending.toml +2 -1
  13. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/notebooks/john+sally.ipynb +2 -2
  14. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/notebooks/kim+sam.ipynb +2 -2
  15. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/notebooks/template.ipynb +4 -4
  16. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/notebooks/tutorial_1.ipynb +5 -5
  17. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/notebooks/tutorial_2.ipynb +2 -2
  18. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/notebooks/tutorial_3.ipynb +2 -2
  19. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/src/owlplanner/config.py +11 -8
  20. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/src/owlplanner/plan.py +75 -31
  21. owlplanner-2025.12.3/src/owlplanner/socialsecurity.py +89 -0
  22. owlplanner-2025.12.3/src/owlplanner/version.py +1 -0
  23. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/tests/test_regressions.py +16 -6
  24. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/tests/test_repro.py +14 -13
  25. owlplanner-2025.12.3/tests/test_socsec.py +72 -0
  26. owlplanner-2025.12.3/ui/AI +48 -0
  27. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/ui/Create_Case.py +16 -4
  28. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/ui/Documentation.py +80 -42
  29. owlplanner-2025.12.3/ui/Fixed_Income.py +92 -0
  30. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/ui/Quick_Start.py +13 -12
  31. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/ui/Wages_and_Contributions.py +2 -2
  32. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/ui/owlbridge.py +10 -5
  33. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/ui/sskeys.py +5 -2
  34. owlplanner-2025.11.9/src/owlplanner/version.py +0 -1
  35. owlplanner-2025.11.9/ui/Fixed_Income.py +0 -68
  36. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/.devcontainer/devcontainer.json +0 -0
  37. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/.flake8 +0 -0
  38. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/.gitattributes +0 -0
  39. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/.github/workflows/github-actions-runtests.yml +0 -0
  40. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/.gitignore +0 -0
  41. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/.streamlit/config.toml +0 -0
  42. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/.streamlit/fullconfig.toml +0 -0
  43. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/INSTALL.md +0 -0
  44. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/LICENSE +0 -0
  45. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/USER_GUIDE.md +0 -0
  46. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docker/Dockerfile.bare +0 -0
  47. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docker/Dockerfile.static +0 -0
  48. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docker/buildentrypoint.sh +0 -0
  49. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docker/docker-compose.yml +0 -0
  50. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docker/runentrypoint.sh +0 -0
  51. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docs/images/AD-taxDef.png +0 -0
  52. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docs/images/AD-taxFree.png +0 -0
  53. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docs/images/AD-taxable.png +0 -0
  54. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docs/images/Hist_Bequest.png +0 -0
  55. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docs/images/Hist_Spending.png +0 -0
  56. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docs/images/MC-tutorial2a.png +0 -0
  57. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docs/images/MC-tutorial2b.png +0 -0
  58. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docs/images/OwlUI.png +0 -0
  59. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docs/images/allocations.png +0 -0
  60. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docs/images/owl.png +0 -0
  61. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docs/images/piecewiseConstant.png +0 -0
  62. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docs/images/profile.png +0 -0
  63. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docs/images/ratesCorrelations.png +0 -0
  64. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docs/images/ratesPlot.png +0 -0
  65. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docs/images/savingsPlot.png +0 -0
  66. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docs/images/sourcesPlot.png +0 -0
  67. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docs/images/spendingPlot.png +0 -0
  68. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docs/images/taxIncomePlot.png +0 -0
  69. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docs/images/taxesPlot.png +0 -0
  70. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docs/owl.pdf +0 -0
  71. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/docs/owl.tex +0 -0
  72. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/examples/jack+jill.xlsx +0 -0
  73. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/examples/joe.xlsx +0 -0
  74. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/examples/john+sally.xlsx +0 -0
  75. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/examples/jon+jane.xlsx +0 -0
  76. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/examples/kim+sam.xlsx +0 -0
  77. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/examples/template.xlsx +0 -0
  78. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/owlplanner.cmd +0 -0
  79. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/owlplanner.sh +0 -0
  80. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/pyproject.toml +0 -0
  81. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/pytest.ini +0 -0
  82. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/requirements.txt +0 -0
  83. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/src/owlplanner/__init__.py +0 -0
  84. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/src/owlplanner/abcapi.py +0 -0
  85. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/src/owlplanner/data/__init__.py +0 -0
  86. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/src/owlplanner/data/rates.csv +0 -0
  87. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/src/owlplanner/mylogging.py +0 -0
  88. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/src/owlplanner/plotting/__init__.py +0 -0
  89. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/src/owlplanner/plotting/base.py +0 -0
  90. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/src/owlplanner/plotting/factory.py +0 -0
  91. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/src/owlplanner/plotting/matplotlib_backend.py +0 -0
  92. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/src/owlplanner/plotting/plotly_backend.py +0 -0
  93. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/src/owlplanner/progress.py +0 -0
  94. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/src/owlplanner/rates.py +0 -0
  95. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/src/owlplanner/tax2025.py +0 -0
  96. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/src/owlplanner/tax2026.py +0 -0
  97. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/src/owlplanner/timelists.py +0 -0
  98. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/src/owlplanner/utils.py +0 -0
  99. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/tests/test_logger.py +0 -0
  100. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/tests/test_toml_cases.py +0 -0
  101. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/tests/test_ui_asset_allocation.py +0 -0
  102. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/tests/test_ui_compare_summaries.py +0 -0
  103. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/tests/test_ui_sskeys.py +0 -0
  104. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/tests/test_units.py +0 -0
  105. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/ui/About_Owl.py +0 -0
  106. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/ui/Asset_Allocation.py +0 -0
  107. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/ui/Current_Assets.py +0 -0
  108. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/ui/Graphs.py +0 -0
  109. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/ui/Historical_Range.py +0 -0
  110. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/ui/Logs.py +0 -0
  111. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/ui/Monte_Carlo.py +0 -0
  112. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/ui/Optimization_Parameters.py +0 -0
  113. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/ui/Output_Files.py +0 -0
  114. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/ui/README.md +0 -0
  115. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/ui/Rates_Selection.py +0 -0
  116. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/ui/Settings.py +0 -0
  117. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/ui/Worksheets.py +0 -0
  118. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/ui/__init__.py +0 -0
  119. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/ui/main.py +0 -0
  120. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/ui/progress.py +0 -0
  121. {owlplanner-2025.11.9 → owlplanner-2025.12.3}/ui/tomlexamples.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: owlplanner
3
- Version: 2025.11.9
3
+ Version: 2025.12.3
4
4
  Summary: Owl - Optimal Wealth Lab: 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
@@ -722,16 +722,16 @@ Users can select varying return rates to perform historical back testing,
722
722
  stochastic rates for performing Monte Carlo analyses,
723
723
  or fixed rates either derived from historical averages, or set by the user.
724
724
 
725
- There are a few ways to run Owl:
725
+ There are three ways to run Owl:
726
726
 
727
- - Run Owl directly on the Streamlit Community Server at
727
+ - **Streamlit Hub:** Run Owl remotely as hosted on the Streamlit Community Server at
728
728
  [owlplanner.streamlit.app](https://owlplanner.streamlit.app).
729
729
 
730
- - Run locally on your computer using a Docker image.
731
- Follow these [instructions](docker/README.md) for this option.
730
+ - **Docker Container:** Run Owl locally on your computer using a Docker image.
731
+ Follow these [instructions](docker/README.md) for using this option.
732
732
 
733
- - Run locally on your computer using Python code and libraries.
734
- Follow these [instructions](INSTALL.md) to install Owl from the source code and run it on your computer.
733
+ - **Self-hosting:** Run Owl locally on your computer using Python code and libraries.
734
+ Follow these [instructions](INSTALL.md) to install from the source code and self-host on your own computer.
735
735
 
736
736
  -------------------------------------------------------------------------------------
737
737
  ## Overview
@@ -760,7 +760,7 @@ There are and were
760
760
  good retirement optimizers in the recent past, but the vast majority of them are either proprietary platforms
761
761
  collecting your data, or academic papers that share the results without really sharing the details of
762
762
  the underlying mathematical models.
763
- The algorithms in Owl rely on the open-source HiGHS linear programming solver and but they have also been ported to
763
+ The algorithms in Owl rely on the open-source HiGHS linear programming solver but they have also been ported and tested on
764
764
  other platforms such as Mosek and COIN-OR. The complete formulation and
765
765
  detailed description of the underlying
766
766
  mathematical model can be found [here](https://github.com/mdlacasse/Owl/blob/main/docs/owl.pdf).
@@ -849,13 +849,16 @@ They can also be optimized explicitly as an option, but this choice can lead to
849
849
  due to the use of the many additional binary variables required by the formulation.
850
850
  Future Medicare and IRMAA values are simple projections of current values with the assumed inflation rates.
851
851
 
852
+ Owl has a basic social security calculator that determines the actual benefits based on the individual's
853
+ primary insurance amount (PIA), full retirement age (FRA), and claiming age. Both
854
+ spousal's benefits and survivor's benefits are calculated for non-complex cases.
855
+
852
856
  ### Limitations
853
857
  Owl is work in progress. At the current time:
854
858
  - Only the US federal income tax is considered (and minimized through the optimization algorithm).
855
859
  Head of household filing status has not been added but can easily be.
856
860
  - Required minimum distributions are calculated, but tables for spouses more than 10 years apart are not included.
857
861
  These cases are detected and will generate an error message.
858
- - Social security rule for surviving spouse assumes that benefits were taken at full retirement age.
859
862
  - Current version has no optimization of asset allocations between individuals and/or types of savings accounts.
860
863
  If there is interest, that could be added in the future.
861
864
  - 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).
@@ -15,16 +15,16 @@ Users can select varying return rates to perform historical back testing,
15
15
  stochastic rates for performing Monte Carlo analyses,
16
16
  or fixed rates either derived from historical averages, or set by the user.
17
17
 
18
- There are a few ways to run Owl:
18
+ There are three ways to run Owl:
19
19
 
20
- - Run Owl directly on the Streamlit Community Server at
20
+ - **Streamlit Hub:** Run Owl remotely as hosted on the Streamlit Community Server at
21
21
  [owlplanner.streamlit.app](https://owlplanner.streamlit.app).
22
22
 
23
- - Run locally on your computer using a Docker image.
24
- Follow these [instructions](docker/README.md) for this option.
23
+ - **Docker Container:** Run Owl locally on your computer using a Docker image.
24
+ Follow these [instructions](docker/README.md) for using this option.
25
25
 
26
- - Run locally on your computer using Python code and libraries.
27
- Follow these [instructions](INSTALL.md) to install Owl from the source code and run it on your computer.
26
+ - **Self-hosting:** Run Owl locally on your computer using Python code and libraries.
27
+ Follow these [instructions](INSTALL.md) to install from the source code and self-host on your own computer.
28
28
 
29
29
  -------------------------------------------------------------------------------------
30
30
  ## Overview
@@ -53,7 +53,7 @@ There are and were
53
53
  good retirement optimizers in the recent past, but the vast majority of them are either proprietary platforms
54
54
  collecting your data, or academic papers that share the results without really sharing the details of
55
55
  the underlying mathematical models.
56
- The algorithms in Owl rely on the open-source HiGHS linear programming solver and but they have also been ported to
56
+ The algorithms in Owl rely on the open-source HiGHS linear programming solver but they have also been ported and tested on
57
57
  other platforms such as Mosek and COIN-OR. The complete formulation and
58
58
  detailed description of the underlying
59
59
  mathematical model can be found [here](https://github.com/mdlacasse/Owl/blob/main/docs/owl.pdf).
@@ -142,13 +142,16 @@ They can also be optimized explicitly as an option, but this choice can lead to
142
142
  due to the use of the many additional binary variables required by the formulation.
143
143
  Future Medicare and IRMAA values are simple projections of current values with the assumed inflation rates.
144
144
 
145
+ Owl has a basic social security calculator that determines the actual benefits based on the individual's
146
+ primary insurance amount (PIA), full retirement age (FRA), and claiming age. Both
147
+ spousal's benefits and survivor's benefits are calculated for non-complex cases.
148
+
145
149
  ### Limitations
146
150
  Owl is work in progress. At the current time:
147
151
  - Only the US federal income tax is considered (and minimized through the optimization algorithm).
148
152
  Head of household filing status has not been added but can easily be.
149
153
  - Required minimum distributions are calculated, but tables for spouses more than 10 years apart are not included.
150
154
  These cases are detected and will generate an error message.
151
- - Social security rule for surviving spouse assumes that benefits were taken at full retirement age.
152
155
  - Current version has no optimization of asset allocations between individuals and/or types of savings accounts.
153
156
  If there is interest, that could be added in the future.
154
157
  - 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).
@@ -1,3 +1,15 @@
1
+ ### Version 2025.12.03
2
+ - Coded social security to use monthly PIA instead of annual amount
3
+ - Added exact routines for FRA and increase/decrease factors due to claiming age
4
+ - Added exact spousal benefits
5
+ - Adjusted documentation for social security
6
+ - Added birth month for more precise calculation on first year of social security
7
+ - Added month to age for claiming social security
8
+
9
+ ### Version 2025.11.29
10
+ - Fixed social security for survivor
11
+ - Enhanced documentation for SS amounts
12
+
1
13
  ### Version 2025.11.09
2
14
  - Moved development status to production/stable in pyproject
3
15
  - Made version propagate everywhere needed
@@ -9,7 +9,7 @@
9
9
  This document describes how to run Owl using a Docker container.
10
10
 
11
11
  ------------------------------------------------------------------------------------
12
- ### Run Owl without the source code
12
+ ### Running Owl without the source code
13
13
  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.
@@ -0,0 +1,7 @@
1
+ ::
2
+ :: A simple script to build both Docker images
3
+ ::
4
+ docker build --no-cache -f Dockerfile.bare -t owlplanner/owldocker.bare:latest .
5
+ docker push owlplanner/owldocker.bare
6
+ docker build --no-cache -f Dockerfile.static -t owlplanner/owldocker.static:latest .
7
+ docker push owlplanner/owldocker.static
@@ -5,6 +5,7 @@ Description = "This is a case involving a single individual. Case is used for co
5
5
  Status = "single"
6
6
  Names = [ "Charles",]
7
7
  "Birth year" = [ 1966,]
8
+ "Birth month" = [ 1,]
8
9
  "Life expectancy" = [ 89,]
9
10
  "Start date" = "2025-01-01"
10
11
 
@@ -20,7 +21,7 @@ Names = [ "Charles",]
20
21
  "Pension amounts" = [ 0.0,]
21
22
  "Pension ages" = [ 65,]
22
23
  "Pension indexed" = [ true,]
23
- "Social security amounts" = [ 36.0,]
24
+ "Social security PIA amounts" = [ 3000,]
24
25
  "Social security ages" = [ 70,]
25
26
 
26
27
  ["Rates Selection"]
@@ -5,6 +5,7 @@ Description = "This example aims to demonstrate some of Owl's capabilities. Jack
5
5
  Status = "married"
6
6
  Names = [ "Jack", "Jill",]
7
7
  "Birth year" = [ 1962, 1965,]
8
+ "Birth month" = [ 1, 1,]
8
9
  "Life expectancy" = [ 89, 92,]
9
10
  "Start date" = "01-01"
10
11
 
@@ -22,7 +23,7 @@ Names = [ "Jack", "Jill",]
22
23
  "Pension amounts" = [ 0.0, 10.5,]
23
24
  "Pension ages" = [ 65, 65,]
24
25
  "Pension indexed" = [ false, false,]
25
- "Social security amounts" = [ 28.4, 19.7,]
26
+ "Social security PIA amounts" = [ 2360, 1642,]
26
27
  "Social security ages" = [ 70, 62,]
27
28
 
28
29
  ["Rates Selection"]
@@ -5,6 +5,7 @@ Description = "This is an example of a case involving a single individual. Joe i
5
5
  Status = "single"
6
6
  Names = [ "Joe",]
7
7
  "Birth year" = [ 1966,]
8
+ "Birth month" = [ 1,]
8
9
  "Life expectancy" = [ 89,]
9
10
  "Start date" = "01-01"
10
11
 
@@ -20,7 +21,7 @@ Names = [ "Joe",]
20
21
  "Pension amounts" = [ 18.0,]
21
22
  "Pension ages" = [ 65,]
22
23
  "Pension indexed" = [ true,]
23
- "Social security amounts" = [ 28.4,]
24
+ "Social security PIA amounts" = [ 2360,]
24
25
  "Social security ages" = [ 67,]
25
26
 
26
27
  ["Rates Selection"]
@@ -5,6 +5,7 @@ Description = "This example reproduces the case of John and Sally, discussed by
5
5
  Status = "married"
6
6
  Names = [ "John", "Sally",]
7
7
  "Birth year" = [ 1962, 1962,]
8
+ "Birth month" = [ 1, 1,]
8
9
  "Life expectancy" = [ 92, 92,]
9
10
  "Start date" = "01-01"
10
11
 
@@ -22,7 +23,7 @@ Names = [ "John", "Sally",]
22
23
  "Pension amounts" = [ 0.0, 0.0,]
23
24
  "Pension ages" = [ 65, 65,]
24
25
  "Pension indexed" = [ false, false,]
25
- "Social security amounts" = [ 36.0, 21.6,]
26
+ "Social security PIA amounts" = [ 3000, 1800,]
26
27
  "Social security ages" = [ 67, 67,]
27
28
 
28
29
  ["Rates Selection"]
@@ -5,6 +5,7 @@ Description = "This case reproduces a similar case discussed a while back on i-o
5
5
  Status = "married"
6
6
  Names = [ "Jon", "Jane",]
7
7
  "Birth year" = [ 1965, 1968,]
8
+ "Birth month" = [ 1, 1,]
8
9
  "Life expectancy" = [ 92, 92,]
9
10
  "Start date" = "01-01"
10
11
 
@@ -22,7 +23,7 @@ Names = [ "Jon", "Jane",]
22
23
  "Pension amounts" = [ 0.0, 0.0,]
23
24
  "Pension ages" = [ 65, 65,]
24
25
  "Pension indexed" = [ false, false,]
25
- "Social security amounts" = [ 21.0, 21.0,]
26
+ "Social security PIA amounts" = [ 1750, 1750,]
26
27
  "Social security ages" = [ 65, 65,]
27
28
 
28
29
  ["Rates Selection"]
@@ -5,6 +5,7 @@ Description = "This is the case of Kim and Sam used as an example case for optim
5
5
  Status = "married"
6
6
  Names = [ "Kim", "Sam",]
7
7
  "Birth year" = [ 1966, 1967,]
8
+ "Birth month" = [ 1, 1,]
8
9
  "Life expectancy" = [ 86, 89,]
9
10
  "Start date" = "01-01"
10
11
 
@@ -22,7 +23,7 @@ Names = [ "Kim", "Sam",]
22
23
  "Pension amounts" = [ 0.0, 0.0,]
23
24
  "Pension ages" = [ 65, 65,]
24
25
  "Pension indexed" = [ false, false,]
25
- "Social security amounts" = [ 45.0, 25.0,]
26
+ "Social security PIA amounts" = [ 3750, 2083,]
26
27
  "Social security ages" = [ 70, 68,]
27
28
 
28
29
  ["Rates Selection"]
@@ -5,6 +5,7 @@ Description = "This is the case of Kim and Sam used as an example case for optim
5
5
  Status = "married"
6
6
  Names = [ "Kim", "Sam",]
7
7
  "Birth year" = [ 1966, 1967,]
8
+ "Birth month" = [ 1, 1,]
8
9
  "Life expectancy" = [ 86, 89,]
9
10
  "Start date" = "01-01"
10
11
 
@@ -22,7 +23,7 @@ Names = [ "Kim", "Sam",]
22
23
  "Pension amounts" = [ 0.0, 0.0,]
23
24
  "Pension ages" = [ 65, 65,]
24
25
  "Pension indexed" = [ false, false,]
25
- "Social security amounts" = [ 45.0, 25.0,]
26
+ "Social security PIA amounts" = [ 3750, 2083,]
26
27
  "Social security ages" = [ 70, 68,]
27
28
 
28
29
  ["Rates Selection"]
@@ -41,14 +41,14 @@
41
41
  "%%time\n",
42
42
  "import owlplanner as owl\n",
43
43
  "\n",
44
- "plan = owl.Plan(['John', 'Sally'], [1962, 1962], [92, 92], 'john+sally')\n",
44
+ "plan = owl.Plan(['John', 'Sally'], [1962, 1962], [1, 1], [92, 92], 'john+sally')\n",
45
45
  "# plan.setPlotBackend(\"plotly\")\n",
46
46
  "plan.setAccountBalances(taxable=[200, 200], taxDeferred=[750, 750], taxFree=[50, 50])\n",
47
47
  "# Unrealistic empty contributions and wages\n",
48
48
  "plan.readContributions('../examples/john+sally.xlsx')\n",
49
49
  "# plan.setInterpolationMethod('s-curve')\n",
50
50
  "plan.setAllocationRatios('individual', generic=[[[60, 40, 0, 0], [60, 40, 0, 0]], [[60, 40, 0, 0], [60, 40, 0, 0]]])\n",
51
- "plan.setSocialSecurity([36, 21.6], [67, 67])\n",
51
+ "plan.setSocialSecurity([3000, 1800], [67, 67])\n",
52
52
  "#plan.setSpendingProfile('smile')\n",
53
53
  "plan.setSpendingProfile('flat')\n",
54
54
  "plan.setRates('historical average', 1990, 2023)\n",
@@ -27,7 +27,7 @@
27
27
  "outputs": [],
28
28
  "source": [
29
29
  "import owlplanner as owl\n",
30
- "p = owl.Plan(['Kim', 'Sam'], [1966, 1967], [86, 89], 'kim+sam-spending', verbose=True)\n",
30
+ "p = owl.Plan(['Kim', 'Sam'], [1966, 1967], [1, 1], [86, 89], 'kim+sam-spending', verbose=True)\n",
31
31
  "# p.setPlotBackend(\"plotly\")"
32
32
  ]
33
33
  },
@@ -39,7 +39,7 @@
39
39
  "outputs": [],
40
40
  "source": [
41
41
  "# p.setPension([0, 0], [65, 65])\n",
42
- "p.setSocialSecurity([45, 25], [70, 68])"
42
+ "p.setSocialSecurity([3750, 2083], [70, 68])"
43
43
  ]
44
44
  },
45
45
  {
@@ -135,7 +135,7 @@
135
135
  "metadata": {},
136
136
  "outputs": [],
137
137
  "source": [
138
- "plan = owl.Plan(['Kim', 'Sam'], [YYYY, YYYY], [AA, AA], 'Kim+Sam-spending', verbose=True)"
138
+ "plan = owl.Plan(['Kim', 'Sam'], [YYYY, YYYY], [MM, MM], [AA, AA], 'Kim+Sam-spending', verbose=True)"
139
139
  ]
140
140
  },
141
141
  {
@@ -340,13 +340,13 @@
340
340
  "metadata": {},
341
341
  "source": [
342
342
  "## What about anticipated fixed income?\n",
343
- "Pension and social security are fixed income. Model here assumes that pension income is not inflation adjusted while social security benefits are (but Owl can easily be modified to account for inflation-adjusted pensions). Numbers to be provided are the predicted annual amount for each spouse and the age of the commencement of benefits.\n",
343
+ "Pension and social security are fixed income. Pension income can be adjusted for inflation as social security benefits are. Numbers to be provided are the predicted annual amount for each spouse and the age of the commencement of benefits.\n",
344
344
  "\n",
345
345
  "By default, no pension benefits are assumed. This can also be specified explicitly by entering zeros (0) as entries, as in\n",
346
346
  "\n",
347
- " plan.setPension([0, 0], [65, 65])\n",
347
+ " plan.setPension([0, 0], [65, 65], [False, False])\n",
348
348
  " \n",
349
- "For social security, one must provide the predicted annual amount(s) and the starting age(s) at which benefits are anticipated to be received. There are plenty of social security benefit estimators on the web, including the info you can get directly from your own account at the Social Security Administration (ssa.gov). Another interesting calculator can be found at www.opensocialsecurity.com. This calculator allows you to compare different scenarios regarding your commencement age through a sensitivity plot.\n"
349
+ "For social security, one must provide the predicted monthly Primary Insurance Amount(s) and the starting age(s) at which benefits are anticipated to be received. There are plenty of social security benefit estimators on the web, including the info you can get directly from your own account at the Social Security Administration (ssa.gov). Another interesting calculator can be found at www.opensocialsecurity.com. This calculator allows you to compare different scenarios regarding your commencement age through a sensitivity plot.\n"
350
350
  ]
351
351
  },
352
352
  {
@@ -133,7 +133,7 @@
133
133
  "metadata": {},
134
134
  "outputs": [],
135
135
  "source": [
136
- "plan = owl.Plan(['Jack', 'Jill'], [1962, 1965], [89, 92], 'jack+jill-spending-69')"
136
+ "plan = owl.Plan(['Jack', 'Jill'], [1962, 1965], [1, 1], [89, 92], 'jack+jill-spending-69')"
137
137
  ]
138
138
  },
139
139
  {
@@ -332,13 +332,13 @@
332
332
  "metadata": {},
333
333
  "source": [
334
334
  "## What about anticipated fixed income?\n",
335
- "Pension and social security are fixed income. Model here assumes that pension income is not inflation adjusted while social security benefits are (but Owl can easily be modified to account for inflation-adjusted pensions). Numbers to be provided are the predicted annual amount for each spouse and the age of the commencement of benefits. Values are expressed in today's dollars (as do statements from the Social Security Administration).\n",
335
+ "Pension and social security are fixed income. Pension income can be inflation adjusted as social security benefits are. Numbers to be provided are the predicted annual amount for each spouse and the age of the commencement of benefits. Values are expressed in today's dollars (as do statements from the Social Security Administration).\n",
336
336
  "\n",
337
337
  "By default, no pension benefits are assumed. This can also be specified explicitly by entering zeros (0) as entries, as in\n",
338
338
  "\n",
339
339
  " plan.setPension([0, 0], [65, 65])\n",
340
340
  " \n",
341
- "For social security, one must provide the predicted annual amount(s) and the starting age(s) at which benefits are anticipated to be received. There are plenty of social security benefit estimators on the web, including the info you can get directly from your own account at the Social Security Administration (ssa.gov). Another interesting calculator can be found at www.opensocialsecurity.com. This calculator allows you to compare different scenarios regarding your commencement age through a sensitivity plot.\n"
341
+ "For social security, one must provide the predicted monthly Primary Insurance Amount(s) and the starting age(s) at which benefits are anticipated to be received. There are plenty of social security benefit estimators on the web, including the info you can get directly from your own account at the Social Security Administration (ssa.gov). Another interesting calculator can be found at www.opensocialsecurity.com. This calculator allows you to compare different scenarios regarding your commencement age through a sensitivity plot.\n"
342
342
  ]
343
343
  },
344
344
  {
@@ -346,7 +346,7 @@
346
346
  "id": "43bd9a2a-e2bf-434f-88ac-b0a70dcdd1fd",
347
347
  "metadata": {},
348
348
  "source": [
349
- "Here, Jill has an unindexed pension of \\\\$10 k per year. Both Jack and Jill believe they have good genes and decided to take their social security benefits at age 70. The amounts provided (28k\\\\$ and 25k\\\\$) are estimation of the amounts they would receive at age 70."
349
+ "Here, Jill has an unindexed pension of \\\\$10 k per year. Both Jack and Jill believe they have good genes and decided to take their social security benefits at age 70. The amounts provided (28k\\\\$ and 25k\\\\$) are estimation of the amounts they would receive at Full Retirement Age (FRA)."
350
350
  ]
351
351
  },
352
352
  {
@@ -357,7 +357,7 @@
357
357
  "outputs": [],
358
358
  "source": [
359
359
  "plan.setPension([0, 10], [65, 65])\n",
360
- "plan.setSocialSecurity([28, 25], [70, 70])"
360
+ "plan.setSocialSecurity([2333, 2083], [70, 70])"
361
361
  ]
362
362
  },
363
363
  {
@@ -95,7 +95,7 @@
95
95
  "metadata": {},
96
96
  "outputs": [],
97
97
  "source": [
98
- "plan = owl.Plan(['Jack', 'Jill'], [1962, 1965], [89, 92], 'jack+jill-spending-MC', verbose=True)"
98
+ "plan = owl.Plan(['Jack', 'Jill'], [1962, 1965], [1, 1], [89, 92], 'jack+jill-spending-MC', verbose=True)"
99
99
  ]
100
100
  },
101
101
  {
@@ -145,7 +145,7 @@
145
145
  "outputs": [],
146
146
  "source": [
147
147
  "plan.setPension([0, 10], [65, 65])\n",
148
- "plan.setSocialSecurity([28, 25], [70, 70])"
148
+ "plan.setSocialSecurity([2333, 2083], [70, 70])"
149
149
  ]
150
150
  },
151
151
  {
@@ -95,7 +95,7 @@
95
95
  "metadata": {},
96
96
  "outputs": [],
97
97
  "source": [
98
- "plan = owl.Plan(['Jack', 'Jill'], [1962, 1965], [89, 92], 'jack+jill-tutorial3', verbose=True)"
98
+ "plan = owl.Plan(['Jack', 'Jill'], [1962, 1965], [1, 1], [89, 92], 'jack+jill-tutorial3', verbose=True)"
99
99
  ]
100
100
  },
101
101
  {
@@ -145,7 +145,7 @@
145
145
  "outputs": [],
146
146
  "source": [
147
147
  "plan.setPension([0, 10], [65, 65])\n",
148
- "plan.setSocialSecurity([28, 25], [70, 70])"
148
+ "plan.setSocialSecurity([2333, 2083], [70, 70])"
149
149
  ]
150
150
  },
151
151
  {
@@ -37,6 +37,7 @@ def saveConfig(myplan, file, mylog):
37
37
  "Status": ["unknown", "single", "married"][myplan.N_i],
38
38
  "Names": myplan.inames,
39
39
  "Birth year": myplan.yobs.tolist(),
40
+ "Birth month": myplan.mobs.tolist(),
40
41
  "Life expectancy": myplan.expectancy.tolist(),
41
42
  "Start date": myplan.startDate,
42
43
  }
@@ -55,10 +56,10 @@ def saveConfig(myplan, file, mylog):
55
56
 
56
57
  # Fixed Income.
57
58
  diconf["Fixed Income"] = {
58
- "Pension amounts": (myplan.pensionAmounts / 1000).tolist(),
59
+ "Pension monthly amounts": (myplan.pensionAmounts).tolist(),
59
60
  "Pension ages": myplan.pensionAges.tolist(),
60
61
  "Pension indexed": myplan.pensionIsIndexed,
61
- "Social security amounts": (myplan.ssecAmounts / 1000).tolist(),
62
+ "Social security PIA amounts": (myplan.ssecAmounts).tolist(),
62
63
  "Social security ages": myplan.ssecAges.tolist(),
63
64
  }
64
65
 
@@ -181,11 +182,13 @@ def readConfig(file, *, verbose=True, logstreams=None, readContributions=True):
181
182
  inames = diconf["Basic Info"]["Names"]
182
183
  # status = diconf['Basic Info']['Status']
183
184
  yobs = diconf["Basic Info"]["Birth year"]
184
- expectancy = diconf["Basic Info"]["Life expectancy"]
185
185
  icount = len(yobs)
186
+ # Default to January if no month entry found.
187
+ mobs = diconf["Basic Info"].get("Birth month", [1]*icount)
188
+ expectancy = diconf["Basic Info"]["Life expectancy"]
186
189
  s = ["", "s"][icount - 1]
187
190
  mylog.vprint(f"Plan for {icount} individual{s}: {inames}.")
188
- p = plan.Plan(inames, yobs, expectancy, name, verbose=True, logstreams=logstreams)
191
+ p = plan.Plan(inames, yobs, mobs, expectancy, name, verbose=True, logstreams=logstreams)
189
192
  p._description = diconf.get("Description", "")
190
193
 
191
194
  # Assets.
@@ -217,11 +220,11 @@ def readConfig(file, *, verbose=True, logstreams=None, readContributions=True):
217
220
  mylog.vprint(f"Ignoring to read contributions file {timeListsFileName}.")
218
221
 
219
222
  # Fixed Income.
220
- ssecAmounts = np.array(diconf["Fixed Income"]["Social security amounts"], dtype=np.float32)
221
- ssecAges = np.array(diconf["Fixed Income"]["Social security ages"], dtype=np.int32)
223
+ ssecAmounts = np.array(diconf["Fixed Income"].get("Social security PIA amounts", [0]*icount), dtype=np.int32)
224
+ ssecAges = np.array(diconf["Fixed Income"]["Social security ages"])
222
225
  p.setSocialSecurity(ssecAmounts, ssecAges)
223
- pensionAmounts = np.array(diconf["Fixed Income"]["Pension amounts"], dtype=np.float32)
224
- pensionAges = np.array(diconf["Fixed Income"]["Pension ages"], dtype=np.int32)
226
+ pensionAmounts = np.array(diconf["Fixed Income"].get("Pension monthly amounts", [0]*icount), dtype=np.float32)
227
+ pensionAges = np.array(diconf["Fixed Income"]["Pension ages"])
225
228
  pensionIsIndexed = diconf["Fixed Income"]["Pension indexed"]
226
229
  p.setPension(pensionAmounts, pensionAges, pensionIsIndexed)
227
230