owlplanner 2025.2.27__tar.gz → 2025.3.7__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 (102) hide show
  1. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/.github/workflows/github-actions-runtests.yml +1 -0
  2. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/INSTALL.md +4 -3
  3. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/PKG-INFO +9 -3
  4. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/README.md +8 -2
  5. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/pyproject.toml +1 -1
  6. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/src/owlplanner/plan.py +20 -9
  7. owlplanner-2025.3.7/src/owlplanner/version.py +1 -0
  8. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/Create_Case.py +2 -2
  9. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/Documentation.py +29 -13
  10. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/Output_Files.py +1 -2
  11. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/Quick_Start.py +2 -3
  12. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/requirements.txt +1 -1
  13. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/sskeys.py +5 -5
  14. owlplanner-2025.2.27/src/owlplanner/version.py +0 -1
  15. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/.devcontainer/devcontainer.json +0 -0
  16. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/.flake8 +0 -0
  17. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/.gitattributes +0 -0
  18. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/.gitignore +0 -0
  19. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/LICENSE +0 -0
  20. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/USER_GUIDE.md +0 -0
  21. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/docker/Dockerfile +0 -0
  22. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/docker/README.md +0 -0
  23. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/docker/docker-compose.yml +0 -0
  24. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/docker/fastentrypoint.sh +0 -0
  25. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/docs/images/AD-taxDef.png +0 -0
  26. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/docs/images/AD-taxFree.png +0 -0
  27. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/docs/images/AD-taxable.png +0 -0
  28. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/docs/images/Hist_Bequest.png +0 -0
  29. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/docs/images/Hist_Spending.png +0 -0
  30. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/docs/images/MC-tutorial2a.png +0 -0
  31. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/docs/images/MC-tutorial2b.png +0 -0
  32. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/docs/images/OwlUI.png +0 -0
  33. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/docs/images/allocations.png +0 -0
  34. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/docs/images/owl.png +0 -0
  35. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/docs/images/profile.png +0 -0
  36. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/docs/images/ratesCorrelations.png +0 -0
  37. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/docs/images/ratesPlot.png +0 -0
  38. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/docs/images/savingsPlot.png +0 -0
  39. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/docs/images/sourcesPlot.png +0 -0
  40. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/docs/images/spendingPlot.png +0 -0
  41. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/docs/images/taxIncomePlot.png +0 -0
  42. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/docs/images/taxesPlot.png +0 -0
  43. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/docs/owl.pdf +0 -0
  44. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/docs/owl.tex +0 -0
  45. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/examples/case_jack+jill.toml +0 -0
  46. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/examples/case_joe.toml +0 -0
  47. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/examples/case_john+sally.toml +0 -0
  48. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/examples/case_jon+jane.toml +0 -0
  49. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/examples/case_kim+sam-bequest.toml +0 -0
  50. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/examples/case_kim+sam-spending.toml +0 -0
  51. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/examples/jack+jill.xlsx +0 -0
  52. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/examples/joe.xlsx +0 -0
  53. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/examples/john+sally.xlsx +0 -0
  54. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/examples/jon+jane.xlsx +0 -0
  55. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/examples/template.xlsx +0 -0
  56. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/notebooks/john+sally.ipynb +0 -0
  57. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/notebooks/kim+sam.ipynb +0 -0
  58. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/notebooks/template.ipynb +0 -0
  59. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/notebooks/tutorial_1.ipynb +0 -0
  60. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/notebooks/tutorial_2.ipynb +0 -0
  61. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/notebooks/tutorial_3.ipynb +0 -0
  62. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/owlplanner.cmd +0 -0
  63. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/owlplanner.sh +0 -0
  64. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/requirements.txt +0 -0
  65. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/src/owlplanner/__init__.py +0 -0
  66. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/src/owlplanner/abcapi.py +0 -0
  67. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/src/owlplanner/config.py +0 -0
  68. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/src/owlplanner/data/__init__.py +0 -0
  69. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/src/owlplanner/data/rates.csv +0 -0
  70. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/src/owlplanner/logging.py +0 -0
  71. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/src/owlplanner/progress.py +0 -0
  72. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/src/owlplanner/rates.py +0 -0
  73. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/src/owlplanner/tax2025.py +0 -0
  74. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/src/owlplanner/timelists.py +0 -0
  75. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/src/owlplanner/utils.py +0 -0
  76. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/tests/test_logger.py +0 -0
  77. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/tests/test_regressions.py +0 -0
  78. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/tests/test_repro.py +0 -0
  79. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/tests/test_toml_cases.py +0 -0
  80. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/tests/test_units.py +0 -0
  81. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ttt.py +0 -0
  82. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/About_Owl.py +0 -0
  83. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/Asset_Allocation.py +0 -0
  84. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/Assets.py +0 -0
  85. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/Fixed_Income.py +0 -0
  86. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/Graphs.py +0 -0
  87. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/Historical_Range.py +0 -0
  88. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/Logs.py +0 -0
  89. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/Monte_Carlo.py +0 -0
  90. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/Optimization_Parameters.py +0 -0
  91. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/README.md +0 -0
  92. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/Rates_Selection.py +0 -0
  93. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/Settings.py +0 -0
  94. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/Wages_And_Contributions.py +0 -0
  95. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/Worksheets.py +0 -0
  96. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/main+fonts.py +0 -0
  97. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/main.py +0 -0
  98. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/owlbridge.py +0 -0
  99. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/plots.py +0 -0
  100. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/progress.py +0 -0
  101. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/style.css +0 -0
  102. {owlplanner-2025.2.27 → owlplanner-2025.3.7}/ui/tomlexamples.py +0 -0
@@ -5,6 +5,7 @@ name: Python package
5
5
 
6
6
  run-name: ${{ github.actor }} running tests
7
7
  on: [push]
8
+ permissions: read-all
8
9
  jobs:
9
10
  build:
10
11
  runs-on: ubuntu-latest
@@ -14,7 +14,7 @@ or, if one prefers to have everything on their own computer,
14
14
  to install and run a Docker image as described in these [instructions](docker/README.md).
15
15
 
16
16
  ### Requirements
17
- You will need Python and `pip` installed on your computer for that purpose.
17
+ You will need Python and `pip` installed on your computer for completing the installation.
18
18
 
19
19
  ### Installation steps for developers
20
20
  These instructions are command-line instructions.
@@ -29,7 +29,8 @@ From the top directory of the source code run:
29
29
  python -m build
30
30
  pip install -e .
31
31
  ```
32
- The -e instructs Python to load the live version in the current directory tree.
32
+ The -e instructs `pip` to install in *editable* mode and use the live version
33
+ in the current directory tree.
33
34
 
34
35
  ### Running the streamlit frontend
35
36
  Running the Owl user interface locally from Windows:
@@ -42,7 +43,7 @@ Running the Owl user interface locally from Linux or MacOS:
42
43
  ```
43
44
 
44
45
  ### Publishing a version (for reference only)
