Skip to content

save_case writes an asset_allocation the web UI cannot load (TypeError: 'int' object is not subscriptable) #144

Description

@khoguer10

Version: owlplanner 2026.9.15 (also reproduces on the hosted app at owlplanner.streamlit.app)

save_case writes a case file that the web UI cannot load. Uploading the TOML it produces on the Create Case page fails with a redacted TypeError; the traceback ends at ui_bridge.config_to_ui.

Reproduction
No case file needed — save_case produces the offending file itself:

import json, toml
from owlplanner.assistant import tools as owl
from owlplanner.config.ui_bridge import config_to_ui

res = owl.save_case(
names=["Alice", "Bob"], birth_dates=["1963-03-15", "1961-11-02"],
life_expectancy=[90, 87],
taxable=[100_000, 100_000], tax_deferred=[500_000, 400_000], roth=[50_000, 0],
initial_allocation=[60, 40, 0, 0], final_allocation=[60, 40, 0, 0],
output_dir="/tmp/owlrepro", case_name="repro",
)
conf = toml.load(json.loads(res)["toml_file"])
print(conf["asset_allocation"])
config_to_ui(conf)
Output:

{'interpolation_method': 'linear', 'type': 'spouses', 'generic': [[60, 40, 0, 0], [60, 40, 0, 0]]}
TypeError: 'int' object is not subscriptable
Streamlit traceback from the same file uploaded to the hosted app:

File "ui/Create_Case.py", line 100, in render_case_loader
if kz.createCaseFromFile(mystringio):
File "ui/sskeys.py", line 302, in createCaseFromFile
name, dic = owb.createCaseFromFile(strio)
File "ui/owlbridge.py", line 1938, in createCaseFromFile
mydic = config_to_ui(diconf, mylog=mylog)
File "src/owlplanner/config/ui_bridge.py", line 300, in config_to_ui
dic[f"j3_init%{k}
{i}"] = int(g[0][k])
Cause
plan.setAllocationRatios documents two different shapes for generic: individual carries one [initial, final] pair per person (3 levels), while spouses carries a single household pair (2 levels), since assets are coordinated across spouses.

save_case correctly selects type = "spouses" when the allocation is household-wide, and writes the 2-level form. But config_to_ui handles both types in one branch and then indexes both as individual:

if alloc_type == "individual" or alloc_type == "spouses":
generic = aa.get("generic", DEFAULT_GENERIC_ALLOCATION)
g = generic[i] if i < len(generic) else DEFAULT_GENERIC_ALLOCATION[0]
for k in range(4):
dic[f"j3_init%{k}_{i}"] = int(g[0][k])
For spouses, generic[0] is the initial vector [60, 40, 0, 0], so g[0] is the integer 60 and g[0][k] raises.

The solver path is unaffected — owlcli run reads the same file correctly and solves to the expected objective. It is the config → UI direction only.

Possibly related: config/schema.py types the field as generic: Optional[List[List[List[int]]]], which admits only the individual shape, so a valid spouses allocation does not match the declared schema either.

Suggested fix
Branch on the type in config_to_ui, e.g. use g = generic (the household pair) when alloc_type == "spouses" and g = generic[i] when it is individual; and widen the schema annotation to accept both shapes.

Workaround
Rewriting the block to the equivalent individual form — the same pair repeated once per person — loads correctly in the UI and leaves the solver result unchanged:

[asset_allocation]
type = "individual"
generic = [ [ [60,40,0,0], [60,40,0,0] ], [ [60,40,0,0], [60,40,0,0] ] ]

Thanks for Owl — the MILP formulation and compare_to_baseline in particular have been very useful as an independent check on a separately-built planner.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workinggood first issueGood for newcomers

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions