ProfRick commited on
Commit
0a08613
·
verified ·
1 Parent(s): 22f6941

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +126 -53
app.py CHANGED
@@ -1,5 +1,4 @@
1
  import streamlit as st
2
- st.caption("Loaded: app.py (root)") # or "Loaded: src/streamlit_app.py"
3
  from io import BytesIO
4
  from datetime import datetime
5
 
@@ -114,7 +113,7 @@ SCENARIOS = {
114
  "control_options": ["Kidney (local tubular control)", "Pancreas", "Medulla"],
115
  "control_correct": "Kidney (local tubular control)",
116
  "effector_options":[
117
- "Glucose excretion into the urine increases", # correct
118
  "Renal glucose reabsorption increases",
119
  "Insulin secretion increases"
120
  ],
@@ -217,10 +216,35 @@ def init_state():
217
  def loop():
218
  return SCENARIOS[st.session_state.scenario][st.session_state.loop_idx]
219
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  def reset_loop():
221
  st.session_state.assign = {"sensor": None, "control": None, "effector": None}
222
  st.session_state.stage = 1
223
  st.session_state.msgs = {"s1":"", "s2":"", "s3":"", "s4":""}
 
 
 
 
 
 
224
 
225
  def next_loop_or_finish():
226
  st.session_state.progress[st.session_state.scenario][st.session_state.loop_idx] = True
@@ -230,61 +254,107 @@ def next_loop_or_finish():
230
  st.session_state.msgs["s1"] = "Great—now build the second loop for this variable."
231
  else:
232
  st.session_state.msgs["s1"] = "Scenario complete! You can generate a certificate below."
 
233
 
234
  def all_loops_complete_for_current_scenario() -> bool:
235
  return all(st.session_state.progress[st.session_state.scenario])
236
 
237
- def set_scenario(name):
238
- st.session_state.scenario = name
239
- st.session_state.loop_idx = 0
240
- reset_loop()
241
-
242
  # ==============================
243
- # Diagram (tight cubic curves)
244
  # ==============================
245
  def diagram_svg(sensor_txt, control_txt, effector_txt):
246
- sx, sy, sw, sh = 90, 260, 240, 56 # sensor (left)
247
- cx, cy, cw, ch = 410, 60, 220, 56 # control (top)
248
- ex, ey, ew, eh = 740, 320, 260, 56 # effector (right)
249
-
250
- sR = (sx+sw, sy+sh/2) # sensor right-mid
251
- cL = (cx, cy+ch/2) # control left-mid
252
- cR = (cx+cw, cy+ch/2) # control right-mid
253
- eL = (ex, ey+eh/2) # effector left-mid
254
- eB = (ex+ew/2, ey+eh) # effector bottom-mid
255
- sB = (sx+sw/4, sy+sh) # near sensor bottom
256
-
257
- # tighter control points
258
- sc_c1 = (sR[0] + 80, sR[1] - 40)
259
- sc_c2 = (cL[0] - 80, cL[1] + 40)
260
- ce_c1 = (cR[0] + 80, cR[1] - 40)
261
- ce_c2 = (eL[0] - 80, eL[1] + 40)
262
- es_c1 = (eB[0] - 80, eB[1] + 60)
263
- es_c2 = (sB[0] + 100, sB[1] + 30)
264
-
265
- path_sc = f"M {sR[0]} {sR[1]} C {sc_c1[0]} {sc_c1[1]}, {sc_c2[0]} {sc_c2[1]}, {cL[0]} {cL[1]}"
266
- path_ce = f"M {cR[0]} {cR[1]} C {ce_c1[0]} {ce_c1[1]}, {ce_c2[0]} {ce_c2[1]}, {eL[0]} {eL[1]}"
267
- path_es = f"M {eB[0]} {eB[1]} C {es_c1[0]} {es_c1[1]}, {es_c2[0]} {es_c2[1]}, {sB[0]} {sB[1]}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
 
269
  svg = f"""
270
  <div style="position:relative;width:100%;background:#f6f5ff;border:1px solid #e3e3f8;border-radius:16px;overflow:hidden;">
271
- <svg viewBox="0 0 1000 600" style="width:100%;height:auto;display:block">
272
  <defs>
273
- <marker id="arrow" markerWidth="10" markerHeight="10" refX="6" refY="3" orient="auto" markerUnits="strokeWidth">
274
- <path d="M0,0 L0,6 L9,3 z" fill="#4b2bb3"/>
275
  </marker>
276
  </defs>
277
- <path d="{path_sc}" fill="none" stroke="#4b2bb3" stroke-width="6" marker-end="url(#arrow)"/>
278
- <path d="{path_ce}" fill="none" stroke="#4b2bb3" stroke-width="6" marker-end="url(#arrow)"/>
279
- <path d="{path_es}" fill="none" stroke="#4b2bb3" stroke-width="6" marker-end="url(#arrow)"/>
280
- <rect x="330" y="520" width="340" height="26" rx="6" fill="none" stroke="#4b2bb3" stroke-width="6"/>
281
- <polygon points="500,548 540,548 520,590" fill="#4b2bb3"/>
282
- <text x="355" y="538" font-size="18" fill="#4b2bb3">Imbalance</text>
283
- <text x="610" y="538" font-size="18" fill="#4b2bb3">Imbalance</text>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
  <rect x="{sx}" y="{sy}" width="{sw}" height="{sh}" rx="10" fill="#e9ecff" stroke="#6b57e5"/>
285
  <text x="{sx+sw/2}" y="{sy+sh/2+6}" font-size="16" text-anchor="middle" fill="#1f1d2e">{sensor_txt}</text>
 
286
  <rect x="{cx}" y="{cy}" width="{cw}" height="{ch}" rx="10" fill="#e9ecff" stroke="#6b57e5"/>
287
  <text x="{cx+cw/2}" y="{cy+ch/2+6}" font-size="16" text-anchor="middle" fill="#1f1d2e">{control_txt}</text>
 
288
  <rect x="{ex}" y="{ey}" width="{ew}" height="{eh}" rx="10" fill="#e9ecff" stroke="#6b57e5"/>
289
  <text x="{ex+ew/2}" y="{ey+eh/2+6}" font-size="16" text-anchor="middle" fill="#1f1d2e">{effector_txt}</text>
290
  </svg>
@@ -341,17 +411,20 @@ def init_and_render():
341
 
342
  st.title("🧩 Interdependence Concept Game")
343
 
344
- # Scenario + reset
345
- col1, col2 = st.columns([2,1])
346
- with col1:
347
- scenario_list = list(SCENARIOS.keys())
348
- idx = scenario_list.index(st.session_state.scenario)
349
- chosen = st.selectbox("Scenario", scenario_list, index=idx)
350
- if chosen != st.session_state.scenario:
351
- set_scenario(chosen)
352
- with col2:
 
 
353
  if st.button("Reset Loop"):
354
  reset_loop()
 
355
 
356
  # Stage 1
357
  current_loop = loop()
@@ -397,7 +470,7 @@ def init_and_render():
397
 
398
  # Stage 2 — Sensor → Control
399
  if st.session_state.stage >= 2:
400
- st.subheader("Stage 2 · Zoom: Sensor → Control Center")
401
  st.markdown(current_loop["stage2_desc"])
402
  sig = st.radio("Signaling", ["Autocrine","Paracrine","Endocrine"], horizontal=True, key="st2_sig")
403
  rec = st.radio("Receptor", ["Cell membrane","Inside the cell"], horizontal=True, key="st2_rec")
@@ -415,9 +488,9 @@ def init_and_render():
415
  st.session_state.msgs["s2"] = msg
416
  st.info(st.session_state.msgs["s2"])
417
 
418
- # Stage 3 — Control → Effector
419
  if st.session_state.stage >= 3:
420
- st.subheader("Stage 3 · Zoom: Control Center → Effectors")
421
  st.markdown(current_loop["stage3_desc"])
422
  sig3 = st.radio("Signaling", ["Autocrine","Paracrine","Endocrine"], horizontal=True, key="st3_sig")
423
  rec3 = st.radio("Receptor", ["Cell membrane","Inside the cell"], horizontal=True, key="st3_rec")
@@ -435,7 +508,7 @@ def init_and_render():
435
  st.session_state.msgs["s3"] = msg
436
  st.info(st.session_state.msgs["s3"])
437
 
438
- # Stage 4 — Outcome
439
  if st.session_state.stage >= 4:
440
  st.subheader("Stage 4 · Outcome")
441
  st.markdown(f"**{current_loop['outcome_question']}**")
 
1
  import streamlit as st
 
2
  from io import BytesIO
3
  from datetime import datetime
4
 
 
113
  "control_options": ["Kidney (local tubular control)", "Pancreas", "Medulla"],
114
  "control_correct": "Kidney (local tubular control)",
115
  "effector_options":[
116
+ "Glucose excretion into the urine increases", # (we use this phrasing per your request)
117
  "Renal glucose reabsorption increases",
118
  "Insulin secretion increases"
119
  ],
 
216
  def loop():
217
  return SCENARIOS[st.session_state.scenario][st.session_state.loop_idx]
218
 
219
+ # Keys used by widgets that need clearing on reset / loop change
220
+ WIDGET_KEYS = [
221
+ "s1_sensor", "s1_control", "s1_effector",
222
+ "st2_sig", "st2_rec", "st2_grad",
223
+ "st3_sig", "st3_rec", "st3_grad",
224
+ "st4_ans", "scenario_select"
225
+ ]
226
+
227
+ def clear_widget_keys():
228
+ for k in WIDGET_KEYS:
229
+ if k in st.session_state:
230
+ del st.session_state[k]
231
+
232
+ def safe_rerun():
233
+ try:
234
+ st.rerun()
235
+ except Exception:
236
+ st.experimental_rerun()
237
+
238
  def reset_loop():
239
  st.session_state.assign = {"sensor": None, "control": None, "effector": None}
240
  st.session_state.stage = 1
241
  st.session_state.msgs = {"s1":"", "s2":"", "s3":"", "s4":""}
242
+ clear_widget_keys()
243
+
244
+ def set_scenario(name):
245
+ st.session_state.scenario = name
246
+ st.session_state.loop_idx = 0
247
+ reset_loop()
248
 
249
  def next_loop_or_finish():
250
  st.session_state.progress[st.session_state.scenario][st.session_state.loop_idx] = True
 
254
  st.session_state.msgs["s1"] = "Great—now build the second loop for this variable."
255
  else:
256
  st.session_state.msgs["s1"] = "Scenario complete! You can generate a certificate below."
257
+ safe_rerun() # immediately show the next loop or the certificate UI
258
 
259
  def all_loops_complete_for_current_scenario() -> bool:
260
  return all(st.session_state.progress[st.session_state.scenario])
261
 
 
 
 
 
 
262
  # ==============================
263
+ # Diagram (FOUR arrows)
264
  # ==============================
265
  def diagram_svg(sensor_txt, control_txt, effector_txt):
266
+ # Box geometry
267
+ sx, sy, sw, sh = 90, 260, 240, 56 # Sensor (left)
268
+ cx, cy, cw, ch = 410, 60, 220, 56 # Control (top)
269
+ ex, ey, ew, eh = 740, 320, 260, 56 # Effector (right)
270
+
271
+ # Anchors
272
+ sL = (sx, sy + sh/2) # sensor LEFT-mid
273
+ sR = (sx + sw, sy + sh/2) # sensor RIGHT-mid
274
+ cL = (cx, cy + ch/2) # control LEFT-mid
275
+ cR = (cx + cw, cy + ch/2) # control RIGHT-mid
276
+ cB = (cx + cw/2, cy + ch) # control BOTTOM-mid
277
+ eL = (ex, ey + eh/2) # effector LEFT-mid
278
+ eB = (ex + ew/2, ey + eh) # effector BOTTOM-mid
279
+
280
+ # Baseline
281
+ base_x, base_y, base_w, base_h = 330, 508, 360, 26
282
+ base_mid_x = base_x + base_w/2
283
+ base_imb_anchor = (base_x + 20, base_y + base_h/2) # arrow #1 start
284
+ base_bal_anchor = (base_x + base_w - 20, base_y + base_h/2) # arrow #4 end
285
+
286
+ # Arrow paths (cubic Béziers)
287
+ # (1) Imbalance -> Sensor (to left side)
288
+ imbs_c1 = (base_imb_anchor[0] + 60, base_imb_anchor[1] - 40)
289
+ imbs_c2 = (sL[0] - 80, sL[1] + 20)
290
+ path_imb_to_s = (
291
+ f"M {base_imb_anchor[0]} {base_imb_anchor[1]} "
292
+ f"C {imbs_c1[0]} {imbs_c1[1]}, {imbs_c2[0]} {imbs_c2[1]}, {sL[0]} {sL[1]}"
293
+ )
294
+
295
+ # (2) Sensor -> Control (sensor RIGHT to control BOTTOM)
296
+ sc_c1 = (sR[0] + 70, sR[1] - 100)
297
+ sc_c2 = (cB[0] - 70, cB[1] - 100)
298
+ path_s_to_c = (
299
+ f"M {sR[0]} {sR[1]} "
300
+ f"C {sc_c1[0]} {sc_c1[1]}, {sc_c2[0]} {sc_c2[1]}, {cB[0]} {cB[1]}"
301
+ )
302
+
303
+ # (3) Control -> Effector (control RIGHT to effector LEFT)
304
+ ce_c1 = (cR[0] + 80, cR[1] + 40)
305
+ ce_c2 = (eL[0] - 80, eL[1] - 40)
306
+ path_c_to_e = (
307
+ f"M {cR[0]} {cR[1]} "
308
+ f"C {ce_c1[0]} {ce_c1[1]}, {ce_c2[0]} {ce_c2[1]}, {eL[0]} {eL[1]}"
309
+ )
310
+
311
+ # (4) Effector -> Balance (effector BOTTOM to baseline RIGHT)
312
+ eb_c1 = (eB[0] - 120, eB[1] + 20)
313
+ eb_c2 = (base_bal_anchor[0] - 80, base_bal_anchor[1] - 20)
314
+ path_e_to_bal = (
315
+ f"M {eB[0]} {eB[1]} "
316
+ f"C {eb_c1[0]} {eb_c1[1]}, {eb_c2[0]} {eb_c2[1]}, "
317
+ f"{base_bal_anchor[0]} {base_bal_anchor[1]}"
318
+ )
319
 
320
  svg = f"""
321
  <div style="position:relative;width:100%;background:#f6f5ff;border:1px solid #e3e3f8;border-radius:16px;overflow:hidden;">
322
+ <svg viewBox="0 0 1000 600" style="width:100%;height:auto;display:block" preserveAspectRatio="xMidYMid meet">
323
  <defs>
324
+ <marker id="arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto" markerUnits="strokeWidth">
325
+ <path d="M0,0 L0,8 L8,4 z" fill="#4b2bb3"/>
326
  </marker>
327
  </defs>
328
+
329
+ <!-- Four arrows -->
330
+ <path d="{path_imb_to_s}" fill="none" stroke="#4b2bb3" stroke-width="5"
331
+ stroke-linecap="round" stroke-linejoin="round" marker-end="url(#arrow)"/>
332
+ <path d="{path_s_to_c}" fill="none" stroke="#4b2bb3" stroke-width="5"
333
+ stroke-linecap="round" stroke-linejoin="round" marker-end="url(#arrow)"/>
334
+ <path d="{path_c_to_e}" fill="none" stroke="#4b2bb3" stroke-width="5"
335
+ stroke-linecap="round" stroke-linejoin="round" marker-end="url(#arrow)"/>
336
+ <path d="{path_e_to_bal}" fill="none" stroke="#4b2bb3" stroke-width="5"
337
+ stroke-linecap="round" stroke-linejoin="round" marker-end="url(#arrow)"/>
338
+
339
+ <!-- Baseline -->
340
+ <rect x="{base_x}" y="{base_y}" width="{base_w}" height="{base_h}" rx="6"
341
+ fill="none" stroke="#4b2bb3" stroke-width="5"/>
342
+ <polygon points="{base_mid_x - 20},{base_y + base_h + 2}
343
+ {base_mid_x + 20},{base_y + base_h + 2}
344
+ {base_mid_x},{base_y + base_h + 44}"
345
+ fill="#4b2bb3"/>
346
+ <text x="{base_x + 70}" y="{base_y + base_h - 8}" font-size="16"
347
+ text-anchor="middle" fill="#4b2bb3">Imbalance</text>
348
+ <text x="{base_x + base_w - 70}" y="{base_y + base_h - 8}" font-size="16"
349
+ text-anchor="middle" fill="#4b2bb3">Balance</text>
350
+
351
+ <!-- Boxes -->
352
  <rect x="{sx}" y="{sy}" width="{sw}" height="{sh}" rx="10" fill="#e9ecff" stroke="#6b57e5"/>
353
  <text x="{sx+sw/2}" y="{sy+sh/2+6}" font-size="16" text-anchor="middle" fill="#1f1d2e">{sensor_txt}</text>
354
+
355
  <rect x="{cx}" y="{cy}" width="{cw}" height="{ch}" rx="10" fill="#e9ecff" stroke="#6b57e5"/>
356
  <text x="{cx+cw/2}" y="{cy+ch/2+6}" font-size="16" text-anchor="middle" fill="#1f1d2e">{control_txt}</text>
357
+
358
  <rect x="{ex}" y="{ey}" width="{ew}" height="{eh}" rx="10" fill="#e9ecff" stroke="#6b57e5"/>
359
  <text x="{ex+ew/2}" y="{ey+eh/2+6}" font-size="16" text-anchor="middle" fill="#1f1d2e">{effector_txt}</text>
360
  </svg>
 
411
 
412
  st.title("🧩 Interdependence Concept Game")
413
 
414
+ # Scenario select with on_change that truly switches
415
+ scenario_list = list(SCENARIOS.keys())
416
+ def on_change_scenario():
417
+ set_scenario(st.session_state["scenario_select"])
418
+ safe_rerun()
419
+
420
+ idx = scenario_list.index(st.session_state.scenario)
421
+ top_col1, top_col2 = st.columns([2,1])
422
+ with top_col1:
423
+ st.selectbox("Scenario", scenario_list, index=idx, key="scenario_select", on_change=on_change_scenario)
424
+ with top_col2:
425
  if st.button("Reset Loop"):
426
  reset_loop()
427
+ safe_rerun()
428
 
429
  # Stage 1
430
  current_loop = loop()
 
470
 
471
  # Stage 2 — Sensor → Control
472
  if st.session_state.stage >= 2:
473
+ st.subheader("Stage 2 · Sensor → Control Center")
474
  st.markdown(current_loop["stage2_desc"])
475
  sig = st.radio("Signaling", ["Autocrine","Paracrine","Endocrine"], horizontal=True, key="st2_sig")
476
  rec = st.radio("Receptor", ["Cell membrane","Inside the cell"], horizontal=True, key="st2_rec")
 
488
  st.session_state.msgs["s2"] = msg
489
  st.info(st.session_state.msgs["s2"])
490
 
491
+ # Stage 3 — Control → Effector (title without "Zoom")
492
  if st.session_state.stage >= 3:
493
+ st.subheader("Stage 3 · Control Center → Effectors")
494
  st.markdown(current_loop["stage3_desc"])
495
  sig3 = st.radio("Signaling", ["Autocrine","Paracrine","Endocrine"], horizontal=True, key="st3_sig")
496
  rec3 = st.radio("Receptor", ["Cell membrane","Inside the cell"], horizontal=True, key="st3_rec")
 
508
  st.session_state.msgs["s3"] = msg
509
  st.info(st.session_state.msgs["s3"])
510
 
511
+ # Stage 4 — Outcome (title already fine)
512
  if st.session_state.stage >= 4:
513
  st.subheader("Stage 4 · Outcome")
514
  st.markdown(f"**{current_loop['outcome_question']}**")