From 27e185c2a6f95555c3187dbc4ec7a6fb46593764 Mon Sep 17 00:00:00 2001 From: elpatron Date: Sun, 24 Aug 2025 19:44:24 +0200 Subject: [PATCH] Fix Markov simulation chart display issue - Fix circular import issue by moving simulation_manager to app/__init__.py - Enhance get_wealth_evolution to include inequality metrics data - Add get_inequality_evolution method for complete chart data - Update API to return top10_shares and capital_shares in evolution data - Modify onSimulationComplete to fetch and populate charts with complete data - Fix simulation threading to properly mark completion state - Add test script to verify chart data generation The charts now properly display simulation results by fetching complete evolution data when simulation completes, resolving the empty diagrams issue. --- app/__init__.py | 4 ++ app/__pycache__/__init__.cpython-312.pyc | Bin 1373 -> 1471 bytes .../economic_model.cpython-312.pyc | Bin 20498 -> 21560 bytes app/models/economic_model.py | 17 ++++++++ app/routes/__pycache__/api.cpython-312.pyc | Bin 15294 -> 15555 bytes app/routes/__pycache__/main.cpython-312.pyc | Bin 5083 -> 5087 bytes app/routes/api.py | 16 ++++++- app/routes/main.py | 4 +- app/static/js/simulation.js | 40 +++++++++++++++--- test_charts.py | 34 +++++++++++++++ 10 files changed, 106 insertions(+), 9 deletions(-) create mode 100644 test_charts.py diff --git a/app/__init__.py b/app/__init__.py index 73027f1..36f592b 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -8,10 +8,14 @@ updates of the Markov economics simulation. from flask import Flask from flask_socketio import SocketIO from config import config +from app.models import SimulationManager # Initialize SocketIO socketio = SocketIO(cors_allowed_origins="*") +# Global simulation manager instance +simulation_manager = SimulationManager() + def create_app(config_name='development'): """ diff --git a/app/__pycache__/__init__.cpython-312.pyc b/app/__pycache__/__init__.cpython-312.pyc index 89d5ebd5fa7f0b26cb5bd34201ebf95e98c8cb05..fc7b586b08636bdd173f533f89e6d3f3211987fe 100644 GIT binary patch delta 286 zcmcc1wV#{sG%qg~0}#mitm@rXY*CK^6g*AsQmpzJ|k%5UJl_iC34f|?F zkU|EAC=Q?q`)UY>A&N7FD}|$lF^a2_QxG%qg~0}yO>U7fL%c_N<#quWGvT{$L(RF)LhHEgRHL6Qs%QEVxkDeNtb zQS6l*np_im_HRyLEM*en0ZQHCD#|Y{NiEh(EXXY4o&20xhYchQWJYl?krl#MxOE#izOaci@`KF*02`AnivR!s diff --git a/app/models/__pycache__/economic_model.cpython-312.pyc b/app/models/__pycache__/economic_model.cpython-312.pyc index a257bec9bd6d3ac6589828c459ff1402d3c4c8ce..c527b57f9c4ec9c166f8b831a2725b65fd27b01f 100644 GIT binary patch delta 1218 zcmZ{ke`s4(6vy9rc}dr#O|@C`qfOgH?d(fd(_b2~Thw%0W45G_t%5;Fv%Hr!BTL$s ztgFgCLxZxbt20EqP6P5_IuKLGB85w0d0~Y@i6#TKlq$21&NtjdcKKSsx z=bU@adG{UO+y%&GLHlKGt(u|hNB`33Tm38AyVdF{+|zaE2+^uLp_x3b{(!8jO;e&q z)S9Rx%9Y3px@j)hASgw>Xt?KNE57a2XA~MlF3Jjes!f7nnh7=vM#?6kMKFm5deRW( zgvM!Q&|K22&_pAfE80wT%ke6{wOGG5l1wD!faYNfNF?5F)~H69@)PCA8|^oVq-Y?& zwCi9SS#3AfDo;X&%_uTlMoIlgc}rV_1t!9tgqIi|Ci7}bWetdFODrLd9}CB0>EmR9 z4^EB560uNnLX<-3*imt(C7m4W>I|hu!;*NvIT9X=rNi<6I{163d8Oo}cO^Z7;1M>2 zoq|_MdekQn^@t2bu?9=+snkjpwf`!SN~vf}xoydS;l>d$9r~yFkT{WyA4|uQiAg&d zZ_~B>pSv0uCUuFd*&3?mrL(R=jg@?BGm{7G%~Q8r?zP0ni4B7D)H%>Z3ZhgIibY+`U|cHdI>jsQpiXMp^9m3gBhZ z?eKt|oN#;%P7>(cgjYyc*CFVWPj_{*@H)x&>;kuZx91}T%#tPNQ>rAk2NllECpZh*j%~CtxO0MiW^4KXVB@WSzm`2PX&LCbxyota9 z5+UB*Rya)JyEi-bL$+<1TRyl_o$LKnneV&CT^ssBQy6-o5DeWI8ZLAW->`-6Fm=lP z@asde$JNNfX*u%DFu(%&{MjhHD%;(0+|2g};GFzZ|2kMNVUhO`0ScZgkILg{s7EZ4 z>w5{ILQg5U)FqxMD#nsyyyCtSi`1G@P|(3CO^tMhs60DhRqpVd2h9Q|EFE7W zH=i4T>vFR<&C-764jh6-^1}f?Y?3E@@52T1osWM$i{Utik^@mHZlS8k9Zn|W(uZg) zWxI?D%a(67kIIU`e?z&Ci>MH%$cz5-aFP7sAEe*WJGg`D@xf+TBeR1e=6y=A{Kg=E O52z&o%WVc(O7u6(EMHCl delta 643 zcmYk4O=uHQ5XUpSn@Bb!plvtbb&C?%BGfj4mA`z`t1$h<<+Jbsi4_-X2*RYy>$YM%)OQis3eul4OJ{?qbx@f)U})*gqZRI(}i3*2*sW+Eq5z@fpC& zj&;5PxQyGALlD6;$@h@Jfn6KEt9-WILrCMhL^EbnyP=((Pjv|}i%Z?T(8)e_KNX>h zwe()kb#9#AiZ3!z_xNP_Y(M**UK3#spJ%qgD1OMi@>ID)YsLG0s;0@U%-UY%}3JvtFGyJLIItwmS&UaFwfd>14S) zVLzaxbD77aXsqq*X_+sfNnW6sVBwWQ4Hi(<5A)SqdV=eDJr2)sSs#l{NuYjl5r0}< N8$kW)BHHwY{s3%=sxtrp diff --git a/app/models/economic_model.py b/app/models/economic_model.py index ad79339..ce21d21 100644 --- a/app/models/economic_model.py +++ b/app/models/economic_model.py @@ -262,6 +262,23 @@ class EconomicSimulation: return iterations, total_wealth, gini_coefficients + def get_inequality_evolution(self) -> Tuple[List[int], List[float], List[float], List[float]]: + """ + Get inequality evolution data for plotting. + + Returns: + Tuple of (iterations, gini_over_time, top10_share_over_time, capital_share_over_time) + """ + if not self.snapshots: + return [], [], [], [] + + iterations = [s.iteration for s in self.snapshots] + gini_coefficients = [s.gini_coefficient for s in self.snapshots] + top10_shares = [s.wealth_concentration_top10 for s in self.snapshots] + capital_shares = [s.capital_share for s in self.snapshots] + + return iterations, gini_coefficients, top10_shares, capital_shares + def get_agent_wealth_distribution(self) -> Dict[str, List[float]]: """ Get current wealth distribution across all agents. diff --git a/app/routes/__pycache__/api.cpython-312.pyc b/app/routes/__pycache__/api.cpython-312.pyc index 81acbd368d741d16600d93c8fc74446964c01b94..df73f717e1854a1a94eab09df1950a3054bce12f 100644 GIT binary patch delta 1487 zcmY*YZA@EL7(VCT-dp-{Z%a!{KcVf~K}Y$hFiOe7WPCV&Ofu#l*|Mf`@1PA(xV>zG z?J`9jWHB3@WFfj_F3x15Te9rx4-@0UV98LULuVw7i81~#_`_6&$y{RMx$TPeB=$6&XS<12AafP4Rj( zNf{T`9aQBm_{U(foFX{=+lov<2nMaiA_~Dm)lgEk6{|Hyu3{M8(iR;!9WSqBD$&}D zT9knXtA(q?3I%5=Q8;!LThG1hB~|Bcbr0cCR^dJA;vMoH(5r-5=_s7F1ox-wY8u$0~9LN!M=$dIm zn`;67hD++ldfyI>L~~d}Goj7e`aFZ~n2**gG4U*E#jnnGtGXGm#;AJ2^E(#*jV;WFA-Wf6K%&K2o}+==H>ZAF=KPBes6sm@(URq2 zEen=yhgh&eb(Z1jKC9}ollLpbU1oAs4{^R~rf{5bpyM=pH7TUiLt~u`nkdeS@fDO zbS$fr1(BG@3sJd**Bg$F$0CW-ybuv%V|-RuaQlxaFBQL`1AXN48D}tf zAhQ$r$Cj_g&re<+}HtB9)i&US-sUU|A>Ek9n zslFg%ISfkonl~V|fYS-|M{Bk8$$lM#m(W_PkGhP>X-po1izpa6i{qV;iTVJuZ=o+k z4vc>b&C>5Y3DBM#ZJ17itpYLxE~meK9-e&|8JhWnfY5WWaM t?AiwT_88xz9*22bk{V_p7aELa3_WlxK(zQa1I@%wF5d`P_v;NV#RhYuSb8fv|@W4Iq^Stl# z-uHdaxp)4}8*i9@FqsU9tUq$q*E~C~nx}oXQTw1BBL?LeNys-<85%}t(2;kHbi--h zc6G_iJGaOqvB~u+BUvwU)lDH9bZbFK;LWI57xie-?Ka7N0)J-IDoB<*eN$7N)~jiu ziba=8Y98m76*El0JDCf+-;^yH^$L3BC z+eV%TKPx3`6+@R8zy>%ZcH&0ZV{^b|aT|MloP)2#UX!sL(NZfM9PMoez0L13P3h|W zT0O~3@_Voa#%w<-yX|jd3$0svowW0C0g94VGJCE$0*(HKv_KP49t zUdS?89a_){xlVS$k`{Ae_WLkTNCek(aLX4^W?fIR_ygrP zZv-<79HhICDMS7lwo$0?6IO(yyV%c4RlbUXxhf ztNAJdIl5L@4LTcz^TIJC>Bfmw9tJ?(YaFKGl9Z^>MRxtk#fs#^<$1t9PS|Y?>A&ya~#skon zY{S#=bn?7snkIFVt!5||UKuVH3i;u({4Q~vg*!10V$Un9{9 z`K(GN0w&Og4)KO%LtvF_QF9_8_%3yBTg@^wHWrhMXUh3fY;@>k0aE?F2k1G+YP2YwIhEfdUx(TLoj3;f`UiZ^V7w21n7NNs@PI{3 l4zDvRSw9X(b{+5?QWd59NCi}pAqthOzpQ+{%ZxcX>c43wIo$vN diff --git a/app/routes/__pycache__/main.cpython-312.pyc b/app/routes/__pycache__/main.cpython-312.pyc index 3046a8b08113882977280e00f1c1f5414c150623..07e231da7d500dc2399615ee14d1cd92ae6e30ec 100644 GIT binary patch delta 890 zcmZ{izi-n(6vyv1aoyUn<2b+7G_-Zn;Dvz=gbG#JfGi+}Fd$bpq*WY8=VV~&g2d3J z)gdvkvQ!xmcw^unU}CF`L|0hY5DN(IrHy-3$_?K;z4v)%f8V<=t&^7aNz)X<*K&K@ z?H+1XI5=!=?YZDeJ=M>2GC(|KcL|CAmpea((}*iSC*!kZN1s72B=n*{XJ^oRN`k8b zZp^?l39SiqZU(IP0&OOAUZ9K53U-lwgx@6_Vl&+F zM?Gh_6Z9YZPTzUz(dfh6Yk&fKn0^G6=zUs;dWBzlL}-<7w9F+|&dX?xeU&OujebcQ ztQHX|q2QQ=3dz|HMnhU>O}PnGc1OOho{fx<-MQ#MPNiyC{QV^l+i9v}c^1V)$+Nf0 z8x;XtqxeRcuWsIIV(KD^v2`wSs^|6N#jx)T!fr4OZJ9POS3|jsl4n2En^2CfWE#K? z+}A=_!@KDYETeI06^kttY{3!4l1FJkLv*@A z%j}79n|mJ{C!F8UU0s^+n^T@{@KeGb=e8zaY1<{XX5L$lQ_f&8u^vm5x6-3Hl~Xr$ p_QG6mT9NYP*+fp^8DUQVkLsf(dRbfKyh)@|SBU^1l5~Fi6Ftr04 z3*oV`cB!%;d1XUFLP!XS1-U|<85j_Z47{r)$F16v{P+3Z@7}$;=M($bW}g{Th_Fxh zN4q;iHbY+z?e+Z$;>#lp3)@nCr9G4QS@rTGntXeO7bKJT;|@8Ct)DW+|;@XhYCTNkb3M^jS)AJ%g9W_!-(rX){Ato|l~pe^38KUnbsC zTITKCV>%su%xQFaTI3%Qu*3^$LSiM{>kc|@e=q34oCvhU6RJWE$X}fljhD*@n_xw1 zfzhiK5r!(tEXsJ3y!#{=^kJT_$V=4X+wv{kA+fd;9mu^q-KAsw!RXROXcr5tneSqMw;aS diff --git a/app/routes/api.py b/app/routes/api.py index 7e5b270..51e322c 100644 --- a/app/routes/api.py +++ b/app/routes/api.py @@ -13,7 +13,7 @@ from typing import Optional from app import socketio from app.models import SimulationManager, SimulationParameters -from app.routes.main import simulation_manager +from app import simulation_manager api_bp = Blueprint('api', __name__) @@ -95,6 +95,9 @@ def start_simulation(simulation_id: str): if simulation.is_running: return jsonify({'error': 'Simulation already running'}), 400 + # Mark simulation as running + simulation.is_running = True + # Start simulation in background thread def run_simulation_background(): try: @@ -125,6 +128,9 @@ def start_simulation(simulation_id: str): # Small delay to allow real-time visualization time.sleep(0.01) + # Mark as completed + simulation.is_running = False + # Emit completion socketio.emit('simulation_complete', { 'simulation_id': simulation_id, @@ -132,6 +138,7 @@ def start_simulation(simulation_id: str): }, room=f'simulation_{simulation_id}') except Exception as e: + simulation.is_running = False current_app.logger.error(f"Error in simulation thread: {str(e)}") socketio.emit('simulation_error', { 'simulation_id': simulation_id, @@ -254,10 +261,15 @@ def get_simulation_data(simulation_id: str): # Include evolution data if requested if request.args.get('include_evolution', '').lower() == 'true': + # Get complete inequality data + ineq_iterations, gini_over_time, top10_over_time, capital_over_time = simulation.get_inequality_evolution() + response_data['evolution'] = { 'iterations': iterations, 'total_wealth': total_wealth, - 'gini_coefficients': gini_coefficients + 'gini_coefficients': gini_coefficients, + 'top10_shares': top10_over_time, + 'capital_shares': capital_over_time } return jsonify(response_data) diff --git a/app/routes/main.py b/app/routes/main.py index 2fb435e..64ace60 100644 --- a/app/routes/main.py +++ b/app/routes/main.py @@ -10,8 +10,8 @@ from app.models import SimulationManager, SimulationParameters main_bp = Blueprint('main', __name__) -# Global simulation manager instance -simulation_manager = SimulationManager() +# Import global simulation manager instance +from app import simulation_manager @main_bp.route('/') diff --git a/app/static/js/simulation.js b/app/static/js/simulation.js index bbed8b1..a41bdd3 100644 --- a/app/static/js/simulation.js +++ b/app/static/js/simulation.js @@ -651,14 +651,44 @@ function updateMetricsDisplay(data) { /** * Handle simulation completion */ -function onSimulationComplete(data) { +async function onSimulationComplete(data) { currentSimulation.isRunning = false; updateUIState('complete'); - window.MarkovEconomics.utils.showNotification( - `Simulation completed! ${data.total_snapshots} data points collected.`, - 'success' - ); + // Fetch complete simulation data and populate charts + try { + const response = await window.MarkovEconomics.utils.apiRequest( + `/api/simulation/${currentSimulation.id}/data?include_evolution=true` + ); + + if (response.evolution) { + // Clear and populate with complete data + currentSimulation.data.iterations = response.evolution.iterations; + currentSimulation.data.totalWealth = response.evolution.total_wealth; + currentSimulation.data.giniCoefficients = response.evolution.gini_coefficients; + currentSimulation.data.top10Share = response.evolution.top10_shares || []; + currentSimulation.data.capitalShare = response.evolution.capital_shares || []; + + // Update charts with complete data + updateCharts(); + + // Update final metrics + if (response.latest_snapshot) { + updateMetricsDisplay(response.latest_snapshot); + } + } + + window.MarkovEconomics.utils.showNotification( + `Simulation completed! ${data.total_snapshots} data points collected.`, + 'success' + ); + } catch (error) { + console.error('Failed to fetch simulation data:', error); + window.MarkovEconomics.utils.showNotification( + 'Simulation completed but failed to load results', + 'warning' + ); + } } /** diff --git a/test_charts.py b/test_charts.py new file mode 100644 index 0000000..219c008 --- /dev/null +++ b/test_charts.py @@ -0,0 +1,34 @@ +import requests +import json +import time + +# Test simulation workflow +data = {'r_rate': 0.05, 'g_rate': 0.03, 'num_agents': 50, 'iterations': 200} + +# Create simulation +create_resp = requests.post('http://localhost:5000/api/simulation', json=data) +sim_id = create_resp.json()['simulation_id'] +print('Created simulation:', sim_id) + +# Start simulation +start_resp = requests.post(f'http://localhost:5000/api/simulation/{sim_id}/start') +print('Started simulation:', start_resp.json()['status']) + +# Wait for completion +time.sleep(3) + +# Get evolution data +data_resp = requests.get(f'http://localhost:5000/api/simulation/{sim_id}/data?include_evolution=true') +result = data_resp.json() + +print('\nFinal metrics:') +print(f' Iterations: {len(result["evolution"]["iterations"])}') +print(f' Final Gini: {result["evolution"]["gini_coefficients"][-1]:.3f}') +print(f' Final Total Wealth: ${result["evolution"]["total_wealth"][-1]:.2f}') +print(f' Top 10% data points: {len(result["evolution"].get("top10_shares", []))}') +print(f' Capital share data points: {len(result["evolution"].get("capital_shares", []))}') + +if len(result["evolution"]["gini_coefficients"]) > 0: + print('\n✅ Charts should now be populated with data!') +else: + print('\n❌ No data available for charts') \ No newline at end of file