45
- Run checks before commit:
46
+ Run checks before all commits:
46
47
  ```
47
48
  flake8 ui src tests
48
49
  pytest
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: owlplanner
3
- Version: 2025.2.27
3
+ Version: 2025.3.7
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
@@ -711,7 +711,13 @@ Description-Content-Type: text/markdown
711
711
  -------------------------------------------------------------------------------------
712
712
 
713
713
  ### TL;DR
714
- Owl is a planning tool that uses a linear programming optimization algorithm to provide guidance on retirement decisions. There are a few ways to run Owl.
714
+ Owl is a retirement planning tool that uses a linear programming optimization algorithm
715
+ to provide guidance on retirement decisions, including Roth conversions.
716
+ Users can select varying return rates to perform historical back testing,
717
+ stochastic rates for performing Monte Carlo analyses,
718
+ or fixed rates either derived from historical averages, or set by the user.
719
+
720
+ There are a few ways to run Owl:
715
721
 
716
722
  - Run Owl directly on the Streamlit Community Server at [owlplanner.streamlit.app](https://owlplanner.streamlit.app).
717
723
 
@@ -868,7 +874,7 @@ assets to support, even with no estate being left.
868
874
  ---------------------------------------------------------------
869
875
  ## Documentation
870
876
 
871
- - Documentation for the app user interface is available from the interface itself.
877
+ - Documentation for the app user interface is available from the interface [itself](https://owlplanner.streamlit.app/Documentation).
872
878
  - Installation guide and software requirements can be found [here](INSTALL.md).
873
879
  - User guide for the underlying Python package as used in a Jupyter notebook can be found [here](USER_GUIDE.md).
874
880
 
@@ -8,7 +8,13 @@
8
8
  -------------------------------------------------------------------------------------
9
9
 
10
10
  ### TL;DR
11
- Owl is a planning tool that uses a linear programming optimization algorithm to provide guidance on retirement decisions. There are a few ways to run Owl.
11
+ Owl is a retirement planning tool that uses a linear programming optimization algorithm
12
+ to provide guidance on retirement decisions, including Roth conversions.
13
+ Users can select varying return rates to perform historical back testing,
14
+ stochastic rates for performing Monte Carlo analyses,
15
+ or fixed rates either derived from historical averages, or set by the user.
16
+
17
+ There are a few ways to run Owl:
12
18
 
13
19
  - Run Owl directly on the Streamlit Community Server at [owlplanner.streamlit.app](https://owlplanner.streamlit.app).
14
20
 
@@ -165,7 +171,7 @@ assets to support, even with no estate being left.
165
171
  ---------------------------------------------------------------
166
172
  ## Documentation
167
173
 
168
- - Documentation for the app user interface is available from the interface itself.
174
+ - Documentation for the app user interface is available from the interface [itself](https://owlplanner.streamlit.app/Documentation).
169
175
  - Installation guide and software requirements can be found [here](INSTALL.md).
170
176
  - User guide for the underlying Python package as used in a Jupyter notebook can be found [here](USER_GUIDE.md).
171
177
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "owlplanner"
7
- version = "2025.02.27"
7
+ version = "2025.03.07"
8
8
  authors = [
9
9
  { name="Martin-D. Lacasse", email="martin.d.lacasse@gmail.com" },
10
10
  ]
@@ -1968,8 +1968,8 @@ class Plan(object):
1968
1968
  self.dist_in = self.w_ijn[:, 1, :] - self.rmd_in
1969
1969
  self.dist_in[self.dist_in < 0] = 0
1970
1970
  self.G_n = np.sum(self.F_tn, axis=0)
1971
- T_tn = self.F_tn * self.theta_tn
1972
- self.T_n = np.sum(T_tn, axis=0)
1971
+ self.T_tn = self.F_tn * self.theta_tn
1972
+ self.T_n = np.sum(self.T_tn, axis=0)
1973
1973
 
1974
1974
  tau_0 = np.array(self.tau_kn[0, :])
1975
1975
  tau_0[tau_0 < 0] = 0
@@ -2105,6 +2105,12 @@ class Plan(object):
2105
2105
  taxPaidNow = np.sum(self.T_n / self.gamma_n[:-1], axis=0)
2106
2106
  dic["Total income tax paid on ordinary income"] = f"{u.d(taxPaidNow)}"
2107
2107
  dic["- Total income tax paid on ordinary income (nominal)"] = f"{u.d(taxPaid)}"
2108
+ for t in range(self.N_t):
2109
+ taxPaid = np.sum(self.T_tn[t], axis=0)
2110
+ taxPaidNow = np.sum(self.T_tn[t] / self.gamma_n[:-1], axis=0)
2111
+ tname = tx.taxBracketNames[t]
2112
+ dic[f"-- Subtotal in tax bracket {tname}"] = f"{u.d(taxPaidNow)}"
2113
+ dic[f"--- Subtotal in tax bracket {tname} (nominal)"] = f"{u.d(taxPaid)}"
2108
2114
 
2109
2115
  taxPaid = np.sum(self.U_n, axis=0)
2110
2116
  taxPaidNow = np.sum(self.U_n / self.gamma_n[:-1], axis=0)
@@ -2129,12 +2135,17 @@ class Plan(object):
2129
2135
  iname_s = self.inames[self.i_s]
2130
2136
  iname_d = self.inames[self.i_d]
2131
2137
  dic[f"Sum of spousal transfer to {iname_s} in year {ynx}"] = (f"{u.d(totSpousalNow)}")
2132
- dic[f"- Sum of spousal transfer to {iname_s} in year {ynx} (nominal)"] = (f"{u.d(totSpousal)}")
2133
- dic[f"-- Spousal transfer to {iname_s} in year {ynx} - taxable (nominal)"] = (f"{u.d(q_j[0])}")
2134
- dic[f"-- Spousal transfer to {iname_s} in year {ynx} - tax-def (nominal)"] = (f"{u.d(q_j[1])}")
2135
- dic[f"-- Spousal transfer to {iname_s} in year {ynx} - tax-free (nominal)"] = (f"{u.d(q_j[2])}")
2136
-
2137
- dic[f"Sum of post-tax non-spousal bequests from {iname_d} in year {ynx}"] = (f"{u.d(totOthersNow)}")
2138
+ dic[f"- Sum of spousal transfer to {iname_s} in year {ynx} (nominal)"] = (
2139
+ f"{u.d(totSpousal)}")
2140
+ dic[f"-- Spousal transfer to {iname_s} in year {ynx} - taxable (nominal)"] = (
2141
+ f"{u.d(q_j[0])}")
2142
+ dic[f"-- Spousal transfer to {iname_s} in year {ynx} - tax-def (nominal)"] = (
2143
+ f"{u.d(q_j[1])}")
2144
+ dic[f"-- Spousal transfer to {iname_s} in year {ynx} - tax-free (nominal)"] = (
2145
+ f"{u.d(q_j[2])}")
2146
+
2147
+ dic[f"Sum of post-tax non-spousal bequests from {iname_d} in year {ynx}"] = (
2148
+ f"{u.d(totOthersNow)}")
2138
2149
  dic[f"- Sum of post-tax non-spousal bequests from {iname_d} in year {ynx} (nominal)"] = (
2139
2150
  f"{u.d(totOthers)}")
2140
2151
  dic[f"-- Post-tax non-spousal bequests from {iname_d} in year {ynx} - taxable (nominal)"] = (
@@ -2156,7 +2167,7 @@ class Plan(object):
2156
2167
  dic[f"-- Post-tax account value at the end of {lastyear} - tax-free (nominal)"] = (f"{u.d(estate[2])}")
2157
2168
 
2158
2169
  dic["Plan starting date"] = str(self.startDate)
2159
- dic[f"Cumulative inflation factor from start date to end of {lastyear}"] = f"{self.gamma_n[-1]:.2f}"
2170
+ dic[f"Cumulative inflation factor from start date to end of {lastyear}"] = (f"{self.gamma_n[-1]:.2f}")
2160
2171
  for i in range(self.N_i):
2161
2172
  dic[f"{self.inames[i]:>12}'s {self.horizons[i]:02}-year life horizon"] = (
2162
2173
  f"{now} -> {now + self.horizons[i] - 1}")
@@ -0,0 +1 @@
1
+ __version__ = "2025.03.07"
@@ -25,7 +25,7 @@ elif ret == kz.loadCaseFile:
25
25
  st.info(
26
26
  "#### Starting a case from a *case* parameter file.\n\n"
27
27
  "Upload your own case or select one from multiple examples."
28
- " Alternatively, you can select `New Case...` in the margin selector box to start a case from scratch.\n\n"
28
+ " Alternatively, you can select `New Case...` in the top selector box to start a case from scratch.\n\n"
29
29
  "Look at the :material/help: [Documentation](Documentation) for more details."
30
30
  )
31
31
  st.write("#### Upload your own case file")
@@ -121,7 +121,7 @@ else:
121
121
  st.divider()
122
122
  cantcreate = kz.isIncomplete() or diz1
123
123
  if not cantcreate and kz.getKey("plan") is None:
124
- st.info("Plan needs to be created once all the information needed has been entered.")
124
+ st.info("Plan needs to be created once desired changes are completed.")
125
125
 
126
126
  cantmodify = kz.currentCaseName() == kz.newCase or kz.currentCaseName() == kz.loadCaseFile
127
127
  cantcopy = cantmodify or kz.caseHasNoPlan()
@@ -34,16 +34,28 @@ formulation of the optimization problem can be found
34
34
 
35
35
  --------------------------------------------------------------------------------------
36
36
  ### Getting started with the user interface
37
- Functions of each page are described below in the same order as they appear in the sidebar.
37
+ Functions of each page are described below in the same order as they appear in the left sidebar.
38
38
  Typically, pages would be accessed in order, starting from the top.
39
+
39
40
  The `Case selector` box at the top of the page allows to select an existing case
40
41
  or create a new one from scratch, or from a *case* parameter file, which
41
42
  would then populate all parameter values.
42
43
  This box is present in all pages except those in the **Resources** section
43
- and allows to compare different scenarios.
44
+ and allows to access and compare different scenarios.
45
+
46
+ A typical workflow for exploring different scenarios involves starting with a base
47
+ case and then duplicating/creating derived scenarios with slight changes in the parameters,
48
+ which are configured in the **Case Setup** section. The comparison between the
49
+ different resulting outcomes is shown on the [Output Files](#output-files) page.
50
+
51
+ Owl uses a year as the standard time unit. All values are therefore entered and
52
+ reported as yearly values. These include wages, income, rates, social security, etc.
53
+ Dollar values are typically entered in thousands, unless in tables, where they
54
+ are entered and reported in unit dollars.
44
55
 
45
56
  There are four sections in the user interface:
46
57
  **Case Setup**, **Single Scenario**, **Multiple Scenarios**, and **Resources**.
58
+ The sections below follow the same logical order.
47
59
 
48
60
  -------------------------------------------------
49
61
  ### :orange[Case Setup]
@@ -307,24 +319,28 @@ The first line of the *Sources* worksheets are the most important
307
319
  as these lines are the only ones that are actionable.
308
320
 
309
321
  #### Output Files
310
- This page allow to compare cases and save files for future use.
311
- First it shows a synopsis of the computed scenario by
322
+ This page allows to compare cases and save files for future use.
323
+ First, it shows a synopsis of the computed scenario by
312
324
  displaying sums of income, bequest, and spending values over the duration of the plan.
313
- If more than one case was run, they will be compared provided they were made
314
- for the same individuals.
325
+ If multiple cases were configured and run (most likely through duplication and
326
+ modifying the configuration), they will be compared in that panel provided they were made
327
+ for the same individuals. Column on the left shows the values for the selected case
328
+ while those on the right will show the differences.
315
329
  The contents of the synopsis can be downloaded as a plain text file by
316
330
  clicking the button below it.
317
331
 
318
- Similarly, parameters used to generate the case are collected in *toml* format and displayed.
319
- The `Download case file...` button allows to save the parameters used to generate the
320
- outcome of this case to a *case* file.
321
-
322
332
  Another section called `Excel workbooks` allows
323
- to save the contents of the tables on the corresponding page to an Excel workbook.
333
+ to save the contents of the tables on the corresponding page as an Excel workbook.
334
+ These data are displayed on the *Worksheets* and the *Wages and Contributions* pages.
335
+
336
+ Similarly, all parameters used to generate the case are collected in *toml* format and displayed.
337
+ The `Download case file...` button allows to save the parameters of the selected scenario
338
+ to a *case* file.
324
339
 
325
- With the case parameter and the wages and contributions files,
340
+ With the case parameter file and the wages and contributions worksheet,
326
341
  the same case can be reproduced at a later time by uploading
327
- them through the widgets on the `Create Case` and `Wages and Contributions` pages.
342
+ them through the widgets on the `Create Case` and `Wages and Contributions` pages,
343
+ respectively.
328
344
 
329
345
  --------------------------------------------------------------------------------------
330
346
  ### :orange[Multiple Scenarios]
@@ -18,9 +18,8 @@ else:
18
18
  df = kz.compareSummaries()
19
19
  if df is not None:
20
20
  st.write("#### Synopsis")
21
- # st.code(lines, language=None)
22
- # st.markdown(df.to_markdown())
23
21
  st.dataframe(df[1:], use_container_width=True)
22
+ st.caption("Values are in today's \\$ unless marked otherwise.")
24
23
  st.download_button(
25
24
  "Download synopsis", data=df[1:].to_string(), file_name=f"Synopsis_{caseName}.txt",
26
25
  mime="text/plain;charset=UTF-8"
@@ -49,12 +49,11 @@ experiment with different parameters.
49
49
  For creating your own cases, you can start
50
50
  from scratch by selecting `New Case...` in the selection box while on the **Create Case** page,
51
51
  and fill in the information needed on each page in the `Case Setup` section.
52
- Once a case has been fully parameterized and successfully optimized,
53
- its parameters can be saved by using the `Download case file...` button on the `Output Files` page.
54
-
55
52
  Alternatively, you can duplicate any existing case by using
56
53
  the `Duplicate case` button, and then edit its values to fit your situation.
57
54
 
55
+ Once a case has been fully parameterized and successfully optimized,
56
+ its parameters can be saved by using the `Download case file...` button on the `Output Files` page.
58
57
  Multiple cases can coexist and can be called and compared using the `Case selector` box
59
58
  at the top of the page.
60
59
 
@@ -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.27
10
+ owlplanner >= 2025.03.07
@@ -260,7 +260,7 @@ def storepull(key):
260
260
  def setKey(key, val):
261
261
  ss.cases[ss.currentCase][key] = val
262
262
  ss.cases[ss.currentCase]["caseStatus"] = "modified"
263
- # print("setKey", key, val)
263
+ ss.cases[ss.currentCase]["summaryDf"] = None
264
264
  return val
265
265
 
266
266
 
@@ -326,7 +326,7 @@ def compareSummaries():
326
326
  df = pd.concat([df, odf])
327
327
 
328
328
  if df.shape[0] > 1:
329
- # Unroll to subtract strings representations of numbers.
329
+ # Unroll to subtract $tring representation of numbers.
330
330
  for col in range(1, df.shape[1] - 5):
331
331
  strval = df.iloc[0, col]
332
332
  if isinstance(strval, str) and strval[0] == "$":
@@ -334,9 +334,9 @@ def compareSummaries():
334
334
  for row in range(1, df.shape[0]):
335
335
  fnval = float(df.iloc[row, col][1:].replace(",", ""))
336
336
  diff = fnval - f0val
337
- sign = "+" if diff >= 0 else "-"
338
- sign = "" if diff == 0 else sign
339
- df.iloc[row, col] = f"{sign}${abs(diff):,.0f}"
337
+ sign = "\u2191" if diff >= 0 else "\u2193"
338
+ sign = "\u2192" if diff == 0 else sign
339
+ df.iloc[row, col] = f"{sign} ${abs(diff):,.0f}"
340
340
 
341
341
  return df.transpose()
342
342
 
@@ -1 +0,0 @@
1
- __version__ = "2025.02.27"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes