owlplanner 2025.12.5__py3-none-any.whl → 2026.1.26__py3-none-any.whl
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.
- owlplanner/In Discussion #58, the case of Kim and Sam.md +307 -0
- owlplanner/__init__.py +20 -1
- owlplanner/abcapi.py +24 -23
- owlplanner/cli/README.md +50 -0
- owlplanner/cli/_main.py +52 -0
- owlplanner/cli/cli_logging.py +56 -0
- owlplanner/cli/cmd_list.py +83 -0
- owlplanner/cli/cmd_run.py +86 -0
- owlplanner/config.py +315 -136
- owlplanner/data/__init__.py +21 -0
- owlplanner/data/awi.csv +75 -0
- owlplanner/data/bendpoints.csv +49 -0
- owlplanner/data/newawi.csv +75 -0
- owlplanner/data/rates.csv +99 -98
- owlplanner/debts.py +315 -0
- owlplanner/fixedassets.py +288 -0
- owlplanner/mylogging.py +157 -25
- owlplanner/plan.py +1044 -332
- owlplanner/plotting/__init__.py +16 -3
- owlplanner/plotting/base.py +17 -3
- owlplanner/plotting/factory.py +16 -3
- owlplanner/plotting/matplotlib_backend.py +30 -7
- owlplanner/plotting/plotly_backend.py +33 -10
- owlplanner/progress.py +66 -9
- owlplanner/rates.py +366 -361
- owlplanner/socialsecurity.py +142 -22
- owlplanner/tax2026.py +170 -57
- owlplanner/timelists.py +316 -32
- owlplanner/utils.py +204 -5
- owlplanner/version.py +20 -1
- {owlplanner-2025.12.5.dist-info → owlplanner-2026.1.26.dist-info}/METADATA +50 -158
- owlplanner-2026.1.26.dist-info/RECORD +36 -0
- owlplanner-2026.1.26.dist-info/entry_points.txt +2 -0
- owlplanner-2026.1.26.dist-info/licenses/AUTHORS +15 -0
- owlplanner/tax2025.py +0 -339
- owlplanner-2025.12.5.dist-info/RECORD +0 -24
- {owlplanner-2025.12.5.dist-info → owlplanner-2026.1.26.dist-info}/WHEEL +0 -0
- {owlplanner-2025.12.5.dist-info → owlplanner-2026.1.26.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI command for listing retirement planning case files.
|
|
3
|
+
|
|
4
|
+
This module provides the 'list' command for discovering and displaying
|
|
5
|
+
information about retirement planning case files in a directory.
|
|
6
|
+
|
|
7
|
+
Copyright (C) 2025-2026 The Owlplanner Authors
|
|
8
|
+
|
|
9
|
+
This program is free software: you can redistribute it and/or modify
|
|
10
|
+
it under the terms of the GNU General Public License as published by
|
|
11
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
12
|
+
(at your option) any later version.
|
|
13
|
+
|
|
14
|
+
This program is distributed in the hope that it will be useful,
|
|
15
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
16
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
17
|
+
GNU General Public License for more details.
|
|
18
|
+
|
|
19
|
+
You should have received a copy of the GNU General Public License
|
|
20
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import click
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from loguru import logger
|
|
26
|
+
|
|
27
|
+
import owlplanner as owl
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@click.command(name="list")
|
|
31
|
+
@click.argument(
|
|
32
|
+
"directory",
|
|
33
|
+
type=click.Path(exists=True, file_okay=False, path_type=Path),
|
|
34
|
+
default=".",
|
|
35
|
+
)
|
|
36
|
+
def cmd_list(directory):
|
|
37
|
+
"""
|
|
38
|
+
List OWL plan files in a directory.
|
|
39
|
+
|
|
40
|
+
DIRECTORY defaults to the current directory.
|
|
41
|
+
"""
|
|
42
|
+
logger.debug(f"Listing plans in directory: {directory}")
|
|
43
|
+
|
|
44
|
+
toml_files = sorted(directory.glob("*.toml"))
|
|
45
|
+
|
|
46
|
+
if not toml_files:
|
|
47
|
+
click.echo("No .toml files found.")
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
plans = []
|
|
51
|
+
|
|
52
|
+
for filename in toml_files:
|
|
53
|
+
try:
|
|
54
|
+
logger.debug(f"Loading plan from {filename}")
|
|
55
|
+
plan = owl.readConfig(str(filename), logstreams="loguru", readContributions=False)
|
|
56
|
+
plans.append((filename.stem, plan))
|
|
57
|
+
except Exception as e:
|
|
58
|
+
logger.warning(f"Failed to load {filename}: {e}")
|
|
59
|
+
|
|
60
|
+
if not plans:
|
|
61
|
+
click.echo("No valid OWL plans found.")
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
click.echo(f"{'FILE':<30} {'PLAN NAME':<20} {' TIME LISTS FILE':<30}")
|
|
65
|
+
click.echo("-" * 80)
|
|
66
|
+
|
|
67
|
+
CHECK = "✓"
|
|
68
|
+
CROSS = "✗"
|
|
69
|
+
|
|
70
|
+
plan_dir = directory
|
|
71
|
+
|
|
72
|
+
for stem, plan in plans:
|
|
73
|
+
# Truncate plan name if needed
|
|
74
|
+
plan_name = plan._name
|
|
75
|
+
if len(plan_name) > 20:
|
|
76
|
+
plan_name = plan_name[:16] + "..."
|
|
77
|
+
|
|
78
|
+
# Check if timeListsFileName exists in current directory
|
|
79
|
+
tl_name = plan.timeListsFileName
|
|
80
|
+
exists = (plan_dir / tl_name).exists() if tl_name else False
|
|
81
|
+
mark = CHECK if exists else CROSS
|
|
82
|
+
|
|
83
|
+
click.echo(f"{stem:<30} {plan_name:<20} {mark}{tl_name:<30}")
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI command for running retirement planning cases.
|
|
3
|
+
|
|
4
|
+
This module provides the 'run' command for executing retirement planning
|
|
5
|
+
optimization from the command line.
|
|
6
|
+
|
|
7
|
+
Copyright (C) 2025-2026 The Owlplanner Authors
|
|
8
|
+
|
|
9
|
+
This program is free software: you can redistribute it and/or modify
|
|
10
|
+
it under the terms of the GNU General Public License as published by
|
|
11
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
12
|
+
(at your option) any later version.
|
|
13
|
+
|
|
14
|
+
This program is distributed in the hope that it will be useful,
|
|
15
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
16
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
17
|
+
GNU General Public License for more details.
|
|
18
|
+
|
|
19
|
+
You should have received a copy of the GNU General Public License
|
|
20
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import click
|
|
24
|
+
from loguru import logger
|
|
25
|
+
import owlplanner as owl
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def validate_toml(ctx, param, value: Path):
|
|
30
|
+
if value is None:
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
# If no suffix, append .toml
|
|
34
|
+
if value.suffix == "":
|
|
35
|
+
value = value.with_suffix(".toml")
|
|
36
|
+
|
|
37
|
+
# Enforce .toml extension
|
|
38
|
+
if value.suffix.lower() != ".toml":
|
|
39
|
+
raise click.BadParameter("File must have a .toml extension")
|
|
40
|
+
|
|
41
|
+
# Check existence AFTER normalization
|
|
42
|
+
if not value.exists():
|
|
43
|
+
raise click.BadParameter(f"File '{value}' does not exist")
|
|
44
|
+
|
|
45
|
+
if not value.is_file():
|
|
46
|
+
raise click.BadParameter(f"'{value}' is not a file")
|
|
47
|
+
|
|
48
|
+
return value
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@click.command(name="run")
|
|
52
|
+
@click.argument(
|
|
53
|
+
"filename",
|
|
54
|
+
type=click.Path(exists=False, dir_okay=False, path_type=Path),
|
|
55
|
+
callback=validate_toml,
|
|
56
|
+
)
|
|
57
|
+
@click.option(
|
|
58
|
+
"--with-config",
|
|
59
|
+
"with_config",
|
|
60
|
+
type=click.Choice(["no", "first", "last"], case_sensitive=False),
|
|
61
|
+
default="first",
|
|
62
|
+
show_default=True,
|
|
63
|
+
help="Include config TOML sheet at the first or last position.",
|
|
64
|
+
)
|
|
65
|
+
def cmd_run(filename: Path, with_config: str):
|
|
66
|
+
"""Run the solver for an input OWL plan file.
|
|
67
|
+
|
|
68
|
+
FILENAME is the OWL plan file to run. If no extension is provided,
|
|
69
|
+
.toml will be appended. The file must exist.
|
|
70
|
+
|
|
71
|
+
An output Excel file with results will be created in the current directory.
|
|
72
|
+
The output filename is derived from the input filename by appending
|
|
73
|
+
'_results.xlsx' to the stem of the input filename.
|
|
74
|
+
|
|
75
|
+
Optionally include the case configuration as a TOML worksheet.
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
logger.debug(f"Executing the run command with file: {filename}")
|
|
79
|
+
|
|
80
|
+
plan = owl.readConfig(str(filename), logstreams="loguru", readContributions=True)
|
|
81
|
+
plan.solve(plan.objective, plan.solverOptions)
|
|
82
|
+
click.echo(f"Case status: {plan.caseStatus}")
|
|
83
|
+
if plan.caseStatus == "solved":
|
|
84
|
+
output_filename = filename.with_name(filename.stem + "_results.xlsx")
|
|
85
|
+
plan.saveWorkbook(basename=output_filename, overwrite=True, with_config=with_config)
|
|
86
|
+
click.echo(f"Results saved to: {output_filename}")
|