owlplanner 2025.2.4__tar.gz → 2025.2.5__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 (91) hide show
  1. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/PKG-INFO +25 -40
  2. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/README.md +24 -39
  3. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/pyproject.toml +1 -1
  4. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/src/owlplanner/plan.py +101 -109
  5. owlplanner-2025.2.5/src/owlplanner/version.py +1 -0
  6. owlplanner-2025.2.5/ui/Case_Summary.py +46 -0
  7. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/ui/Documentation.py +16 -13
  8. owlplanner-2025.2.5/ui/Graphs.py +32 -0
  9. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/ui/Quick_Start.py +1 -1
  10. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/ui/Rates_Selection.py +4 -4
  11. owlplanner-2025.2.4/ui/Case_Worksheets.py → owlplanner-2025.2.5/ui/Worksheets.py +4 -1
  12. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/ui/main.py +2 -2
  13. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/ui/owlbridge.py +18 -16
  14. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/ui/requirements.txt +1 -1
  15. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/ui/sskeys.py +4 -0
  16. owlplanner-2025.2.4/src/owlplanner/version.py +0 -1
  17. owlplanner-2025.2.4/ui/Case_Results.py +0 -51
  18. owlplanner-2025.2.4/ui/Case_Summary.py +0 -21
  19. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/.devcontainer/devcontainer.json +0 -0
  20. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/.flake8 +0 -0
  21. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/.github/workflows/github-actions-runtests.yml +0 -0
  22. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/.gitignore +0 -0
  23. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/INSTALL.md +0 -0
  24. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/LICENSE +0 -0
  25. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/docs/images/AD-taxDef.png +0 -0
  26. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/docs/images/AD-taxFree.png +0 -0
  27. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/docs/images/AD-taxable.png +0 -0
  28. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/docs/images/Hist_Bequest.png +0 -0
  29. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/docs/images/Hist_Spending.png +0 -0
  30. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/docs/images/MC-tutorial2a.png +0 -0
  31. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/docs/images/MC-tutorial2b.png +0 -0
  32. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/docs/images/OwlUI.png +0 -0
  33. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/docs/images/allocations.png +0 -0
  34. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/docs/images/owl.png +0 -0
  35. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/docs/images/profile.png +0 -0
  36. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/docs/images/ratesCorrelations.png +0 -0
  37. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/docs/images/ratesPlot.png +0 -0
  38. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/docs/images/savingsPlot.png +0 -0
  39. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/docs/images/sourcesPlot.png +0 -0
  40. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/docs/images/spendingPlot.png +0 -0
  41. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/docs/images/taxIncomePlot.png +0 -0
  42. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/docs/images/taxesPlot.png +0 -0
  43. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/docs/owl.pdf +0 -0
  44. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/docs/owl.tex +0 -0
  45. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/examples/case_jack+jill.toml +0 -0
  46. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/examples/case_joe.toml +0 -0
  47. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/examples/case_john+sally.toml +0 -0
  48. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/examples/case_kim+sam-bequest.toml +0 -0
  49. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/examples/case_kim+sam-spending.toml +0 -0
  50. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/examples/jack+jill.xlsx +0 -0
  51. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/examples/joe.xlsx +0 -0
  52. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/examples/john+sally.xlsx +0 -0
  53. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/examples/template.xlsx +0 -0
  54. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/notebooks/john+sally.ipynb +0 -0
  55. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/notebooks/kim+sam.ipynb +0 -0
  56. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/notebooks/template.ipynb +0 -0
  57. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/notebooks/tutorial_1.ipynb +0 -0
  58. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/notebooks/tutorial_2.ipynb +0 -0
  59. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/notebooks/tutorial_3.ipynb +0 -0
  60. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/owlplanner.cmd +0 -0
  61. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/requirements.txt +0 -0
  62. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/src/owlplanner/__init__.py +0 -0
  63. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/src/owlplanner/abcapi.py +0 -0
  64. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/src/owlplanner/config.py +0 -0
  65. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/src/owlplanner/data/__init__.py +0 -0
  66. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/src/owlplanner/data/rates.csv +0 -0
  67. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/src/owlplanner/logging.py +0 -0
  68. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/src/owlplanner/progress.py +0 -0
  69. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/src/owlplanner/rates.py +0 -0
  70. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/src/owlplanner/tax2025.py +0 -0
  71. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/src/owlplanner/timelists.py +0 -0
  72. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/src/owlplanner/utils.py +0 -0
  73. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/tests/test_logger.py +0 -0
  74. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/tests/test_regressions.py +0 -0
  75. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/tests/test_repro.py +0 -0
  76. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/tests/test_toml_cases.py +0 -0
  77. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/tests/test_units.py +0 -0
  78. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/ui/About_Owl.py +0 -0
  79. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/ui/Asset_Allocation.py +0 -0
  80. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/ui/Assets.py +0 -0
  81. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/ui/Basic_Info.py +0 -0
  82. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/ui/Fixed_Income.py +0 -0
  83. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/ui/Historical_Range.py +0 -0
  84. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/ui/Logs.py +0 -0
  85. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/ui/Monte_Carlo.py +0 -0
  86. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/ui/Optimization_Parameters.py +0 -0
  87. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/ui/README.md +0 -0
  88. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/ui/Settings.py +0 -0
  89. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/ui/Wages_And_Contributions.py +0 -0
  90. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/ui/plots.py +0 -0
  91. {owlplanner-2025.2.4 → owlplanner-2025.2.5}/ui/progress.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: owlplanner
3
- Version: 2025.2.4
3
+ Version: 2025.2.5
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
@@ -731,7 +731,8 @@ This is exactly where this tool fits it. Given your savings capabilities and spe
731
731
  it can generate different future realizations of
732
732
  your strategy under different market assumptions, helping to better understand your financial situation.
733
733
 
734
- Disclaimers: I am not a financial planner. You make your own decisions. This program comes with no guarantee. Use at your own risk.
734
+ Disclaimers: I am not a financial planner. You make your own decisions.
735
+ This program comes with no guarantee. Use at your own risk.
735
736
 
736
737
  More disclaimers: While some output of the code has been verified with other approaches,
737
738
  this code is still under development and I cannot guarantee the accuracy of the results.
@@ -975,47 +976,31 @@ The output of the last command reports that if future rates are exactly like tho
975
976
  starting from 1969 and the following years, Jack and Jill could afford an annual spending of
976
977
  \\$97k starting this year
977
978
  (with a basis of \\$88.8k - the basis multiplies the profile which can vary over the course of the plan).
