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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +49 -59
app.py CHANGED
@@ -1,4 +1,5 @@
1
  import streamlit as st
 
2
  from io import BytesIO
3
  from datetime import datetime
4
 
@@ -6,7 +7,6 @@ from datetime import datetime
6
  try:
7
  from reportlab.pdfgen import canvas
8
  from reportlab.lib.pagesizes import letter
9
- from reportlab.lib.units import inch
10
  REPORTLAB_AVAILABLE = True
11
  except Exception:
12
  REPORTLAB_AVAILABLE = False
@@ -14,7 +14,7 @@ except Exception:
14
  st.set_page_config(page_title="Interdependence Concept Game", page_icon="🧬", layout="centered")
15
 
16
  # ==============================
17
- # Content (updated per your notes)
18
  # ==============================
19
  SCENARIOS = {
20
  "Blood Pressure (Hypotension)": [
@@ -39,7 +39,7 @@ SCENARIOS = {
39
  "stage3_rec": "Cell membrane",
40
  "stage3_grad": "Concentration",
41
  "stage3_feedback_wrong": "Same synaptic logic: diffusion across a small gap.",
42
- # Stage 4: Outcome (focus on CO + vasoconstriction)
43
  "outcome_question": "What will be the following response?",
44
  "outcome_options": [
45
  "Cardiac output increases and vasoconstriction increases", # correct
@@ -113,7 +113,7 @@ SCENARIOS = {
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
  ],
@@ -160,7 +160,7 @@ SCENARIOS = {
160
  "stage3_feedback_wrong":"Synaptic diffusion across a small gap.",
161
  "outcome_question": "What will be the following response?",
162
  "outcome_options": [
163
- "Muscle contraction", # correct (removed '(shivering)')
164
  "Muscle relaxation",
165
  "Cutaneous vasodilation"
166
  ],
@@ -216,19 +216,6 @@ def init_state():
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()
@@ -239,7 +226,8 @@ 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
@@ -254,13 +242,17 @@ def next_loop_or_finish():
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
@@ -271,7 +263,6 @@ def diagram_svg(sensor_txt, control_txt, effector_txt):
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
@@ -363,43 +354,37 @@ def diagram_svg(sensor_txt, control_txt, effector_txt):
363
  return svg
364
 
365
  # ==============================
366
- # Certificate generation
367
  # ==============================
368
  def generate_certificate_pdf(student_name: str, scenario_name: str, loops_done: list[str]) -> bytes:
369
  buf = BytesIO()
370
  c = canvas.Canvas(buf, pagesize=letter)
371
  W, H = letter
372
 
373
- c.setFillColorRGB(0.29, 0.25, 0.71)
374
  c.setFont("Helvetica-Bold", 28)
375
- c.drawCentredString(W/2, H - 1.5*inch, "Certificate of Completion")
376
 
377
- c.setFillColorRGB(0,0,0)
378
  c.setFont("Helvetica", 13)
379
- c.drawCentredString(W/2, H - 2.0*inch, "This certifies that")
380
-
381
  c.setFont("Helvetica-Bold", 20)
382
- c.drawCentredString(W/2, H - 2.5*inch, student_name if student_name.strip() else "Student")
383
-
384
  c.setFont("Helvetica", 13)
385
- c.drawCentredString(W/2, H - 3.1*inch, "has successfully completed the scenario")
386
-
387
  c.setFont("Helvetica-Bold", 16)
388
- c.drawCentredString(W/2, H - 3.55*inch, scenario_name)
389
 
390
  c.setFont("Helvetica", 12)
391
- y = H - 4.2*inch
392
  c.drawCentredString(W/2, y, "Completed loops:")
393
- y -= 0.3*inch
394
  for lp in loops_done:
395
  c.drawCentredString(W/2, y, f"• {lp}")
396
- y -= 0.25*inch
397
 
398
  c.setFont("Helvetica-Oblique", 11)
399
- c.drawCentredString(W/2, 1.25*inch, f"Issued on {datetime.now().strftime('%Y-%m-%d %H:%M')}")
400
 
401
- c.showPage()
402
- c.save()
403
  buf.seek(0)
404
  return buf.read()
405
 
@@ -411,13 +396,14 @@ def init_and_render():
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)
@@ -426,30 +412,35 @@ def init_and_render():
426
  reset_loop()
427
  safe_rerun()
428
 
429
- # Stage 1
430
  current_loop = loop()
 
 
431
  st.subheader(f"Stage 1 · Build the negative feedback loop — **{current_loop['loop_name']}**")
432
 
 
433
  labels = st.session_state.assign
434
- sensor_label = labels["sensor"] or "Sensor"
435
- control_label = labels["control"] or "Control Center"
436
- eff_label = labels["effector"] or "Effector(s)"
437
- st.markdown(diagram_svg(sensor_label, control_label, eff_label), unsafe_allow_html=True)
 
 
438
 
439
  st.markdown("---")
440
 
441
  # Stage 1 — selections
442
  c = current_loop
443
  st.write("**Sensor options**")
444
- sel_s = st.radio("Sensor", c["sensor_options"], index=None, horizontal=True, key="s1_sensor")
445
  if sel_s is not None: labels["sensor"] = sel_s
446
 
447
  st.write("**Control center options**")
448
- sel_c = st.radio("Control center", c["control_options"], index=None, horizontal=True, key="s1_control")
449
  if sel_c is not None: labels["control"] = sel_c
450
 
451
  st.write("**Effector options**")
452
- sel_e = st.radio("Effector(s)", c["effector_options"], index=None, horizontal=True, key="s1_effector")
453
  if sel_e is not None: labels["effector"] = sel_e
454
 
455
  if st.button("Check Stage 1"):
@@ -472,9 +463,9 @@ def init_and_render():
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")
477
- grd = st.radio("Gradient (primary transport)", ["Concentration","Electrochemical","Pressure"], horizontal=True, key="st2_grad")
478
 
479
  if st.button("Check Stage 2"):
480
  ok = (sig==current_loop["stage2_sig"] and rec==current_loop["stage2_rec"] and grd==current_loop["stage2_grad"])
@@ -488,13 +479,13 @@ def init_and_render():
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")
497
- grd3 = st.radio("Gradient (primary transport)", ["Concentration","Electrochemical","Pressure"], horizontal=True, key="st3_grad")
498
 
499
  if st.button("Check Stage 3"):
500
  ok = (sig3==current_loop["stage3_sig"] and rec3==current_loop["stage3_rec"] and grd3==current_loop["stage3_grad"])
@@ -508,11 +499,11 @@ def init_and_render():
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']}**")
515
- ans = st.radio("Choose one:", current_loop["outcome_options"], index=None, key="st4_ans")
516
  if st.button("Finish Loop"):
517
  if ans == current_loop["outcome_correct"]:
518
  st.session_state.msgs["s4"] = "✅ Correct."
@@ -528,7 +519,6 @@ def init_and_render():
528
  st.success("Scenario complete. Generate your PDF certificate below.")
529
  student_name = st.text_input("Student name for certificate", "")
530
  loops_list = [lp["loop_name"] for lp in SCENARIOS[st.session_state.scenario]]
531
-
532
  if st.button("Generate PDF Certificate"):
533
  pdf_bytes = generate_certificate_pdf(student_name, st.session_state.scenario, loops_list)
534
  st.download_button(
 
1
  import streamlit as st
2
+ import streamlit.components.v1 as components
3
  from io import BytesIO
4
  from datetime import datetime
5
 
 
7
  try:
8
  from reportlab.pdfgen import canvas
9
  from reportlab.lib.pagesizes import letter
 
10
  REPORTLAB_AVAILABLE = True
11
  except Exception:
12
  REPORTLAB_AVAILABLE = False
 
14
  st.set_page_config(page_title="Interdependence Concept Game", page_icon="🧬", layout="centered")
15
 
16
  # ==============================
17
+ # Content
18
  # ==============================
19
  SCENARIOS = {
20
  "Blood Pressure (Hypotension)": [
 
39
  "stage3_rec": "Cell membrane",
40
  "stage3_grad": "Concentration",
41
  "stage3_feedback_wrong": "Same synaptic logic: diffusion across a small gap.",
42
+ # Stage 4: Outcome
43
  "outcome_question": "What will be the following response?",
44
  "outcome_options": [
45
  "Cardiac output increases and vasoconstriction increases", # correct
 
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", # correct
117
  "Renal glucose reabsorption increases",
118
  "Insulin secretion increases"
119
  ],
 
160
  "stage3_feedback_wrong":"Synaptic diffusion across a small gap.",
161
  "outcome_question": "What will be the following response?",
162
  "outcome_options": [
163
+ "Muscle contraction", # correct
164
  "Muscle relaxation",
165
  "Cutaneous vasodilation"
166
  ],
 
216
  def loop():
217
  return SCENARIOS[st.session_state.scenario][st.session_state.loop_idx]
218
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  def safe_rerun():
220
  try:
221
  st.rerun()
 
226
  st.session_state.assign = {"sensor": None, "control": None, "effector": None}
227
  st.session_state.stage = 1
228
  st.session_state.msgs = {"s1":"", "s2":"", "s3":"", "s4":""}
229
+ # Clear any old radio selections by changing keys next render
230
+ st.session_state.get("widget_nonce") # just touch
231
 
232
  def set_scenario(name):
233
  st.session_state.scenario = name
 
242
  st.session_state.msgs["s1"] = "Great—now build the second loop for this variable."
243
  else:
244
  st.session_state.msgs["s1"] = "Scenario complete! You can generate a certificate below."
245
+ safe_rerun()
246
 
247
  def all_loops_complete_for_current_scenario() -> bool:
248
  return all(st.session_state.progress[st.session_state.scenario])
249
 
250
+ # Make a loop-specific key suffix so widgets never collide across loops
251
+ def loop_key_suffix():
252
+ return f"{st.session_state.scenario}_{st.session_state.loop_idx}"
253
+
254
  # ==============================
255
+ # Diagram (FOUR arrows) — rendered via st.components.html
256
  # ==============================
257
  def diagram_svg(sensor_txt, control_txt, effector_txt):
258
  # Box geometry
 
263
  # Anchors
264
  sL = (sx, sy + sh/2) # sensor LEFT-mid
265
  sR = (sx + sw, sy + sh/2) # sensor RIGHT-mid
 
266
  cR = (cx + cw, cy + ch/2) # control RIGHT-mid
267
  cB = (cx + cw/2, cy + ch) # control BOTTOM-mid
268
  eL = (ex, ey + eh/2) # effector LEFT-mid
 
354
  return svg
355
 
356
  # ==============================
357
+ # Certificate
358
  # ==============================
359
  def generate_certificate_pdf(student_name: str, scenario_name: str, loops_done: list[str]) -> bytes:
360
  buf = BytesIO()
361
  c = canvas.Canvas(buf, pagesize=letter)
362
  W, H = letter
363
 
 
364
  c.setFont("Helvetica-Bold", 28)
365
+ c.drawCentredString(W/2, H - 100, "Certificate of Completion")
366
 
 
367
  c.setFont("Helvetica", 13)
368
+ c.drawCentredString(W/2, H - 140, "This certifies that")
 
369
  c.setFont("Helvetica-Bold", 20)
370
+ c.drawCentredString(W/2, H - 170, student_name if student_name.strip() else "Student")
 
371
  c.setFont("Helvetica", 13)
372
+ c.drawCentredString(W/2, H - 200, "has successfully completed the scenario")
 
373
  c.setFont("Helvetica-Bold", 16)
374
+ c.drawCentredString(W/2, H - 225, scenario_name)
375
 
376
  c.setFont("Helvetica", 12)
377
+ y = H - 270
378
  c.drawCentredString(W/2, y, "Completed loops:")
379
+ y -= 18
380
  for lp in loops_done:
381
  c.drawCentredString(W/2, y, f"• {lp}")
382
+ y -= 16
383
 
384
  c.setFont("Helvetica-Oblique", 11)
385
+ c.drawCentredString(W/2, 80, f"Issued on {datetime.now().strftime('%Y-%m-%d %H:%M')}")
386
 
387
+ c.showPage(); c.save()
 
388
  buf.seek(0)
389
  return buf.read()
390
 
 
396
 
397
  st.title("🧩 Interdependence Concept Game")
398
 
399
+ # Scenario select (reruns on change)
400
  scenario_list = list(SCENARIOS.keys())
401
+ idx = scenario_list.index(st.session_state.scenario)
402
+
403
  def on_change_scenario():
404
  set_scenario(st.session_state["scenario_select"])
405
  safe_rerun()
406
 
 
407
  top_col1, top_col2 = st.columns([2,1])
408
  with top_col1:
409
  st.selectbox("Scenario", scenario_list, index=idx, key="scenario_select", on_change=on_change_scenario)
 
412
  reset_loop()
413
  safe_rerun()
414
 
415
+ # Current loop & loop-scope key suffix
416
  current_loop = loop()
417
+ key_sfx = loop_key_suffix()
418
+
419
  st.subheader(f"Stage 1 · Build the negative feedback loop — **{current_loop['loop_name']}**")
420
 
421
+ # Diagram render via components.html (reliable for SVG)
422
  labels = st.session_state.assign
423
+ svg_html = diagram_svg(
424
+ labels['sensor'] or "Sensor",
425
+ labels['control'] or "Control Center",
426
+ labels['effector'] or "Effector(s)"
427
+ )
428
+ components.html(svg_html, height=640, scrolling=False)
429
 
430
  st.markdown("---")
431
 
432
  # Stage 1 — selections
433
  c = current_loop
434
  st.write("**Sensor options**")
435
+ sel_s = st.radio("Sensor", c["sensor_options"], index=None, horizontal=True, key=f"s1_sensor_{key_sfx}")
436
  if sel_s is not None: labels["sensor"] = sel_s
437
 
438
  st.write("**Control center options**")
439
+ sel_c = st.radio("Control center", c["control_options"], index=None, horizontal=True, key=f"s1_control_{key_sfx}")
440
  if sel_c is not None: labels["control"] = sel_c
441
 
442
  st.write("**Effector options**")
443
+ sel_e = st.radio("Effector(s)", c["effector_options"], index=None, horizontal=True, key=f"s1_effector_{key_sfx}")
444
  if sel_e is not None: labels["effector"] = sel_e
445
 
446
  if st.button("Check Stage 1"):
 
463
  if st.session_state.stage >= 2:
464
  st.subheader("Stage 2 · Sensor → Control Center")
465
  st.markdown(current_loop["stage2_desc"])
466
+ sig = st.radio("Signaling", ["Autocrine","Paracrine","Endocrine"], index=None, horizontal=True, key=f"st2_sig_{key_sfx}")
467
+ rec = st.radio("Receptor", ["Cell membrane","Inside the cell"], index=None, horizontal=True, key=f"st2_rec_{key_sfx}")
468
+ grd = st.radio("Gradient (primary transport)", ["Concentration","Electrochemical","Pressure"], index=None, horizontal=True, key=f"st2_grad_{key_sfx}")
469
 
470
  if st.button("Check Stage 2"):
471
  ok = (sig==current_loop["stage2_sig"] and rec==current_loop["stage2_rec"] and grd==current_loop["stage2_grad"])
 
479
  st.session_state.msgs["s2"] = msg
480
  st.info(st.session_state.msgs["s2"])
481
 
482
+ # Stage 3 — Control → Effector
483
  if st.session_state.stage >= 3:
484
  st.subheader("Stage 3 · Control Center → Effectors")
485
  st.markdown(current_loop["stage3_desc"])
486
+ sig3 = st.radio("Signaling", ["Autocrine","Paracrine","Endocrine"], index=None, horizontal=True, key=f"st3_sig_{key_sfx}")
487
+ rec3 = st.radio("Receptor", ["Cell membrane","Inside the cell"], index=None, horizontal=True, key=f"st3_rec_{key_sfx}")
488
+ grd3 = st.radio("Gradient (primary transport)", ["Concentration","Electrochemical","Pressure"], index=None, horizontal=True, key=f"st3_grad_{key_sfx}")
489
 
490
  if st.button("Check Stage 3"):
491
  ok = (sig3==current_loop["stage3_sig"] and rec3==current_loop["stage3_rec"] and grd3==current_loop["stage3_grad"])
 
499
  st.session_state.msgs["s3"] = msg
500
  st.info(st.session_state.msgs["s3"])
501
 
502
+ # Stage 4 — Outcome
503
  if st.session_state.stage >= 4:
504
  st.subheader("Stage 4 · Outcome")
505
  st.markdown(f"**{current_loop['outcome_question']}**")
506
+ ans = st.radio("Choose one:", current_loop["outcome_options"], index=None, key=f"st4_ans_{key_sfx}")
507
  if st.button("Finish Loop"):
508
  if ans == current_loop["outcome_correct"]:
509
  st.session_state.msgs["s4"] = "✅ Correct."
 
519
  st.success("Scenario complete. Generate your PDF certificate below.")
520
  student_name = st.text_input("Student name for certificate", "")
521
  loops_list = [lp["loop_name"] for lp in SCENARIOS[st.session_state.scenario]]
 
522
  if st.button("Generate PDF Certificate"):
523
  pdf_bytes = generate_certificate_pdf(student_name, st.session_state.scenario, loops_list)
524
  st.download_button(