import%20marimo%0A%0A__generated_with%20%3D%20%220.17.6%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Serializing%20and%20Reloading%20Loman%20Computations%0A%0A%20%20%20%20Loman%20computations%20can%20be%20saved%20to%20a%20JSON%20file%20and%20reloaded%20later%20%E2%80%94%20useful%20for%3A%0A%0A%20%20%20%20-%20**Post-mortem%20debugging**%3A%20save%20a%20batch%20run%20in%20full%20detail%20so%20you%20can%20inspect%20every%20intermediate%20value%20if%20something%20goes%20wrong.%0A%20%20%20%20-%20**Checkpoint%20%2F%20resume**%3A%20persist%20a%20partially-completed%20computation%20and%20pick%20up%20where%20you%20left%20off.%0A%20%20%20%20-%20**Reproducibility**%3A%20store%20the%20exact%20inputs%20and%20results%20alongside%20the%20code%20that%20produced%20them.%0A%0A%20%20%20%20This%20notebook%20walks%20through%20the%20key%20features%20of%20%60write_json%60%20%2F%20%60read_json%60%3A%0A%0A%20%20%20%201.%20Basic%20round-trip%0A%20%20%20%202.%20Excluding%20nodes%20with%20%60serialize%3DFalse%60%0A%20%20%20%203.%20Handling%20lambdas%20with%20%60ComputationSerializer(use_dill_for_functions%3DTrue)%60%0A%20%20%20%204.%20Preserving%20%60PINNED%60%20state%0A%20%20%20%205.%20Post-mortem%20inspection%20of%20%60ERROR%60%20nodes%0A%20%20%20%206.%20Pandas%20DataFrames%20as%20node%20values%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%201.%20Basic%20round-trip%0A%0A%20%20%20%20We%20start%20with%20a%20small%20computation%3A%20three%20nodes%20where%20%60c%60%20depends%20on%20%60a%60%20and%20%60b%60.%0A%20%20%20%20All%20three%20nodes%20use%20importable%20module-level%20functions%2C%20so%20they%20serialise%20cleanly.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20io%0A%20%20%20%20import%20json%0A%20%20%20%20import%20math%0A%0A%20%20%20%20from%20loman%20import%20Computation%2C%20ComputationSerializer%2C%20States%0A%0A%20%20%20%20def%20square(x)%3A%0A%20%20%20%20%20%20%20%20return%20x**2%0A%0A%20%20%20%20def%20hypotenuse(a%2C%20b)%3A%0A%20%20%20%20%20%20%20%20return%20math.sqrt(a%20%2B%20b)%0A%0A%20%20%20%20comp%20%3D%20Computation()%0A%20%20%20%20comp.add_node(%22a%22%2C%20value%3D3.0)%0A%20%20%20%20comp.add_node(%22b%22%2C%20value%3D4.0)%0A%20%20%20%20comp.add_node(%22a_sq%22%2C%20square%2C%20kwds%3D%7B%22x%22%3A%20%22a%22%7D)%0A%20%20%20%20comp.add_node(%22b_sq%22%2C%20square%2C%20kwds%3D%7B%22x%22%3A%20%22b%22%7D)%0A%20%20%20%20comp.add_node(%22c%22%2C%20hypotenuse%2C%20kwds%3D%7B%22a%22%3A%20%22a_sq%22%2C%20%22b%22%3A%20%22b_sq%22%7D)%0A%20%20%20%20comp.compute_all()%0A%20%20%20%20comp.to_dict()%0A%20%20%20%20return%20Computation%2C%20ComputationSerializer%2C%20States%2C%20comp%2C%20io%2C%20json%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Save%20to%20an%20in-memory%20buffer%20and%20reload%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Computation%2C%20comp%2C%20io)%3A%0A%20%20%20%20buf%20%3D%20io.StringIO()%0A%20%20%20%20comp.write_json(buf)%0A%20%20%20%20buf.seek(0)%0A%20%20%20%20comp_loaded%20%3D%20Computation.read_json(buf)%0A%20%20%20%20comp_loaded.to_dict()%0A%20%20%20%20return%20(comp_loaded%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20output%20is%20plain%20JSON%20text%20%E2%80%94%20open%20it%20in%20any%20text%20editor%20to%20inspect%20it.%0A%20%20%20%20Here%20is%20what%20the%20file%20actually%20looks%20like%20for%20this%20computation%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20io%2C%20json)%3A%0A%20%20%20%20_buf%20%3D%20io.StringIO()%0A%20%20%20%20comp.write_json(_buf)%0A%20%20%20%20print(json.dumps(json.loads(_buf.getvalue())%2C%20indent%3D2))%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Each%20node%20carries%20its%20%60state%60%2C%20encoded%20%60value%60%2C%20and%20(where%20applicable)%20a%20%60func%60%0A%20%20%20%20reference%20stored%20as%20%60%7B%22type%22%3A%20%22func_ref%22%2C%20%22module%22%3A%20%22...%22%2C%20%22qualname%22%3A%20%22...%22%7D%60.%0A%20%20%20%20Edges%20record%20the%20dependency%20wiring%20including%20parameter%20names.%0A%0A%20%20%20%20The%20reloaded%20computation%20has%20the%20same%20values%20and%20states.%20The%20function%20references%20are%0A%20%20%20%20also%20preserved%20%E2%80%94%20we%20can%20update%20an%20input%20and%20recompute%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20reloaded%20computation%20has%20the%20same%20values%20and%20states.%20The%20function%20references%20are%0A%20%20%20%20also%20preserved%20%E2%80%94%20we%20can%20update%20an%20input%20and%20recompute%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp_loaded)%3A%0A%20%20%20%20comp_loaded.insert(%22a%22%2C%205.0)%0A%20%20%20%20comp_loaded.compute_all()%0A%20%20%20%20comp_loaded.to_dict()%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp_loaded)%3A%0A%20%20%20%20comp_loaded%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%202.%20Excluding%20nodes%20with%20%60serialize%3DFalse%60%0A%0A%20%20%20%20Some%20nodes%20hold%20values%20that%20should%20not%20be%20saved%20%E2%80%94%20database%20connections%2C%20licensed%0A%20%20%20%20data%2C%20or%20objects%20that%20cannot%20be%20serialised.%20Pass%20%60serialize%3DFalse%60%20when%20adding%20the%0A%20%20%20%20node%3A%20it%20will%20be%20stored%20as%20%60UNINITIALIZED%60%20in%20the%20file%20with%20no%20value.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Computation%2C%20io)%3A%0A%20%20%20%20def%20_expensive_db_fetch()%3A%0A%20%20%20%20%20%20%20%20%23%20Pretend%20this%20returns%20data%20from%20a%20live%20database.%0A%20%20%20%20%20%20%20%20return%20%7B%22price%22%3A%2042.0%7D%0A%0A%20%20%20%20comp_skip%20%3D%20Computation()%0A%20%20%20%20comp_skip.add_node(%22db_conn%22%2C%20value%3Dobject()%2C%20serialize%3DFalse)%20%20%23%20not%20saved%0A%20%20%20%20comp_skip.add_node(%22raw_data%22%2C%20value%3D%7B%22price%22%3A%2042.0%7D)%20%20%23%20saved%0A%20%20%20%20comp_skip.add_node(%22result%22%2C%20value%3D42.0%20*%201.1)%20%20%23%20saved%0A%0A%20%20%20%20buf_skip%20%3D%20io.StringIO()%0A%20%20%20%20comp_skip.write_json(buf_skip)%0A%20%20%20%20buf_skip.seek(0)%0A%0A%20%20%20%20comp_skip2%20%3D%20Computation.read_json(buf_skip)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%22db_conn%22%3A%20comp_skip2.state(%22db_conn%22)%2C%20%20%23%20UNINITIALIZED%20%E2%80%94%20not%20restored%0A%20%20%20%20%20%20%20%20%22raw_data%22%3A%20comp_skip2.value(%22raw_data%22)%2C%0A%20%20%20%20%20%20%20%20%22result%22%3A%20comp_skip2.value(%22result%22)%2C%0A%20%20%20%20%7D%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%60db_conn%60%20comes%20back%20as%20%60UNINITIALIZED%60%20%E2%80%94%20exactly%20as%20if%20the%20node%20had%20never%20been%0A%20%20%20%20given%20a%20value%20%E2%80%94%20while%20%60raw_data%60%20and%20%60result%60%20round-trip%20perfectly.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%203.%20Lambdas%20and%20closures%0A%0A%20%20%20%20By%20default%2C%20%60write_json%60%20raises%20a%20%60SerializationError%60%20when%20a%20node's%20function%20is%20a%0A%20%20%20%20lambda%2C%20because%20lambdas%20have%20no%20importable%20module%20path%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Computation%2C%20io)%3A%0A%20%20%20%20from%20loman%20import%20SerializationError%20as%20_SerializationError%0A%0A%20%20%20%20comp_lambda%20%3D%20Computation()%0A%20%20%20%20comp_lambda.add_node(%22x%22%2C%20value%3D5)%0A%20%20%20%20comp_lambda.add_node(%22y%22%2C%20lambda%20x%3A%20x**2)%0A%20%20%20%20comp_lambda.compute_all()%0A%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20comp_lambda.write_json(io.StringIO())%0A%20%20%20%20%20%20%20%20error_msg%20%3D%20None%0A%20%20%20%20except%20_SerializationError%20as%20e%3A%0A%20%20%20%20%20%20%20%20error_msg%20%3D%20str(e)%0A%0A%20%20%20%20error_msg%0A%20%20%20%20return%20(comp_lambda%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20error%20message%20points%20to%20the%20fix%3A%20pass%20%60use_dill_for_functions%3DTrue%60%20to%0A%20%20%20%20%60ComputationSerializer%60.%20This%20encodes%20the%20callable%20as%20a%20base64%20%5Bdill%5D(https%3A%2F%2Fgithub.com%2Fuqfoundation%2Fdill)%0A%20%20%20%20blob%20inside%20the%20JSON%2C%20so%20lambdas%20and%20closures%20%E2%80%94%20including%20ones%20that%20capture%20local%0A%20%20%20%20variables%20%E2%80%94%20round-trip%20intact%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Computation%2C%20ComputationSerializer%2C%20comp_lambda%2C%20io)%3A%0A%20%20%20%20s_dill%20%3D%20ComputationSerializer(use_dill_for_functions%3DTrue)%0A%0A%20%20%20%20buf_lambda%20%3D%20io.StringIO()%0A%20%20%20%20comp_lambda.write_json(buf_lambda%2C%20serializer%3Ds_dill)%0A%20%20%20%20buf_lambda.seek(0)%0A%20%20%20%20comp_lambda2%20%3D%20Computation.read_json(buf_lambda%2C%20serializer%3Ds_dill)%0A%0A%20%20%20%20%23%20Value%20is%20restored%20%E2%80%A6%0A%20%20%20%20print(%22Loaded%20value%20of%20y%3A%22%2C%20comp_lambda2.value(%22y%22))%0A%0A%20%20%20%20%23%20%E2%80%A6%20and%20the%20function%20is%20live%20%E2%80%94%20we%20can%20recompute%20after%20changing%20x.%0A%20%20%20%20comp_lambda2.insert(%22x%22%2C%2012)%0A%20%20%20%20comp_lambda2.compute_all()%0A%20%20%20%20print(%22Recomputed%20y%20after%20x%3D12%3A%22%2C%20comp_lambda2.value(%22y%22))%0A%20%20%20%20return%20(buf_lambda%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20lambda%20is%20stored%20as%20a%20%60%22dill_func%22%60%20object%20in%20the%20JSON.%20The%20%60blob%60%20field%20is%20a%0A%20%20%20%20base64-encoded%20dill%20byte%20string%20%E2%80%94%20here%20is%20what%20the%20%60func%60%20field%20looks%20like%0A%20%20%20%20(blob%20truncated%20for%20readability)%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(buf_lambda%2C%20json)%3A%0A%20%20%20%20_raw%20%3D%20json.loads(buf_lambda.getvalue())%0A%20%20%20%20%23%20show%20only%20the%20lambda%20node's%20func%20field%2C%20blob%20truncated%0A%20%20%20%20_lambda_node%20%3D%20next(n%20for%20n%20in%20_raw%5B%22nodes%22%5D%20if%20n%5B%22func%22%5D%20is%20not%20None%20and%20n%5B%22func%22%5D.get(%22type%22)%20%3D%3D%20%22dill_func%22)%0A%20%20%20%20_func%20%3D%20dict(_lambda_node%5B%22func%22%5D)%0A%20%20%20%20_func%5B%22blob%22%5D%20%3D%20_func%5B%22blob%22%5D%5B%3A40%5D%20%2B%20%22...%22%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%22key%22%3A%20_lambda_node%5B%22key%22%5D%2C%0A%20%20%20%20%20%20%20%20%22func%22%3A%20_func%2C%0A%20%20%20%20%7D%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Closures%20that%20capture%20variables%20from%20an%20enclosing%20scope%20also%20work%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Computation%2C%20ComputationSerializer%2C%20io)%3A%0A%20%20%20%20scale%20%3D%202.5%0A%0A%20%20%20%20def%20scale_up(x)%3A%0A%20%20%20%20%20%20%20%20return%20x%20*%20scale%20%20%23%20captures%20%60scale%60%20from%20the%20enclosing%20scope%0A%0A%20%20%20%20comp_closure%20%3D%20Computation()%0A%20%20%20%20comp_closure.add_node(%22x%22%2C%20value%3D4)%0A%20%20%20%20comp_closure.add_node(%22y%22%2C%20scale_up)%0A%20%20%20%20comp_closure.compute_all()%0A%0A%20%20%20%20s2%20%3D%20ComputationSerializer(use_dill_for_functions%3DTrue)%0A%20%20%20%20buf_closure%20%3D%20io.StringIO()%0A%20%20%20%20comp_closure.write_json(buf_closure%2C%20serializer%3Ds2)%0A%20%20%20%20buf_closure.seek(0)%0A%20%20%20%20comp_closure2%20%3D%20Computation.read_json(buf_closure%2C%20serializer%3Ds2)%0A%0A%20%20%20%20comp_closure2.insert(%22x%22%2C%2010)%0A%20%20%20%20comp_closure2.compute_all()%0A%20%20%20%20print(%22y%20after%20reload%20with%20x%3D10%3A%22%2C%20comp_closure2.value(%22y%22))%20%20%23%2025.0%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%3E%20**Note%3A**%20The%20dill%20blob%20is%20not%20portable%20across%20Python%20versions.%20Prefer%20named%0A%20%20%20%20%3E%20module-level%20functions%20when%20portability%20matters%3B%20use%20%60use_dill_for_functions%3DTrue%60%0A%20%20%20%20%3E%20when%20convenience%20is%20more%20important.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%204.%20Preserving%20PINNED%20state%0A%0A%20%20%20%20A%20%60PINNED%60%20node's%20value%20is%20locked%20%E2%80%94%20downstream%20recalculations%20use%20it%20but%20it%20is%0A%20%20%20%20never%20overwritten%20by%20%60compute_all%60.%20The%20%60PINNED%60%20state%20survives%20a%20round-trip%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Computation%2C%20States%2C%20io)%3A%0A%20%20%20%20comp_pin%20%3D%20Computation()%0A%20%20%20%20comp_pin.add_node(%22rate%22%2C%20value%3D0.05)%0A%20%20%20%20comp_pin.add_node(%22principal%22%2C%20value%3D1000.0)%0A%0A%20%20%20%20def%20calc_interest(rate%2C%20principal)%3A%0A%20%20%20%20%20%20%20%20return%20rate%20*%20principal%0A%0A%20%20%20%20comp_pin.add_node(%22interest%22%2C%20calc_interest)%0A%20%20%20%20comp_pin.compute_all()%0A%0A%20%20%20%20%23%20Pin%20the%20rate%20so%20that%20even%20if%20we%20reload%20and%20change%20inputs%2C%20it%20stays%20fixed.%0A%20%20%20%20comp_pin.pin(%22rate%22)%0A%20%20%20%20print(%22State%20of%20rate%20before%20save%3A%22%2C%20comp_pin.state(%22rate%22))%0A%0A%20%20%20%20buf_pin%20%3D%20io.StringIO()%0A%20%20%20%20comp_pin.write_json(buf_pin)%0A%20%20%20%20buf_pin.seek(0)%0A%20%20%20%20comp_pin2%20%3D%20Computation.read_json(buf_pin)%0A%0A%20%20%20%20print(%22State%20of%20rate%20after%20reload%3A%22%2C%20comp_pin2.state(%22rate%22))%0A%20%20%20%20assert%20comp_pin2.state(%22rate%22)%20%3D%3D%20States.PINNED%0A%20%20%20%20print(%22Value%20of%20rate%20after%20reload%3A%22%2C%20comp_pin2.value(%22rate%22))%0A%20%20%20%20return%20(comp_pin%2C)%0A%0A%0A%40app.cell%0Adef%20_(comp_pin)%3A%0A%20%20%20%20comp_pin%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%205.%20Post-mortem%20inspection%20of%20ERROR%20nodes%0A%0A%20%20%20%20When%20a%20node%20raises%20an%20exception%20during%20%60compute_all%60%2C%20it%20enters%20%60ERROR%60%20state.%0A%20%20%20%20%60write_json%60%20preserves%20the%20exception%20type%2C%20message%2C%20and%20traceback%20as%20strings%20%E2%80%94%0A%20%20%20%20so%20you%20can%20reload%20a%20failed%20computation%20and%20inspect%20what%20went%20wrong%2C%20even%20without%0A%20%20%20%20the%20original%20exception%20class%20available%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Computation%2C%20States%2C%20io)%3A%0A%20%20%20%20def%20bad_calc(x)%3A%0A%20%20%20%20%20%20%20%20msg%20%3D%20f%22unexpected%20value%3A%20%7Bx!r%7D%22%0A%20%20%20%20%20%20%20%20raise%20ValueError(msg)%0A%0A%20%20%20%20comp_err%20%3D%20Computation()%0A%20%20%20%20comp_err.add_node(%22x%22%2C%20value%3D-1)%0A%20%20%20%20comp_err.add_node(%22result%22%2C%20bad_calc)%0A%20%20%20%20comp_err.compute_all()%0A%0A%20%20%20%20print(%22State%20of%20result%3A%22%2C%20comp_err.state(%22result%22))%0A%0A%20%20%20%20buf_err%20%3D%20io.StringIO()%0A%20%20%20%20comp_err.write_json(buf_err)%0A%20%20%20%20buf_err.seek(0)%0A%20%20%20%20comp_err2%20%3D%20Computation.read_json(buf_err)%0A%0A%20%20%20%20print(%22State%20after%20reload%3A%22%2C%20comp_err2.state(%22result%22))%0A%20%20%20%20assert%20comp_err2.state(%22result%22)%20%3D%3D%20States.ERROR%0A%0A%20%20%20%20err_val%20%3D%20comp_err2.value(%22result%22)%0A%20%20%20%20print(%22Exception%20message%3A%22%2C%20err_val.exception)%0A%20%20%20%20print(%22Traceback%20(first%20line)%3A%22%2C%20err_val.traceback.splitlines()%5B0%5D)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%206.%20Pandas%20DataFrames%20as%20node%20values%0A%0A%20%20%20%20DataFrames%20and%20Series%20are%20serialised%20automatically%20using%20a%20JSON%20split-orientation%0A%20%20%20%20format.%20No%20extra%20configuration%20needed%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Computation%2C%20io)%3A%0A%20%20%20%20import%20pandas%20as%20pd%0A%0A%20%20%20%20def%20enrich(raw)%3A%0A%20%20%20%20%20%20%20%20df%20%3D%20raw.copy()%0A%20%20%20%20%20%20%20%20df%5B%22value_eur%22%5D%20%3D%20df%5B%22qty%22%5D%20*%20df%5B%22price_usd%22%5D%20*%200.92%0A%20%20%20%20%20%20%20%20return%20df%0A%0A%20%20%20%20prices%20%3D%20pd.DataFrame(%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22ticker%22%3A%20%5B%22AAPL%22%2C%20%22MSFT%22%2C%20%22GOOG%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22qty%22%3A%20%5B10%2C%2020%2C%205%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22price_usd%22%3A%20%5B182.5%2C%20375.2%2C%20140.8%5D%2C%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20)%0A%0A%20%20%20%20comp_df%20%3D%20Computation()%0A%20%20%20%20comp_df.add_node(%22prices%22%2C%20value%3Dprices)%0A%20%20%20%20comp_df.add_node(%22enriched%22%2C%20enrich)%0A%20%20%20%20comp_df.compute_all()%0A%0A%20%20%20%20buf_df%20%3D%20io.StringIO()%0A%20%20%20%20comp_df.write_json(buf_df)%0A%20%20%20%20buf_df.seek(0)%0A%20%20%20%20comp_df2%20%3D%20Computation.read_json(buf_df)%0A%20%20%20%20comp_df2.value(%22enriched%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Summary%0A%0A%20%20%20%20%7C%20Scenario%20%7C%20How%20%7C%0A%20%20%20%20%7C---%7C---%7C%0A%20%20%20%20%7C%20Basic%20round-trip%20%7C%20%60comp.write_json(path)%60%20%2F%20%60Computation.read_json(path)%60%20%7C%0A%20%20%20%20%7C%20Exclude%20a%20node%20%7C%20%60add_node(...%2C%20serialize%3DFalse)%60%20%7C%0A%20%20%20%20%7C%20Lambda%20%2F%20closure%20%7C%20%60ComputationSerializer(use_dill_for_functions%3DTrue)%60%20%7C%0A%20%20%20%20%7C%20PINNED%20state%20%7C%20Preserved%20automatically%20%7C%0A%20%20%20%20%7C%20ERROR%20state%20%7C%20Exception%20%2B%20traceback%20stored%20as%20strings%20%7C%0A%20%20%20%20%7C%20Pandas%20%2F%20NumPy%20%7C%20Handled%20automatically%20%7C%0A%0A%20%20%20%20The%20JSON%20format%20is%20designed%20for%20short-term%20inspection%20and%20post-mortem%20debugging%2C%0A%20%20%20%20not%20long-term%20archival.%20The%20format%20may%20change%20between%20releases.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20return%20(mo%2C)%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
f132ee7585846fbd2053ffd61b5f3a1e3f12dc4d603230540763f41c778f2537