978
- The summary also contains many more details:
979
+ The summary also contains some details:
979
980
  ```
980
981
  SUMMARY ================================================================
982
+ Net yearly spending basis in 2025$: $91,812
983
+ Net yearly spending for year 2025: $100,448
984
+ Net spending remaining in year 2025: $100,448
985
+ Total net spending in 2025$: $2,809,453 ($7,757,092 nominal)
986
+ Total Roth conversions in 2025$: $320,639 ($456,454 nominal)
987
+ Total income tax paid on ordinary income in 2025$: $247,788 ($469,522 nominal)
988
+ Total tax paid on gains and dividends in 2025$: $3,313 ($3,768 nominal)
989
+ Total Medicare premiums paid in 2025$: $117,660 ($343,388 nominal)
990
+ Spousal wealth transfer from Jack to Jill in year 2051 (nominal): taxable: $0 tax-def: $57,224 tax-free: $2,102,173
991
+ Sum of spousal bequests to Jill in year 2051 in 2025$: $499,341 ($2,159,397 nominal)
992
+ Post-tax non-spousal bequests from Jack in year 2051 (nominal): taxable: $0 tax-def: $0 tax-free: $0
993
+ Sum of post-tax non-spousal bequests from Jack in year 2051 in 2025$: $0 ($0 nominal)
994
+ Post-tax account values at the end of final plan year 2057 (nominal): taxable: $0 tax-def: $0 tax-free: $2,488,808
995
+ Total estate value at the end of final plan year 2057 in 2025$: $500,000 ($2,488,808 nominal)
996
+ Plan starting date: 01-01
997
+ Cumulative inflation factor from start date to end of plan: 4.98
998
+ Jack's 27-year life horizon: 2025 -> 2051
999
+ Jill's 33-year life horizon: 2025 -> 2057
981
1000
  Plan name: jack & jill - tutorial
982
- Jack's life horizon: 2024 -> 2051
983
- Jill's life horizon: 2024 -> 2057
984
- Contributions file: examples/jack+jill.xlsx
985
- Initial balances [taxable, tax-deferred, tax-free]:
986
- Jack's accounts: ['$90,500', '$600,500', '$70,000']
987
- Jill's accounts: ['$60,200', '$150,000', '$40,000']
988
- Return rates: historical
989
- Rates used: from 1969 to 2002
990
- This year's starting date: 01-01
991
- Optimized for: maxSpending
992
- Solver options: {'maxRothConversion': 100, 'bequest': 500, 'noRothConversions': 'Jill'}
993
- Number of decision variables: 1026
994
- Number of constraints: 894
995
- Spending profile: smile
996
- Surviving spouse spending needs: 60%
997
- Net yearly spending in year 2024: $97,098
998
- Net spending remaining in year 2024: $97,098
999
- Net yearly spending profile basis in 2024$: $88,763
1000
- Assumed heirs tax rate: 30%
1001
- Spousal surplus deposit fraction: 0.5
1002
- Spousal beneficiary fractions to Jill: [1, 1, 1]
1003
- Spousal wealth transfer from Jack to Jill in year 2051 (nominal):
1004
- taxable: $0 tax-def: $63,134 tax-free: $2,583,303
1005
- Sum of spousal bequests to Jill in year 2051 in 2024$: $592,103 ($2,646,437 nominal)
1006
- Post-tax non-spousal bequests from Jack in year 2051 (nominal):
1007
- taxable: $0 tax-def: $0 tax-free: $0
1008
- Sum of post-tax non-spousal bequests from Jack in year 2051 in 2024$: $0 ($0 nominal)
1009
- Total net spending in 2024$: $2,804,910 ($7,916,623 nominal)
1010
- Total Roth conversions in 2024$: $311,760 ($443,005 nominal)
1011
- Total ordinary income tax paid in 2024$: $236,710 ($457,922 nominal)
1012
- Total dividend tax paid in 2024$: $3,437 ($3,902 nominal)
1013
- Total Medicare premiums paid in 2024$: $117,817 ($346,404 nominal)
1014
- Post-tax account values at the end of final plan year 2057: (nominal)
1015
- taxable: $0 tax-def: $0 tax-free: $2,553,871
1016
- Total estate value at the end of final plan year 2057 in 2024$: $500,000 ($2,553,871 nominal)
1017
- Inflation factor from this year's start date to the end of plan final year: 5.11
1018
- Case executed on: 2024-12-09 at 22:11:57
1001
+ Number of decision variables: 996
1002
+ Number of constraints: 867
1003
+ Case executed on: 2025-02-04 at 22:55:03
1019
1004
  ------------------------------------------------------------------------
1020
1005
  ```
1021
1006
  And an Excel workbook can be saved with all the detailed amounts over the years by using the following command:
@@ -28,7 +28,8 @@ This is exactly where this tool fits it. Given your savings capabilities and spe
28
28
  it can generate different future realizations of
29
29
  your strategy under different market assumptions, helping to better understand your financial situation.
30
30
 
31
- Disclaimers: I am not a financial planner. You make your own decisions. This program comes with no guarantee. Use at your own risk.
31
+ Disclaimers: I am not a financial planner. You make your own decisions.
32
+ This program comes with no guarantee. Use at your own risk.
32
33
 
33
34
  More disclaimers: While some output of the code has been verified with other approaches,
34
35
  this code is still under development and I cannot guarantee the accuracy of the results.
@@ -272,47 +273,31 @@ The output of the last command reports that if future rates are exactly like tho
272
273
  starting from 1969 and the following years, Jack and Jill could afford an annual spending of
273
274
  \\$97k starting this year
274
275
  (with a basis of \\$88.8k - the basis multiplies the profile which can vary over the course of the plan).
275
- The summary also contains many more details:
276
+ The summary also contains some details:
276
277
  ```
277
278
  SUMMARY ================================================================
279
+ Net yearly spending basis in 2025$: $91,812
280
+ Net yearly spending for year 2025: $100,448
281
+ Net spending remaining in year 2025: $100,448
282
+ Total net spending in 2025$: $2,809,453 ($7,757,092 nominal)
283
+ Total Roth conversions in 2025$: $320,639 ($456,454 nominal)
284
+ Total income tax paid on ordinary income in 2025$: $247,788 ($469,522 nominal)
285
+ Total tax paid on gains and dividends in 2025$: $3,313 ($3,768 nominal)
286
+ Total Medicare premiums paid in 2025$: $117,660 ($343,388 nominal)
287
+ Spousal wealth transfer from Jack to Jill in year 2051 (nominal): taxable: $0 tax-def: $57,224 tax-free: $2,102,173
288
+ Sum of spousal bequests to Jill in year 2051 in 2025$: $499,341 ($2,159,397 nominal)
289
+ Post-tax non-spousal bequests from Jack in year 2051 (nominal): taxable: $0 tax-def: $0 tax-free: $0
290
+ Sum of post-tax non-spousal bequests from Jack in year 2051 in 2025$: $0 ($0 nominal)
291
+ Post-tax account values at the end of final plan year 2057 (nominal): taxable: $0 tax-def: $0 tax-free: $2,488,808
292
+ Total estate value at the end of final plan year 2057 in 2025$: $500,000 ($2,488,808 nominal)
293
+ Plan starting date: 01-01
294
+ Cumulative inflation factor from start date to end of plan: 4.98
295
+ Jack's 27-year life horizon: 2025 -> 2051
296
+ Jill's 33-year life horizon: 2025 -> 2057
278
297
  Plan name: jack & jill - tutorial
279
- Jack's life horizon: 2024 -> 2051
280
- Jill's life horizon: 2024 -> 2057
281
- Contributions file: examples/jack+jill.xlsx
282
- Initial balances [taxable, tax-deferred, tax-free]:
283
- Jack's accounts: ['$90,500', '$600,500', '$70,000']
284
- Jill's accounts: ['$60,200', '$150,000', '$40,000']
285
- Return rates: historical
286
- Rates used: from 1969 to 2002
287
- This year's starting date: 01-01
288
- Optimized for: maxSpending
289
- Solver options: {'maxRothConversion': 100, 'bequest': 500, 'noRothConversions': 'Jill'}
290
- Number of decision variables: 1026
291
- Number of constraints: 894
292
- Spending profile: smile
293
- Surviving spouse spending needs: 60%
294
- Net yearly spending in year 2024: $97,098
295
- Net spending remaining in year 2024: $97,098
296
- Net yearly spending profile basis in 2024$: $88,763
297
- Assumed heirs tax rate: 30%
298
- Spousal surplus deposit fraction: 0.5
299
- Spousal beneficiary fractions to Jill: [1, 1, 1]
300
- Spousal wealth transfer from Jack to Jill in year 2051 (nominal):
301
- taxable: $0 tax-def: $63,134 tax-free: $2,583,303
302
- Sum of spousal bequests to Jill in year 2051 in 2024$: $592,103 ($2,646,437 nominal)
303
- Post-tax non-spousal bequests from Jack in year 2051 (nominal):
304
- taxable: $0 tax-def: $0 tax-free: $0
305
- Sum of post-tax non-spousal bequests from Jack in year 2051 in 2024$: $0 ($0 nominal)
306
- Total net spending in 2024$: $2,804,910 ($7,916,623 nominal)
307
- Total Roth conversions in 2024$: $311,760 ($443,005 nominal)
308
- Total ordinary income tax paid in 2024$: $236,710 ($457,922 nominal)
309
- Total dividend tax paid in 2024$: $3,437 ($3,902 nominal)
310
- Total Medicare premiums paid in 2024$: $117,817 ($346,404 nominal)
311
- Post-tax account values at the end of final plan year 2057: (nominal)
312
- taxable: $0 tax-def: $0 tax-free: $2,553,871
313
- Total estate value at the end of final plan year 2057 in 2024$: $500,000 ($2,553,871 nominal)
314
- Inflation factor from this year's start date to the end of plan final year: 5.11
315
- Case executed on: 2024-12-09 at 22:11:57
298
+ Number of decision variables: 996
299
+ Number of constraints: 867
300
+ Case executed on: 2025-02-04 at 22:55:03
316
301
  ------------------------------------------------------------------------
317
302
  ```
318
303
  And an Excel workbook can be saved with all the detailed amounts over the years by using the following command:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "owlplanner"
7
- version = "2025.02.04"
7
+ version = "2025.02.05"
8
8
  authors = [
9
9
  { name="Martin-D. Lacasse", email="martin.d.lacasse@gmail.com" },
10
10
  ]
@@ -1413,11 +1413,11 @@ class Plan(object):
1413
1413
 
1414
1414
  progcall.finish()
1415
1415
  self.mylog.resetVerbose()
1416
- fig, summary = self._showResults(objective, df, N, figure)
1417
- self.mylog.print(summary.getvalue())
1416
+ fig, description = self._showResults(objective, df, N, figure)
1417
+ self.mylog.print(description.getvalue())
1418
1418
 
1419
1419
  if figure:
1420
- return fig, summary.getvalue()
1420
+ return fig, description.getvalue()
1421
1421
 
1422
1422
  return N, df
1423
1423
 
@@ -1469,11 +1469,11 @@ class Plan(object):
1469
1469
 
1470
1470
  progcall.finish()
1471
1471
  self.mylog.resetVerbose()
1472
- fig, summary = self._showResults(objective, df, N, figure)
1473
- self.mylog.print(summary.getvalue())
1472
+ fig, description = self._showResults(objective, df, N, figure)
1473
+ self.mylog.print(description.getvalue())
1474
1474
 
1475
1475
  if figure:
1476
- return fig, summary.getvalue()
1476
+ return fig, description.getvalue()
1477
1477
 
1478
1478
  return N, df
1479
1479
 
@@ -1483,9 +1483,9 @@ class Plan(object):
1483
1483
  """
1484
1484
  import seaborn as sbn
1485
1485
 
1486
- summary = io.StringIO()
1486
+ description = io.StringIO()
1487
1487
 
1488
- print('Success rate: %s on %d samples.' % (u.pc(len(df) / N), N), file=summary)
1488
+ print('Success rate: %s on %d samples.' % (u.pc(len(df) / N), N), file=description)
1489
1489
  title = '$N$ = %d, $P$ = %s' % (N, u.pc(len(df) / N))
1490
1490
  means = df.mean(axis=0, numeric_only=True)
1491
1491
  medians = df.median(axis=0, numeric_only=True)
@@ -1498,7 +1498,7 @@ class Plan(object):
1498
1498
  # or if solution led to empty accounts at the end of first spouse's life.
1499
1499
  if np.all(self.phi_j == 1) or medians.iloc[0] < 1:
1500
1500
  if medians.iloc[0] < 1:
1501
- print('Optimized solutions all have null partial bequest in year %d.' % my[0], file=summary)
1501
+ print('Optimized solutions all have null partial bequest in year %d.' % my[0], file=description)
1502
1502
  df.drop('partial', axis=1, inplace=True)
1503
1503
  means = df.mean(axis=0, numeric_only=True)
1504
1504
  medians = df.median(axis=0, numeric_only=True)
@@ -1550,16 +1550,16 @@ class Plan(object):
1550
1550
  # plt.show()
1551
1551
 
1552
1552
  for q in range(len(means)):
1553
- print('%12s: Median (%d $): %s' % (leads[q], self.year_n[0], u.d(medians.iloc[q])), file=summary)
1554
- print('%12s: Mean (%d $): %s' % (leads[q], self.year_n[0], u.d(means.iloc[q])), file=summary)
1553
+ print('%12s: Median (%d $): %s' % (leads[q], self.year_n[0], u.d(medians.iloc[q])), file=description)
1554
+ print('%12s: Mean (%d $): %s' % (leads[q], self.year_n[0], u.d(means.iloc[q])), file=description)
1555
1555
  print(
1556
1556
  '%12s: Range: %s - %s'
1557
1557
  % (leads[q], u.d(1000 * df.iloc[:, q].min()), u.d(1000 * df.iloc[:, q].max())),
1558
- file=summary)
1558
+ file=description)
1559
1559
  nzeros = len(df.iloc[:, q][df.iloc[:, q] < 0.001])
1560
- print('%12s: N zero solns: %d' % (leads[q], nzeros), file=summary)
1560
+ print('%12s: N zero solns: %d' % (leads[q], nzeros), file=description)
1561
1561
 
1562
- return fig, summary
1562
+ return fig, description
1563
1563
 
1564
1564
  def resolve(self):
1565
1565
  """
@@ -2038,141 +2038,133 @@ class Plan(object):
2038
2038
  @_checkCaseStatus
2039
2039
  def summary(self):
2040
2040
  """
2041
- Print summary of values.
2041
+ Print summary in logs.
2042
2042
  """
2043
2043
  self.mylog.print('SUMMARY ================================================================')
2044
- lines = self.summaryList()
2045
- for line in lines:
2046
- self.mylog.print(line)
2044
+ dic = self.summaryDic()
2045
+ for key, value in dic.items():
2046
+ self.mylog.print(f"{key}: {value}")
2047
2047
  self.mylog.print('------------------------------------------------------------------------')
2048
2048
 
2049
2049
  return None
2050
2050
 
2051
+ def summaryList(self):
2052
+ """
2053
+ Return summary as a list.
2054
+ """
2055
+ mylist = []
2056
+ dic = self.summaryDic()
2057
+ for key, value in dic.items():
2058
+ mylist.append(f"{key}: {value}")
2059
+
2060
+ return mylist
2061
+
2051
2062
  def summaryString(self):
2052
2063
  """
2053
- Print summary of values in a string.
2064
+ Return summary as a string.
2054
2065
  """
2055
- string = ''
2056
- lines = self.summaryList()
2057
- for line in lines:
2058
- string += line + '\n'
2066
+ string = 'Synopsis\n'
2067
+ dic = self.summaryDic()
2068
+ for key, value in dic.items():
2069
+ string += f"{key:>70}: {value}\n"
2070
+ # string += "%60s: %s\n" % (key, value)
2059
2071
 
2060
2072
  return string
2061
2073
 
2062
- def summaryList(self):
2074
+ def summaryDic(self):
2063
2075
  """
2064
- Return string with summary of values.
2076
+ Return dictionary containing summary of values.
2065
2077
  """
2066
2078
  now = self.year_n[0]
2067
- lines = []
2068
- lines.append('Plan name: %s' % self._name)
2069
- for i in range(self.N_i):
2070
- lines.append("%12s's %02d-year life horizon: %d -> %d"
2071
- % (self.inames[i], self.horizons[i], now, now + self.horizons[i] - 1))
2072
- lines.append('Contributions file: %s' % self.timeListsFileName)
2073
- lines.append('Initial balances [taxable, tax-deferred, tax-free]:')
2074
- for i in range(self.N_i):
2075
- lines.append("%12s's accounts: %s" % (self.inames[i], [u.d(self.beta_ij[i][j]) for j in range(self.N_j)]))
2076
-
2077
- lines.append('Return rates: %s' % self.rateMethod)
2078
- if self.rateMethod in ['historical', 'historical average', 'histochastic']:
2079
- lines.append('Rates used: from %d to %d' % (self.rateFrm, self.rateTo))
2080
- elif self.rateMethod == 'stochastic':
2081
- lines.append(
2082
- 'Mean rates used (%%): %s' % (['{:.1f}'.format(100 * self.rateValues[k]) for k in range(self.N_k)])
2083
- )
2084
- lines.append(
2085
- 'Standard deviation used (%%): %s'
2086
- % (['{:.1f}'.format(100 * self.rateStdev[k]) for k in range(self.N_k)])
2087
- )
2088
- lines.append('Correlation matrix used:')
2089
- lines.append('\t\t' + str(self.rateCorr).replace('\n', '\n\t\t'))
2090
- else:
2091
- lines.append('Rates used (%%): %s' % (['{:.1f}'.format(100 * self.rateValues[k]) for k in range(self.N_k)]))
2092
- lines.append("This year's starting date: %s" % self.startDate)
2093
- lines.append('Optimized for: %s' % self.objective)
2094
- lines.append('Solver options: %s' % self.solverOptions)
2095
- lines.append('Number of decision variables: %d' % self.A.nvars)
2096
- lines.append('Number of constraints: %d' % self.A.ncons)
2097
- lines.append('Spending profile: %s' % self.spendingProfile)
2098
- if self.spendingProfile == 'smile':
2099
- lines.append('\twith increase: %d%%, dip: %d%%, delay: %dy'
2100
- % (self.smileIncrease, self.smileDip, self.smileDelay))
2101
- if self.N_i == 2:
2102
- lines.append('Surviving spouse spending needs: %s' % u.pc(self.chi, f=0))
2103
-
2104
- lines.append('Net yearly spending in year %d: %s' % (now, u.d(self.g_n[0] / self.yearFracLeft)))
2105
- lines.append('Net spending remaining in year %d: %s' % (now, u.d(self.g_n[0])))
2106
- lines.append('Net yearly spending profile basis in %d$: %s' % (now, u.d(self.g_n[0] / self.xi_n[0])))
2107
-
2108
- lines.append('Assumed heirs marginal tax rate: %s' % u.pc(self.nu, f=0))
2109
-
2110
- if self.N_i == 2 and self.n_d < self.N_n:
2111
- lines.append("Spousal surplus deposit fraction in %s's taxable account: %s"
2112
- % (self.inames[1], self.eta))
2113
- lines.append('Spousal beneficiary fractions to %s: %s' % (self.inames[self.i_s], self.phi_j.tolist()))
2114
- p_j = self.partialEstate_j * (1 - self.phi_j)
2115
- p_j[1] *= 1 - self.nu
2116
- nx = self.n_d - 1
2117
- totOthers = np.sum(p_j)
2118
- totOthersNow = totOthers / self.gamma_n[nx + 1]
2119
- q_j = self.partialEstate_j * self.phi_j
2120
- totSpousal = np.sum(q_j)
2121
- totSpousalNow = totSpousal / self.gamma_n[nx + 1]
2122
- lines.append('Spousal wealth transfer from %s to %s in year %d (nominal):'
2123
- % (self.inames[self.i_d], self.inames[self.i_s], self.year_n[nx]))
2124
- lines.append(' taxable: %s tax-def: %s tax-free: %s' % (u.d(q_j[0]), u.d(q_j[1]), u.d(q_j[2])))
2125
- lines.append('Sum of spousal bequests to %s in year %d in %d$: %s (%s nominal)'
2126
- % (self.inames[self.i_s], self.year_n[nx], now, u.d(totSpousalNow), u.d(totSpousal)))
2127
- lines.append(
2128
- 'Post-tax non-spousal bequests from %s in year %d (nominal):' % (self.inames[self.i_d], self.year_n[nx])
2129
- )
2130
- lines.append(' taxable: %s tax-def: %s tax-free: %s' % (u.d(p_j[0]), u.d(p_j[1]), u.d(p_j[2])))
2131
- lines.append(
2132
- 'Sum of post-tax non-spousal bequests from %s in year %d in %d$: %s (%s nominal)'
2133
- % (self.inames[self.i_d], self.year_n[nx], now, u.d(totOthersNow), u.d(totOthers))
2134
- )
2079
+ dic = {}
2080
+ # Results
2081
+ dic[f"Net yearly spending basis in {now}$"] = (u.d(self.g_n[0] / self.xi_n[0]))
2082
+ dic[f"Net yearly spending for year {now}"] = (u.d(self.g_n[0] / self.yearFracLeft))
2083
+ dic[f"Net spending remaining in year {now}"] = u.d(self.g_n[0])
2135
2084
 
2136
2085
  totIncome = np.sum(self.g_n, axis=0)
2137
2086
  totIncomeNow = np.sum(self.g_n / self.gamma_n[:-1], axis=0)
2138
- lines.append('Total net spending in %d$: %s (%s nominal)' % (now, u.d(totIncomeNow), u.d(totIncome)))
2087
+ dic[f"Total net spending in {now}$"] = (
2088
+ "%s (%s nominal)" % (u.d(totIncomeNow), u.d(totIncome))
2089
+ )
2139
2090
 
2140
2091
  totRoth = np.sum(self.x_in, axis=(0, 1))
2141
2092
  totRothNow = np.sum(np.sum(self.x_in, axis=0) / self.gamma_n[:-1], axis=0)
2142
- lines.append('Total Roth conversions in %d$: %s (%s nominal)' % (now, u.d(totRothNow), u.d(totRoth)))
2093
+ dic[f"Total Roth conversions in {now}$"] = (
2094
+ "%s (%s nominal)" % (u.d(totRothNow), u.d(totRoth))
2095
+ )
2143
2096
 
2144
2097
  taxPaid = np.sum(self.T_n, axis=0)
2145
2098
  taxPaidNow = np.sum(self.T_n / self.gamma_n[:-1], axis=0)
2146
- lines.append('Total income tax paid on ordinary income in %d$: %s (%s nominal)'
2147
- % (now, u.d(taxPaidNow), u.d(taxPaid)))
2099
+ dic[f"Total income tax paid on ordinary income in {now}$"] = (
2100
+ "%s (%s nominal)" % (u.d(taxPaidNow), u.d(taxPaid))
2101
+ )
2148
2102
 
2149
2103
  taxPaid = np.sum(self.U_n, axis=0)
2150
2104
  taxPaidNow = np.sum(self.U_n / self.gamma_n[:-1], axis=0)
2151
- lines.append('Total tax paid on gains and dividends in %d$: %s (%s nominal)'
2152
- % (now, u.d(taxPaidNow), u.d(taxPaid)))
2105
+ dic[f"Total tax paid on gains and dividends in {now}$"] = (
2106
+ "%s (%s nominal)" % (u.d(taxPaidNow), u.d(taxPaid))
2107
+ )
2153
2108
 
2154
2109
  taxPaid = np.sum(self.M_n, axis=0)
2155
2110
  taxPaidNow = np.sum(self.M_n / self.gamma_n[:-1], axis=0)
2156
- lines.append('Total Medicare premiums paid in %d$: %s (%s nominal)' % (now, u.d(taxPaidNow), u.d(taxPaid)))
2111
+ dic[f"Total Medicare premiums paid in {now}$"] = (
2112
+ "%s (%s nominal)" % (u.d(taxPaidNow), u.d(taxPaid))
2113
+ )
2114
+
2115
+ if self.N_i == 2 and self.n_d < self.N_n:
2116
+ p_j = self.partialEstate_j * (1 - self.phi_j)
2117
+ p_j[1] *= 1 - self.nu
2118
+ nx = self.n_d - 1
2119
+ totOthers = np.sum(p_j)
2120
+ totOthersNow = totOthers / self.gamma_n[nx + 1]
2121
+ q_j = self.partialEstate_j * self.phi_j
2122
+ totSpousal = np.sum(q_j)
2123
+ totSpousalNow = totSpousal / self.gamma_n[nx + 1]
2124
+ dic["Spousal wealth transfer from %s to %s in year %d (nominal)" %
2125
+ (self.inames[self.i_d], self.inames[self.i_s], self.year_n[nx])] = (
2126
+ "taxable: %s tax-def: %s tax-free: %s" % (u.d(q_j[0]), u.d(q_j[1]), u.d(q_j[2]))
2127
+ )
2128
+
2129
+ dic["Sum of spousal bequests to %s in year %d in %d$" %
2130
+ (self.inames[self.i_s], self.year_n[nx], now)] = (
2131
+ "%s (%s nominal)" % (u.d(totSpousalNow), u.d(totSpousal))
2132
+ )
2133
+ dic["Post-tax non-spousal bequests from %s in year %d (nominal)" %
2134
+ (self.inames[self.i_d], self.year_n[nx])] = (
2135
+ "taxable: %s tax-def: %s tax-free: %s" % (u.d(p_j[0]), u.d(p_j[1]), u.d(p_j[2]))
2136
+ )
2137
+ dic["Sum of post-tax non-spousal bequests from %s in year %d in %d$" %
2138
+ (self.inames[self.i_d], self.year_n[nx], now)] = (
2139
+ "%s (%s nominal)" % (u.d(totOthersNow), u.d(totOthers))
2140
+ )
2157
2141
 
2158
2142
  estate = np.sum(self.b_ijn[:, :, self.N_n], axis=0)
2159
2143
  estate[1] *= 1 - self.nu
2160
- lines.append('Post-tax account values at the end of final plan year %d: (nominal)' % self.year_n[-1])
2161
- lines.append(' taxable: %s tax-def: %s tax-free: %s' % (u.d(estate[0]), u.d(estate[1]), u.d(estate[2])))
2144
+ dic["Post-tax account values at the end of final plan year %d (nominal)" % self.year_n[-1]] = (
2145
+ "taxable: %s tax-def: %s tax-free: %s" % (u.d(estate[0]), u.d(estate[1]), u.d(estate[2]))
2146
+ )
2162
2147
 
2163
2148
  totEstate = np.sum(estate)
2164
2149
  totEstateNow = totEstate / self.gamma_n[-1]
2165
- lines.append(
2166
- 'Total estate value at the end of final plan year %d in %d$: %s (%s nominal)'
2167
- % (self.year_n[-1], now, u.d(totEstateNow), u.d(totEstate))
2150
+ dic["Total estate value at the end of final plan year %d in %d$" % (self.year_n[-1], now)] = (
2151
+ "%s (%s nominal)" % (u.d(totEstateNow), u.d(totEstate))
2168
2152
  )
2169
- lines.append(
2170
- "Inflation factor from this year's start date to the end of plan final year: %.2f" % self.gamma_n[-1]
2153
+ dic["Plan starting date"] = str(self.startDate)
2154
+ dic["Cumulative inflation factor from start date to end of plan"] = (
2155
+ "%.2f" % (self.gamma_n[-1])
2171
2156
  )
2157
+ for i in range(self.N_i):
2158
+ dic["%12s's %02d-year life horizon" % (self.inames[i], self.horizons[i])] = (
2159
+ "%d -> %d" % (now, now + self.horizons[i] - 1)
2160
+ )
2172
2161
 
2173
- lines.append('Case executed on: %s' % self._timestamp)
2162
+ dic["Plan name"] = self._name
2163
+ dic["Number of decision variables"] = str(self.A.nvars)
2164
+ dic["Number of constraints"] = str(self.A.ncons)
2165
+ dic["Case executed on"] = str(self._timestamp)
2174
2166
 
2175
- return lines
2167
+ return dic
2176
2168
 
2177
2169
  def showRatesCorrelations(self, tag='', shareRange=False, figure=False):
2178
2170
  """
@@ -0,0 +1 @@
1
+ __version__ = "2025.02.05"
@@ -0,0 +1,46 @@
1
+ import streamlit as st
2
+
3
+ import sskeys as kz
4
+ import owlbridge as owb
5
+
6
+ ret = kz.titleBar('summary')
7
+ kz.caseHeader("Case Summary")
8
+
9
+ if ret is None or kz.caseHasNoPlan():
10
+ st.info('Case(s) must be first created before running this page.')
11
+ else:
12
+ if kz.caseIsRunReady():
13
+ owb.runPlan()
14
+
15
+ if kz.caseHasNotCompletedRun():
16
+ st.info("Case status is currently '%s'." % kz.getKey('caseStatus'))
17
+ else:
18
+ lines = kz.getKey('summary')
19
+ if lines != '':
20
+ st.write('#### Synopsis')
21
+ st.code(lines, language=None)
22
+ st.download_button('Download synopsis',
23
+ data=lines,
24
+ file_name='Synopsis_'+kz.getKey('name')+'.txt',
25
+ mime='text/plain;charset=UTF-8')
26
+
27
+ lines = kz.getKey('casetoml')
28
+ if lines != '':
29
+ st.divider()
30
+ st.write("#### Case parameter file")
31
+ st.code(lines, language='toml')
32
+
33
+ st.download_button('Download case parameter file',
34
+ data=lines,
35
+ file_name='case_'+kz.getKey('name')+'.toml',
36
+ mime='application/toml')
37
+
38
+ st.divider()
39
+ st.write("#### Wages and contributions file")
40
+ download2 = st.download_button(
41
+ label="Download wages and contributions file",
42
+ help='Download Excel workbook.',
43
+ data=owb.saveContributions(),
44
+ file_name=kz.getKey('name')+'.xlsx',
45
+ disabled=kz.caseHasNotCompletedRun(),
46
+ mime='application/vnd.ms-excel')
@@ -72,7 +72,7 @@ An example is provided
72
72
  can be found in this [directory](https://github.com/mdlacasse/Owl/blob/main/examples/).
73
73
  Using a *case* file
74
74
  will populate all the fields required to run a scenario. A *case* file for the case being developed
75
- can be saved under the [Case Results](#case-results) page and made available to reload at a later time.
75
+ can be saved under the [Case Summary](#case-summary) page and made available to reload at a later time.
76
76
  Case parameter files can have any name but when saving from the interface, their name will start with *case_*
77
77
  followed by the case name.
78
78
 
@@ -278,8 +278,8 @@ than on January 1$^{st}$, the value of the first year will be reduced accordingl
278
278
  --------------------------------------------------------------------------------------
279
279
  ### :orange[Single Scenario]
280
280
 
281
- #### Case Results
282
- This page allows to run a single scenario based on the selections made
281
+ #### Graphs
282
+ This page displays various plots from a single scenario based on the selections made
283
283
  in the [Case Setup](#case-setup) section.
284
284
  This simulation uses a single instance of a series of rates, either fixed or varying,
285
285
  as selected in the **Case Setup** section.
@@ -292,15 +292,9 @@ in nominal value.
292
292
 
293
293
  When a case has run successfully, different graphs will show the time evolution
294
294
  of different quantities over the duration of the plan. Below
295
- these graphs, two additional buttons will appear. The
296
- `Download case file...` button allows to save all the parameters used to generate the
297
- outcome of this case to a *case* file.
298
- Another button called `Download wages and contributions file...` allows
299
- to save the contents of the tables on the corresponding page to an Excel workbook.
300
- With both these files, the same case can be reproduced at a later time by uploading
301
- them through the widgets on the `Basic Info` and `Wages and Contributions` pages.
295
+ these graphs, two additional buttons will appear.
302
296
 
303
- #### Case Worksheets
297
+ #### Worksheets
304
298
  This page shows the various worksheets containing annual transactions
305
299
  and savings account balances in nominal \\$.
306
300
  Each table can be downloaded separately in csv format, or all tables can be downloaded
@@ -311,10 +305,19 @@ The first line of the *Sources* worksheets are the most important
311
305
  as these lines are the only ones that are actionable.
312
306
 
313
307
  #### Case Summary
314
- This page shows a summary of the scenario which was computed.
308
+ This page shows a synopsis of the scenario which was computed.
315
309
  It displays informative sums of relevant income, bequest, and spending values.
316
310
  The contents of this page can be downloaded as a plain text file by
317
- clicking the button at the bottom of the page.
311
+ clicking the button at the bottom of the section.
312
+
313
+ Parameters used to generate the case are collected in *toml* format and displayed.
314
+ The `Download case file...` button allows to save the parameters used to generate the
315
+ outcome of this case to a *case* file.
316
+
317
+ Finally, another button called `Download wages and contributions file...` allows
318
+ to save the contents of the tables on the corresponding page to an Excel workbook.
319
+ With both these files, the same case can be reproduced at a later time by uploading
320
+ them through the widgets on the `Basic Info` and `Wages and Contributions` pages.
318
321
 
319
322
  --------------------------------------------------------------------------------------
320
323
  ### :orange[Multiple Scenarios]
@@ -0,0 +1,32 @@
1
+ import streamlit as st
2
+
3
+ import sskeys as kz
4
+ import owlbridge as owb
5
+
6
+
7
+ ret = kz.titleBar('results')
8
+ kz.caseHeader("Graphs")
9
+
10
+ if ret is None or kz.caseHasNoPlan():
11
+ st.info('Case(s) must be first created before running this page.')
12
+ else:
13
+ if kz.caseIsRunReady():
14
+ owb.runPlan()
15
+
16
+ st.write("Optimize a single scenario based on the parameters selected in the **Case Setup** section.")
17
+ col1, col2 = st.columns(2, gap='large', vertical_alignment='bottom')
18
+ with col1:
19
+ choices = ['nominal', 'today']
20
+ kz.initKey('plots', choices[0])
21
+ ret = kz.getRadio("Dollar amounts in plots", choices, 'plots',
22
+ callback=owb.setDefaultPlots)
23
+
24
+ with col2:
25
+ st.button('Re-run single case', help='Optimize single scenario.',
26
+ on_click=owb.runPlan, disabled=kz.caseIsNotRunReady())
27
+
28
+ st.divider()
29
+ if kz.caseHasNotCompletedRun():
30
+ st.info("Case status is currently '%s'." % kz.getKey('caseStatus'))
31
+ else:
32
+ owb.plotSingleResults()
@@ -37,7 +37,7 @@ of Jack and Jill provided here as an example:
37
37
  you just downloaded (*case_jack+jill.toml*).
38
38
  1) Navigate to the **Wages and Contributions** page and
39
39
  drag and drop the wages and contributions file you downloaded (*jack+jill.xlsx*).
40
- 1) Move to the **Case Results** page and click on the `Run single case` button.
40
+ 1) Move to the **Single Scenario** section to browse results.
41
41
 
42
42
  Congratulations! :balloon: You just ran your first case. You can now explore each page and
43
43
  experiment with different parameters.
@@ -113,19 +113,19 @@ else:
113
113
  col1, col2, col3, col4 = st.columns(4, gap='large', vertical_alignment='top')
114
114
  with col1:
115
115
  kz.initKey('mean0', 0)
116
- kz.getNum('S&P 500', 'mean0', ro, step=1., callback=updateRates)
116
+ kz.getNum('S&P 500', 'mean0', ro, step=1., min_value=-9., callback=updateRates)
117
117
 
118
118
  with col2:
119
119
  kz.initKey('mean1', 0)
120
- kz.getNum('Corporate Bonds Baa', 'mean1', ro, step=1., callback=updateRates)
120
+ kz.getNum('Corporate Bonds Baa', 'mean1', ro, step=1., min_value=-9., callback=updateRates)
121
121
 
122
122
  with col3:
123
123
  kz.initKey('mean2', 0)
124
- kz.getNum('10-y Treasury Notes', 'mean2', ro, step=1., callback=updateRates)
124
+ kz.getNum('10-y Treasury Notes', 'mean2', ro, step=1., min_value=-9., callback=updateRates)
125
125
 
126
126
  with col4:
127
127
  kz.initKey('mean3', 0)
128
- kz.getNum('Cash Assets/Inflation', 'mean3', ro, step=1., callback=updateRates)
128
+ kz.getNum('Cash Assets/Inflation', 'mean3', ro, step=1., min_value=-9., callback=updateRates)
129
129
 
130
130
  st.write('##### Volatility (%)')
131
131
  col1, col2, col3, col4 = st.columns(4, gap='large', vertical_alignment='top')
@@ -4,11 +4,14 @@ import sskeys as kz
4
4
  import owlbridge as owb
5
5
 
6
6
  ret = kz.titleBar('worksheets')
7
- kz.caseHeader("Case Worksheets")
7
+ kz.caseHeader("Worksheets")
8
8
 
9
9
  if ret is None or kz.caseHasNoPlan():
10
10
  st.info('Case(s) must be first created before running this page.')
11
11
  else:
12
+ if kz.caseIsRunReady():
13
+ owb.runPlan()
14
+
12
15
  if kz.caseHasNotCompletedRun():
13
16
  st.info("Case status is currently '%s'." % kz.getKey('caseStatus'))
14
17
  else:
@@ -18,8 +18,8 @@ pages = {
18
18
  st.Page('Rates_Selection.py', icon=':material/monitoring:'),
19
19
  st.Page('Asset_Allocation.py', icon=':material/percent:'),
20
20
  st.Page('Optimization_Parameters.py', icon=':material/tune:')],
21
- 'Single Scenario': [st.Page('Case_Results.py', icon=':material/directions_run:'),
22
- st.Page('Case_Worksheets.py', icon=':material/data_table:'),
21
+ 'Single Scenario': [st.Page('Graphs.py', icon=':material/directions_run:'),
22
+ st.Page('Worksheets.py', icon=':material/data_table:'),
23
23
  st.Page('Case_Summary.py', icon=':material/description:')],
24
24
  'Multiple Scenarios': [st.Page('Historical_Range.py', icon=':material/history:'),
25
25
  st.Page('Monte_Carlo.py', icon=':material/finance:')],
@@ -125,13 +125,16 @@ def runPlan(plan):
125
125
  kz.storeKey('caseStatus', plan.caseStatus)
126
126
  if plan.caseStatus == 'solved':
127
127
  kz.storeKey('summary', plan.summaryString())
128
+ kz.storeKey('casetoml', getCaseString().getvalue())
128
129
  else:
129
130
  kz.storeKey('summary', '')
131
+ kz.storeKey('casetoml', '')
130
132
 
131
133
 
132
134
  @_checkPlan
133
135
  def runHistorical(plan):
134
- prepareRun(plan)
136
+ plan1 = owl.clone(plan)
137
+ prepareRun(plan1)
135
138
 
136
139
  hyfrm = kz.getKey('hyfrm')
137
140
  hyto = kz.getKey('hyto')
@@ -139,41 +142,33 @@ def runHistorical(plan):
139
142
  objective, options = kz.getSolveParameters()
140
143
  try:
141
144
  mybar = progress.Progress(None)
142
- fig, summary = plan.runHistoricalRange(objective, options, hyfrm, hyto, figure=True, progcall=mybar)
145
+ fig, summary = plan1.runHistoricalRange(objective, options, hyfrm, hyto, figure=True, progcall=mybar)
143
146
  kz.storeKey('histoPlot', fig)
144
147
  kz.storeKey('histoSummary', summary)
145
148
  except Exception as e:
146
149
  kz.storeKey('histoPlot', None)
147
150
  kz.storeKey('histoSummary', None)
148
- kz.storeKey('caseStatus', 'exception')
149
151
  st.error('Solution failed: %s' % e)
150
- setRates()
151
152
  return
152
153
 
153
- kz.storeKey('caseStatus', 'ran Historical Range')
154
- setRates()
155
-
156
154
 
157
155
  @_checkPlan
158
156
  def runMC(plan):
159
- prepareRun(plan)
157
+ plan1 = owl.clone(plan)
158
+ prepareRun(plan1)
160
159
 
161
160
  N = kz.getKey('MC_cases')
162
161
 
163
162
  objective, options = kz.getSolveParameters()
164
163
  try:
165
164
  mybar = progress.Progress(None)
166
- fig, summary = plan.runMC(objective, options, N, figure=True, progcall=mybar)
165
+ fig, summary = plan1.runMC(objective, options, N, figure=True, progcall=mybar)
167
166
  kz.storeKey('monteCarloPlot', fig)
168
167
  kz.storeKey('monteCarloSummary', summary)
169
168
  except Exception as e:
170
169
  kz.storeKey('monteCarloPlot', None)
171
170
  kz.storeKey('monteCarloSummary', None)
172
- kz.storeKey('caseStatus', 'exception')
173
171
  st.error('Solution failed: %s' % e)
174
- return
175
-
176
- kz.storeKey('caseStatus', 'ran Monte Carlo')
177
172
 
178
173
 
179
174
  @_checkPlan
@@ -499,15 +494,21 @@ def saveContributions(plan):
499
494
 
500
495
 
501
496
  @_checkPlan
502
- def saveCaseFile(plan):
497
+ def getCaseString(plan):
503
498
  stringBuffer = StringIO()
504
499
  if kz.getSolveParameters() is None:
505
500
  return ''
506
501
  plan.saveConfig(stringBuffer)
502
+
503
+ return stringBuffer
504
+
505
+
506
+ @_checkPlan
507
+ def saveCaseFile(plan):
508
+ stringBuffer = getCaseString()
507
509
  encoded_data = stringBuffer.getvalue().encode('utf-8')
508
- bytesBuffer = BytesIO(encoded_data)
509
510
 
510
- return bytesBuffer
511
+ return BytesIO(encoded_data)
511
512
 
512
513
 
513
514
  def createCaseFromFile(file):
@@ -531,6 +532,7 @@ def genDic(plan):
531
532
  dic['plan'] = plan
532
533
  dic['name'] = plan._name
533
534
  dic['summary'] = ''
535
+ dic['casetoml'] = ''
534
536
  dic['caseStatus'] = 'new'
535
537
  dic['status'] = ['unknown', 'single', 'married'][plan.N_i]
536
538
  # Prepend year if not there.
@@ -7,4 +7,4 @@ scipy
7
7
  streamlit
8
8
  toml
9
9
  # --extra-index-url https://test.pypi.org/simple
10
- owlplanner >= 2025.02.04
10
+ owlplanner >= 2025.02.05
@@ -131,6 +131,10 @@ def caseHasCompletedRun():
131
131
  return getKey('caseStatus') == 'solved'
132
132
 
133
133
 
134
+ def caseIsRunReady():
135
+ return not caseIsNotRunReady() and getKey('caseStatus') in ['modified', 'new']
136
+
137
+
134
138
  def caseIsNotRunReady():
135
139
  return (getKey('plan') is None or
136
140
  getKey('objective') is None or
@@ -1 +0,0 @@
1
- __version__ = "2025.02.04"
@@ -1,51 +0,0 @@
1
- import streamlit as st
2
-
3
- import sskeys as kz
4
- import owlbridge as owb
5
-
6
-
7
- ret = kz.titleBar('results')
8
- kz.caseHeader("Case Results")
9
-
10
- if ret is None or kz.caseHasNoPlan():
11
- st.info('Case(s) must be first created before running this page.')
12
- else:
13
- st.write("Optimize a single scenario based on the parameters selected in the **Case setup** section.")
14
- col1, col2 = st.columns(2, gap='large', vertical_alignment='bottom')
15
- with col1:
16
- choices = ['nominal', 'today']
17
- kz.initKey('plots', choices[0])
18
- ret = kz.getRadio("Dollar amounts in plots", choices, 'plots',
19
- callback=owb.setDefaultPlots)
20
-
21
- with col2:
22
- st.button('Run single case', help='Optimize single scenario.',
23
- on_click=owb.runPlan, disabled=kz.caseIsNotRunReady())
24
-
25
- st.divider()
26
- if kz.caseHasNotCompletedRun():
27
- st.info("Case status is currently '%s'." % kz.getKey('caseStatus'))
28
- else:
29
- owb.plotSingleResults()
30
-
31
- st.divider()
32
- col1, col2 = st.columns(2, gap='large', vertical_alignment='bottom')
33
- with col1:
34
- fileName = 'case_'+kz.getKey('name')+'.toml'
35
- download3 = st.download_button(
36
- label="Download case file...",
37
- help='Download TOML file.',
38
- data=owb.saveCaseFile(),
39
- file_name=fileName,
40
- disabled=kz.caseHasNotCompletedRun(),
41
- mime='application/toml'
42
- )
43
- with col2:
44
- download2 = st.download_button(
45
- label="Download wages and contributions file...",
46
- help='Download Excel workbook.',
47
- data=owb.saveContributions(),
48
- file_name=kz.getKey('name')+'.xlsx',
49
- disabled=kz.caseHasNotCompletedRun(),
50
- mime='application/vnd.ms-excel'
51
- )
@@ -1,21 +0,0 @@
1
- import streamlit as st
2
-
3
- import sskeys as kz
4
-
5
- ret = kz.titleBar('summary')
6
- kz.caseHeader("Case Summary")
7
-
8
- if ret is None or kz.caseHasNoPlan():
9
- st.info('Case(s) must be first created before running this page.')
10
- else:
11
- if kz.caseHasNotCompletedRun():
12
- st.info("Case status is currently '%s'." % kz.getKey('caseStatus'))
13
- else:
14
- lines = kz.getKey('summary')
15
- if lines != '':
16
- st.code(lines, language=None)
17
- st.divider()
18
- st.download_button('Download Summary',
19
- data=lines,
20
- file_name='Summary_'+kz.getKey('name')+'.txt',
21
- mime='text/plain;charset=UTF-8')
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes