Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -18,7 +18,6 @@ st.set_page_config(page_title="Interdependence Concept Game", page_icon="🧬",
|
|
| 18 |
# ==============================
|
| 19 |
SCENARIOS = {
|
| 20 |
"Blood Pressure (Hypotension)": [
|
| 21 |
-
# Loop 1: Baroreflex (nervous)
|
| 22 |
{
|
| 23 |
"loop_name": "Nervous loop (Baroreflex)",
|
| 24 |
"sensor_options": ["Chemoreceptor", "Mechanoreceptor", "Thermoreceptor"],
|
|
@@ -27,19 +26,14 @@ SCENARIOS = {
|
|
| 27 |
"control_correct": "Medulla",
|
| 28 |
"effector_options":["Pancreas", "Skeletal Muscle", "Heart and Blood Vessels"],
|
| 29 |
"effector_correct":"Heart and Blood Vessels",
|
| 30 |
-
# Stage 2: Sensor -> Control
|
| 31 |
"stage2_desc": "Afferent neurons release neurotransmitters (hydrophilic) across a synapse to the control center neuron.",
|
| 32 |
"stage2_sig": "Paracrine",
|
| 33 |
"stage2_rec": "Cell membrane",
|
| 34 |
"stage2_grad": "Concentration",
|
| 35 |
-
"stage2_feedback_wrong": "For synapses, diffusion across a tiny gap is primary (concentration gradient).",
|
| 36 |
-
# Stage 3: Control -> Effector
|
| 37 |
"stage3_desc": "Controller neurons signal effectors via neurotransmitters (hydrophilic) across synapses.",
|
| 38 |
"stage3_sig": "Paracrine",
|
| 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",
|
|
@@ -48,7 +42,6 @@ SCENARIOS = {
|
|
| 48 |
],
|
| 49 |
"outcome_correct": "Cardiac output increases and vasoconstriction increases",
|
| 50 |
},
|
| 51 |
-
# Loop 2: RAAS (renal)
|
| 52 |
{
|
| 53 |
"loop_name": "Renal loop (RAAS)",
|
| 54 |
"sensor_options": ["Juxtaglomerular apparatus (kidney)", "Baroreceptors (carotid sinus)", "Osmoreceptors (hypothalamus)"],
|
|
@@ -61,12 +54,10 @@ SCENARIOS = {
|
|
| 61 |
"stage2_sig": "Endocrine",
|
| 62 |
"stage2_rec": "Cell membrane",
|
| 63 |
"stage2_grad": "Pressure",
|
| 64 |
-
"stage2_feedback_wrong": "Primary long-distance transport in blood is pressure-driven bulk flow; diffusion matters locally.",
|
| 65 |
"stage3_desc": "Aldosterone (lipophilic steroid) travels **in the blood circulation** to tubular cells.",
|
| 66 |
"stage3_sig": "Endocrine",
|
| 67 |
"stage3_rec": "Inside the cell",
|
| 68 |
"stage3_grad": "Pressure",
|
| 69 |
-
"stage3_feedback_wrong": "Again, long-distance delivery in blood is pressure-driven; diffusion is local.",
|
| 70 |
"outcome_question": "What will be the following response?",
|
| 71 |
"outcome_options": [
|
| 72 |
"Sodium & water reabsorption increases",
|
|
@@ -78,7 +69,6 @@ SCENARIOS = {
|
|
| 78 |
],
|
| 79 |
|
| 80 |
"Glucose (Post-meal Hyperglycemia)": [
|
| 81 |
-
# Loop 1: Insulin
|
| 82 |
{
|
| 83 |
"loop_name": "Insulin loop",
|
| 84 |
"sensor_options": ["Pancreatic beta cells", "Pancreatic alpha cells", "Chemoreceptors (carotid)"],
|
|
@@ -91,12 +81,10 @@ SCENARIOS = {
|
|
| 91 |
"stage2_sig": "Endocrine",
|
| 92 |
"stage2_rec": "Cell membrane",
|
| 93 |
"stage2_grad": "Pressure",
|
| 94 |
-
"stage2_feedback_wrong":"For long-distance delivery in blood, the primary transport is pressure-driven bulk flow.",
|
| 95 |
"stage3_desc": "The messenger reaches distant tissues via circulation to increase glucose uptake.",
|
| 96 |
"stage3_sig": "Endocrine",
|
| 97 |
"stage3_rec": "Cell membrane",
|
| 98 |
"stage3_grad": "Pressure",
|
| 99 |
-
"stage3_feedback_wrong":"Primary transport in blood is pressure-driven.",
|
| 100 |
"outcome_question": "What will be the following response?",
|
| 101 |
"outcome_options": [
|
| 102 |
"Cellular glucose uptake increases",
|
|
@@ -105,7 +93,6 @@ SCENARIOS = {
|
|
| 105 |
],
|
| 106 |
"outcome_correct": "Cellular glucose uptake increases",
|
| 107 |
},
|
| 108 |
-
# Loop 2: Kidney overflow/excretion
|
| 109 |
{
|
| 110 |
"loop_name": "Kidney excretion loop (overflow)",
|
| 111 |
"sensor_options": ["Kidney (proximal tubule)", "Pancreatic beta cells", "Baroreceptors (carotid)"],
|
|
@@ -122,12 +109,10 @@ SCENARIOS = {
|
|
| 122 |
"stage2_sig": "Paracrine",
|
| 123 |
"stage2_rec": "Cell membrane",
|
| 124 |
"stage2_grad": "Concentration",
|
| 125 |
-
"stage2_feedback_wrong":"Local paracrine movement is diffusion-dominant (concentration gradient).",
|
| 126 |
"stage3_desc": "Local control continues at transporters within the same tissue.",
|
| 127 |
"stage3_sig": "Paracrine",
|
| 128 |
"stage3_rec": "Cell membrane",
|
| 129 |
"stage3_grad": "Concentration",
|
| 130 |
-
"stage3_feedback_wrong":"Local paracrine movement is diffusion-dominant.",
|
| 131 |
"outcome_question": "What will be the following response?",
|
| 132 |
"outcome_options": [
|
| 133 |
"Glucose excretion into the urine increases",
|
|
@@ -139,7 +124,6 @@ SCENARIOS = {
|
|
| 139 |
],
|
| 140 |
|
| 141 |
"Temperature (Hypothermia)": [
|
| 142 |
-
# Loop 1: Shivering (somatic motor)
|
| 143 |
{
|
| 144 |
"loop_name": "Shivering loop (somatic motor)",
|
| 145 |
"sensor_options": ["Thermoreceptor", "Mechanoreceptor", "Chemoreceptor"],
|
|
@@ -152,12 +136,10 @@ SCENARIOS = {
|
|
| 152 |
"stage2_sig": "Paracrine",
|
| 153 |
"stage2_rec": "Cell membrane",
|
| 154 |
"stage2_grad": "Concentration",
|
| 155 |
-
"stage2_feedback_wrong":"Synaptic transmission uses diffusion across a tiny space (concentration gradient).",
|
| 156 |
"stage3_desc": "Motor pathways direct skeletal muscle via neurotransmitters across synapses.",
|
| 157 |
"stage3_sig": "Paracrine",
|
| 158 |
"stage3_rec": "Cell membrane",
|
| 159 |
"stage3_grad": "Concentration",
|
| 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",
|
|
@@ -166,7 +148,6 @@ SCENARIOS = {
|
|
| 166 |
],
|
| 167 |
"outcome_correct": "Muscle contraction",
|
| 168 |
},
|
| 169 |
-
# Loop 2: Skin vessels
|
| 170 |
{
|
| 171 |
"loop_name": "Skin vessel loop (vasoconstriction/vasodilation)",
|
| 172 |
"sensor_options": ["Thermoreceptor", "Mechanoreceptor", "Chemoreceptor"],
|
|
@@ -179,12 +160,10 @@ SCENARIOS = {
|
|
| 179 |
"stage2_sig": "Paracrine",
|
| 180 |
"stage2_rec": "Cell membrane",
|
| 181 |
"stage2_grad": "Concentration",
|
| 182 |
-
"stage2_feedback_wrong":"Synaptic diffusion dominates at short range.",
|
| 183 |
"stage3_desc": "Autonomic outputs alter peripheral vessel tone via neurotransmitters (synaptic).",
|
| 184 |
"stage3_sig": "Paracrine",
|
| 185 |
"stage3_rec": "Cell membrane",
|
| 186 |
"stage3_grad": "Concentration",
|
| 187 |
-
"stage3_feedback_wrong":"Local neurotransmitter diffusion across synapses.",
|
| 188 |
"outcome_question": "What will be the following response?",
|
| 189 |
"outcome_options": [
|
| 190 |
"Cutaneous vasoconstriction increases",
|
|
@@ -207,15 +186,15 @@ def init_state():
|
|
| 207 |
if "assign" not in st.session_state:
|
| 208 |
st.session_state.assign = {"sensor": None, "control": None, "effector": None}
|
| 209 |
if "stage" not in st.session_state:
|
| 210 |
-
st.session_state.stage = 1
|
| 211 |
if "stage_token" not in st.session_state:
|
| 212 |
-
st.session_state.stage_token = None
|
| 213 |
if "msgs" not in st.session_state:
|
| 214 |
st.session_state.msgs = {"s1":"", "s2":"", "s3":"", "s4":""}
|
| 215 |
if "progress" not in st.session_state:
|
| 216 |
st.session_state.progress = {sc: [False]*len(SCENARIOS[sc]) for sc in SCENARIOS}
|
| 217 |
if "nonce" not in st.session_state:
|
| 218 |
-
st.session_state.nonce = 0
|
| 219 |
|
| 220 |
def current_loop():
|
| 221 |
return SCENARIOS[st.session_state.scenario][st.session_state.loop_idx]
|
|
@@ -237,7 +216,7 @@ def reset_loop():
|
|
| 237 |
st.session_state.stage = 1
|
| 238 |
st.session_state.stage_token = None
|
| 239 |
st.session_state.msgs = {"s1":"", "s2":"", "s3":"", "s4":""}
|
| 240 |
-
st.session_state.nonce += 1 # ensures
|
| 241 |
|
| 242 |
def set_scenario(name):
|
| 243 |
st.session_state.scenario = name
|
|
@@ -262,19 +241,16 @@ def all_loops_complete_for_current_scenario() -> bool:
|
|
| 262 |
# Diagram (arrows removed)
|
| 263 |
# ==============================
|
| 264 |
def diagram_html(sensor_txt, control_txt, effector_txt):
|
| 265 |
-
# Box geometry
|
| 266 |
sx, sy, sw, sh = 110, 260, 240, 56 # Sensor (left)
|
| 267 |
cx, cy, cw, ch = 420, 70, 220, 56 # Control (top)
|
| 268 |
ex, ey, ew, eh = 740, 320, 260, 56 # Effector (right)
|
| 269 |
|
| 270 |
-
# Baseline
|
| 271 |
base_x, base_y, base_w, base_h = 330, 520, 360, 26
|
| 272 |
base_mid_x = base_x + base_w/2
|
| 273 |
|
| 274 |
html = f"""
|
| 275 |
<div style="position:relative;width:100%;background:#f6f5ff;border:1px solid #e3e3f8;border-radius:16px;overflow:hidden;">
|
| 276 |
<svg viewBox="0 0 1000 620" style="width:100%;height:auto;display:block" preserveAspectRatio="xMidYMid meet">
|
| 277 |
-
<!-- Baseline -->
|
| 278 |
<rect x="{base_x}" y="{base_y}" width="{base_w}" height="{base_h}" rx="8"
|
| 279 |
fill="none" stroke="#4b2bb3" stroke-width="5"/>
|
| 280 |
<polygon points="{base_mid_x - 20},{base_y + base_h + 2}
|
|
@@ -286,7 +262,6 @@ def diagram_html(sensor_txt, control_txt, effector_txt):
|
|
| 286 |
<text x="{base_x + base_w - 70}" y="{base_y + base_h - 8}" font-size="16"
|
| 287 |
text-anchor="middle" fill="#4b2bb3">Balance</text>
|
| 288 |
|
| 289 |
-
<!-- Boxes (no arrows) -->
|
| 290 |
<rect x="{sx}" y="{sy}" width="{sw}" height="{sh}" rx="12" fill="#e9ecff" stroke="#6b57e5"/>
|
| 291 |
<text x="{sx+sw/2}" y="{sy+sh/2+6}" font-size="16" text-anchor="middle" fill="#1f1d2e">{sensor_txt}</text>
|
| 292 |
|
|
@@ -343,7 +318,6 @@ def init_and_render():
|
|
| 343 |
|
| 344 |
st.title("🧩 Interdependence Concept Game")
|
| 345 |
|
| 346 |
-
# Scenario select (reruns on change)
|
| 347 |
scenario_list = list(SCENARIOS.keys())
|
| 348 |
idx = scenario_list.index(st.session_state.scenario)
|
| 349 |
|
|
@@ -359,25 +333,24 @@ def init_and_render():
|
|
| 359 |
reset_loop()
|
| 360 |
safe_rerun()
|
| 361 |
|
| 362 |
-
# Current loop & gating tokens/keys
|
| 363 |
cloop = current_loop()
|
| 364 |
token = loop_token()
|
| 365 |
ksfx = key_suffix()
|
| 366 |
|
| 367 |
st.subheader(f"Stage 1 · Build the negative feedback loop — **{cloop['loop_name']}**")
|
| 368 |
|
| 369 |
-
# Diagram render (no arrows)
|
| 370 |
labels = st.session_state.assign
|
| 371 |
html = diagram_html(
|
| 372 |
labels['sensor'] or "Sensor",
|
| 373 |
labels['control'] or "Control Center",
|
| 374 |
labels['effector'] or "Effector(s)"
|
| 375 |
)
|
| 376 |
-
components.html(html, height=660, scrolling=False
|
| 377 |
|
| 378 |
st.markdown("---")
|
| 379 |
|
| 380 |
-
# ------- Stage 1
|
| 381 |
st.write("**Sensor options**")
|
| 382 |
sel_s = st.radio("Sensor", cloop["sensor_options"], index=None, horizontal=True, key=f"s1_sensor_{ksfx}")
|
| 383 |
if sel_s is not None: st.session_state.assign["sensor"] = sel_s
|
|
@@ -392,24 +365,24 @@ def init_and_render():
|
|
| 392 |
|
| 393 |
if st.button("Check Stage 1", key=f"chk1_{ksfx}"):
|
| 394 |
a = st.session_state.assign
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
st.session_state.msgs["s1"] = "Place all three answers."
|
| 398 |
else:
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
else:
|
| 406 |
st.session_state.msgs["s1"] = "Great! Proceed to Stage 2."
|
| 407 |
st.session_state.stage = 2
|
| 408 |
-
st.session_state.stage_token = token
|
| 409 |
safe_rerun()
|
|
|
|
|
|
|
| 410 |
st.info(st.session_state.msgs["s1"])
|
| 411 |
|
| 412 |
-
#
|
| 413 |
def show_stage(n: int) -> bool:
|
| 414 |
return (st.session_state.stage == n) and (st.session_state.stage_token == token if n >= 2 else True)
|
| 415 |
|
|
@@ -422,16 +395,12 @@ def init_and_render():
|
|
| 422 |
grd = st.radio("Gradient (primary transport)", ["Concentration","Electrochemical","Pressure"], index=None, horizontal=True, key=f"st2_grad_{ksfx}")
|
| 423 |
|
| 424 |
if st.button("Check Stage 2", key=f"chk2_{ksfx}"):
|
| 425 |
-
|
| 426 |
-
if ok:
|
| 427 |
st.session_state.msgs["s2"] = "Correct! Continue to Stage 3."
|
| 428 |
st.session_state.stage = 3
|
| 429 |
safe_rerun()
|
| 430 |
else:
|
| 431 |
-
|
| 432 |
-
if "stage2_feedback_wrong" in cloop:
|
| 433 |
-
msg += " " + cloop["stage2_feedback_wrong"]
|
| 434 |
-
st.session_state.msgs["s2"] = msg
|
| 435 |
st.info(st.session_state.msgs["s2"])
|
| 436 |
|
| 437 |
# ------- Stage 3 -------
|
|
@@ -443,16 +412,12 @@ def init_and_render():
|
|
| 443 |
grd3 = st.radio("Gradient (primary transport)", ["Concentration","Electrochemical","Pressure"], index=None, horizontal=True, key=f"st3_grad_{ksfx}")
|
| 444 |
|
| 445 |
if st.button("Check Stage 3", key=f"chk3_{ksfx}"):
|
| 446 |
-
|
| 447 |
-
if ok:
|
| 448 |
st.session_state.msgs["s3"] = "Nice! Final question…"
|
| 449 |
st.session_state.stage = 4
|
| 450 |
safe_rerun()
|
| 451 |
else:
|
| 452 |
-
|
| 453 |
-
if "stage3_feedback_wrong" in cloop:
|
| 454 |
-
msg += " " + cloop["stage3_feedback_wrong"]
|
| 455 |
-
st.session_state.msgs["s3"] = msg
|
| 456 |
st.info(st.session_state.msgs["s3"])
|
| 457 |
|
| 458 |
# ------- Stage 4 -------
|
|
@@ -465,12 +430,12 @@ def init_and_render():
|
|
| 465 |
st.session_state.msgs["s4"] = "✅ Correct."
|
| 466 |
next_loop_or_finish() # resets & advances; only Stage 1 will show next
|
| 467 |
else:
|
| 468 |
-
st.session_state.msgs["s4"] = "
|
| 469 |
st.info(st.session_state.msgs["s4"])
|
| 470 |
|
| 471 |
st.markdown("---")
|
| 472 |
|
| 473 |
-
# Certificate
|
| 474 |
if all_loops_complete_for_current_scenario() and REPORTLAB_AVAILABLE:
|
| 475 |
st.success("Scenario complete. Generate your PDF certificate below.")
|
| 476 |
student_name = st.text_input("Student name for certificate", "")
|
|
|
|
| 18 |
# ==============================
|
| 19 |
SCENARIOS = {
|
| 20 |
"Blood Pressure (Hypotension)": [
|
|
|
|
| 21 |
{
|
| 22 |
"loop_name": "Nervous loop (Baroreflex)",
|
| 23 |
"sensor_options": ["Chemoreceptor", "Mechanoreceptor", "Thermoreceptor"],
|
|
|
|
| 26 |
"control_correct": "Medulla",
|
| 27 |
"effector_options":["Pancreas", "Skeletal Muscle", "Heart and Blood Vessels"],
|
| 28 |
"effector_correct":"Heart and Blood Vessels",
|
|
|
|
| 29 |
"stage2_desc": "Afferent neurons release neurotransmitters (hydrophilic) across a synapse to the control center neuron.",
|
| 30 |
"stage2_sig": "Paracrine",
|
| 31 |
"stage2_rec": "Cell membrane",
|
| 32 |
"stage2_grad": "Concentration",
|
|
|
|
|
|
|
| 33 |
"stage3_desc": "Controller neurons signal effectors via neurotransmitters (hydrophilic) across synapses.",
|
| 34 |
"stage3_sig": "Paracrine",
|
| 35 |
"stage3_rec": "Cell membrane",
|
| 36 |
"stage3_grad": "Concentration",
|
|
|
|
|
|
|
| 37 |
"outcome_question": "What will be the following response?",
|
| 38 |
"outcome_options": [
|
| 39 |
"Cardiac output increases and vasoconstriction increases",
|
|
|
|
| 42 |
],
|
| 43 |
"outcome_correct": "Cardiac output increases and vasoconstriction increases",
|
| 44 |
},
|
|
|
|
| 45 |
{
|
| 46 |
"loop_name": "Renal loop (RAAS)",
|
| 47 |
"sensor_options": ["Juxtaglomerular apparatus (kidney)", "Baroreceptors (carotid sinus)", "Osmoreceptors (hypothalamus)"],
|
|
|
|
| 54 |
"stage2_sig": "Endocrine",
|
| 55 |
"stage2_rec": "Cell membrane",
|
| 56 |
"stage2_grad": "Pressure",
|
|
|
|
| 57 |
"stage3_desc": "Aldosterone (lipophilic steroid) travels **in the blood circulation** to tubular cells.",
|
| 58 |
"stage3_sig": "Endocrine",
|
| 59 |
"stage3_rec": "Inside the cell",
|
| 60 |
"stage3_grad": "Pressure",
|
|
|
|
| 61 |
"outcome_question": "What will be the following response?",
|
| 62 |
"outcome_options": [
|
| 63 |
"Sodium & water reabsorption increases",
|
|
|
|
| 69 |
],
|
| 70 |
|
| 71 |
"Glucose (Post-meal Hyperglycemia)": [
|
|
|
|
| 72 |
{
|
| 73 |
"loop_name": "Insulin loop",
|
| 74 |
"sensor_options": ["Pancreatic beta cells", "Pancreatic alpha cells", "Chemoreceptors (carotid)"],
|
|
|
|
| 81 |
"stage2_sig": "Endocrine",
|
| 82 |
"stage2_rec": "Cell membrane",
|
| 83 |
"stage2_grad": "Pressure",
|
|
|
|
| 84 |
"stage3_desc": "The messenger reaches distant tissues via circulation to increase glucose uptake.",
|
| 85 |
"stage3_sig": "Endocrine",
|
| 86 |
"stage3_rec": "Cell membrane",
|
| 87 |
"stage3_grad": "Pressure",
|
|
|
|
| 88 |
"outcome_question": "What will be the following response?",
|
| 89 |
"outcome_options": [
|
| 90 |
"Cellular glucose uptake increases",
|
|
|
|
| 93 |
],
|
| 94 |
"outcome_correct": "Cellular glucose uptake increases",
|
| 95 |
},
|
|
|
|
| 96 |
{
|
| 97 |
"loop_name": "Kidney excretion loop (overflow)",
|
| 98 |
"sensor_options": ["Kidney (proximal tubule)", "Pancreatic beta cells", "Baroreceptors (carotid)"],
|
|
|
|
| 109 |
"stage2_sig": "Paracrine",
|
| 110 |
"stage2_rec": "Cell membrane",
|
| 111 |
"stage2_grad": "Concentration",
|
|
|
|
| 112 |
"stage3_desc": "Local control continues at transporters within the same tissue.",
|
| 113 |
"stage3_sig": "Paracrine",
|
| 114 |
"stage3_rec": "Cell membrane",
|
| 115 |
"stage3_grad": "Concentration",
|
|
|
|
| 116 |
"outcome_question": "What will be the following response?",
|
| 117 |
"outcome_options": [
|
| 118 |
"Glucose excretion into the urine increases",
|
|
|
|
| 124 |
],
|
| 125 |
|
| 126 |
"Temperature (Hypothermia)": [
|
|
|
|
| 127 |
{
|
| 128 |
"loop_name": "Shivering loop (somatic motor)",
|
| 129 |
"sensor_options": ["Thermoreceptor", "Mechanoreceptor", "Chemoreceptor"],
|
|
|
|
| 136 |
"stage2_sig": "Paracrine",
|
| 137 |
"stage2_rec": "Cell membrane",
|
| 138 |
"stage2_grad": "Concentration",
|
|
|
|
| 139 |
"stage3_desc": "Motor pathways direct skeletal muscle via neurotransmitters across synapses.",
|
| 140 |
"stage3_sig": "Paracrine",
|
| 141 |
"stage3_rec": "Cell membrane",
|
| 142 |
"stage3_grad": "Concentration",
|
|
|
|
| 143 |
"outcome_question": "What will be the following response?",
|
| 144 |
"outcome_options": [
|
| 145 |
"Muscle contraction",
|
|
|
|
| 148 |
],
|
| 149 |
"outcome_correct": "Muscle contraction",
|
| 150 |
},
|
|
|
|
| 151 |
{
|
| 152 |
"loop_name": "Skin vessel loop (vasoconstriction/vasodilation)",
|
| 153 |
"sensor_options": ["Thermoreceptor", "Mechanoreceptor", "Chemoreceptor"],
|
|
|
|
| 160 |
"stage2_sig": "Paracrine",
|
| 161 |
"stage2_rec": "Cell membrane",
|
| 162 |
"stage2_grad": "Concentration",
|
|
|
|
| 163 |
"stage3_desc": "Autonomic outputs alter peripheral vessel tone via neurotransmitters (synaptic).",
|
| 164 |
"stage3_sig": "Paracrine",
|
| 165 |
"stage3_rec": "Cell membrane",
|
| 166 |
"stage3_grad": "Concentration",
|
|
|
|
| 167 |
"outcome_question": "What will be the following response?",
|
| 168 |
"outcome_options": [
|
| 169 |
"Cutaneous vasoconstriction increases",
|
|
|
|
| 186 |
if "assign" not in st.session_state:
|
| 187 |
st.session_state.assign = {"sensor": None, "control": None, "effector": None}
|
| 188 |
if "stage" not in st.session_state:
|
| 189 |
+
st.session_state.stage = 1
|
| 190 |
if "stage_token" not in st.session_state:
|
| 191 |
+
st.session_state.stage_token = None
|
| 192 |
if "msgs" not in st.session_state:
|
| 193 |
st.session_state.msgs = {"s1":"", "s2":"", "s3":"", "s4":""}
|
| 194 |
if "progress" not in st.session_state:
|
| 195 |
st.session_state.progress = {sc: [False]*len(SCENARIOS[sc]) for sc in SCENARIOS}
|
| 196 |
if "nonce" not in st.session_state:
|
| 197 |
+
st.session_state.nonce = 0
|
| 198 |
|
| 199 |
def current_loop():
|
| 200 |
return SCENARIOS[st.session_state.scenario][st.session_state.loop_idx]
|
|
|
|
| 216 |
st.session_state.stage = 1
|
| 217 |
st.session_state.stage_token = None
|
| 218 |
st.session_state.msgs = {"s1":"", "s2":"", "s3":"", "s4":""}
|
| 219 |
+
st.session_state.nonce += 1 # ensures fresh widget keys
|
| 220 |
|
| 221 |
def set_scenario(name):
|
| 222 |
st.session_state.scenario = name
|
|
|
|
| 241 |
# Diagram (arrows removed)
|
| 242 |
# ==============================
|
| 243 |
def diagram_html(sensor_txt, control_txt, effector_txt):
|
|
|
|
| 244 |
sx, sy, sw, sh = 110, 260, 240, 56 # Sensor (left)
|
| 245 |
cx, cy, cw, ch = 420, 70, 220, 56 # Control (top)
|
| 246 |
ex, ey, ew, eh = 740, 320, 260, 56 # Effector (right)
|
| 247 |
|
|
|
|
| 248 |
base_x, base_y, base_w, base_h = 330, 520, 360, 26
|
| 249 |
base_mid_x = base_x + base_w/2
|
| 250 |
|
| 251 |
html = f"""
|
| 252 |
<div style="position:relative;width:100%;background:#f6f5ff;border:1px solid #e3e3f8;border-radius:16px;overflow:hidden;">
|
| 253 |
<svg viewBox="0 0 1000 620" style="width:100%;height:auto;display:block" preserveAspectRatio="xMidYMid meet">
|
|
|
|
| 254 |
<rect x="{base_x}" y="{base_y}" width="{base_w}" height="{base_h}" rx="8"
|
| 255 |
fill="none" stroke="#4b2bb3" stroke-width="5"/>
|
| 256 |
<polygon points="{base_mid_x - 20},{base_y + base_h + 2}
|
|
|
|
| 262 |
<text x="{base_x + base_w - 70}" y="{base_y + base_h - 8}" font-size="16"
|
| 263 |
text-anchor="middle" fill="#4b2bb3">Balance</text>
|
| 264 |
|
|
|
|
| 265 |
<rect x="{sx}" y="{sy}" width="{sw}" height="{sh}" rx="12" fill="#e9ecff" stroke="#6b57e5"/>
|
| 266 |
<text x="{sx+sw/2}" y="{sy+sh/2+6}" font-size="16" text-anchor="middle" fill="#1f1d2e">{sensor_txt}</text>
|
| 267 |
|
|
|
|
| 318 |
|
| 319 |
st.title("🧩 Interdependence Concept Game")
|
| 320 |
|
|
|
|
| 321 |
scenario_list = list(SCENARIOS.keys())
|
| 322 |
idx = scenario_list.index(st.session_state.scenario)
|
| 323 |
|
|
|
|
| 333 |
reset_loop()
|
| 334 |
safe_rerun()
|
| 335 |
|
|
|
|
| 336 |
cloop = current_loop()
|
| 337 |
token = loop_token()
|
| 338 |
ksfx = key_suffix()
|
| 339 |
|
| 340 |
st.subheader(f"Stage 1 · Build the negative feedback loop — **{cloop['loop_name']}**")
|
| 341 |
|
| 342 |
+
# Diagram render (no arrows)
|
| 343 |
labels = st.session_state.assign
|
| 344 |
html = diagram_html(
|
| 345 |
labels['sensor'] or "Sensor",
|
| 346 |
labels['control'] or "Control Center",
|
| 347 |
labels['effector'] or "Effector(s)"
|
| 348 |
)
|
| 349 |
+
components.html(html, height=660, scrolling=False)
|
| 350 |
|
| 351 |
st.markdown("---")
|
| 352 |
|
| 353 |
+
# ------- Stage 1 -------
|
| 354 |
st.write("**Sensor options**")
|
| 355 |
sel_s = st.radio("Sensor", cloop["sensor_options"], index=None, horizontal=True, key=f"s1_sensor_{ksfx}")
|
| 356 |
if sel_s is not None: st.session_state.assign["sensor"] = sel_s
|
|
|
|
| 365 |
|
| 366 |
if st.button("Check Stage 1", key=f"chk1_{ksfx}"):
|
| 367 |
a = st.session_state.assign
|
| 368 |
+
if not all([a["sensor"], a["control"], a["effector"]]):
|
| 369 |
+
st.session_state.msgs["s1"] = "Please complete all answers."
|
|
|
|
| 370 |
else:
|
| 371 |
+
ok = (
|
| 372 |
+
a["sensor"] == cloop["sensor_correct"] and
|
| 373 |
+
a["control"] == cloop["control_correct"] and
|
| 374 |
+
a["effector"]== cloop["effector_correct"]
|
| 375 |
+
)
|
| 376 |
+
if ok:
|
|
|
|
| 377 |
st.session_state.msgs["s1"] = "Great! Proceed to Stage 2."
|
| 378 |
st.session_state.stage = 2
|
| 379 |
+
st.session_state.stage_token = token
|
| 380 |
safe_rerun()
|
| 381 |
+
else:
|
| 382 |
+
st.session_state.msgs["s1"] = "Please recheck your answers."
|
| 383 |
st.info(st.session_state.msgs["s1"])
|
| 384 |
|
| 385 |
+
# Only show one stage at a time, and only for this loop
|
| 386 |
def show_stage(n: int) -> bool:
|
| 387 |
return (st.session_state.stage == n) and (st.session_state.stage_token == token if n >= 2 else True)
|
| 388 |
|
|
|
|
| 395 |
grd = st.radio("Gradient (primary transport)", ["Concentration","Electrochemical","Pressure"], index=None, horizontal=True, key=f"st2_grad_{ksfx}")
|
| 396 |
|
| 397 |
if st.button("Check Stage 2", key=f"chk2_{ksfx}"):
|
| 398 |
+
if sig==cloop["stage2_sig"] and rec==cloop["stage2_rec"] and grd==cloop["stage2_grad"]:
|
|
|
|
| 399 |
st.session_state.msgs["s2"] = "Correct! Continue to Stage 3."
|
| 400 |
st.session_state.stage = 3
|
| 401 |
safe_rerun()
|
| 402 |
else:
|
| 403 |
+
st.session_state.msgs["s2"] = "Please recheck your answers."
|
|
|
|
|
|
|
|
|
|
| 404 |
st.info(st.session_state.msgs["s2"])
|
| 405 |
|
| 406 |
# ------- Stage 3 -------
|
|
|
|
| 412 |
grd3 = st.radio("Gradient (primary transport)", ["Concentration","Electrochemical","Pressure"], index=None, horizontal=True, key=f"st3_grad_{ksfx}")
|
| 413 |
|
| 414 |
if st.button("Check Stage 3", key=f"chk3_{ksfx}"):
|
| 415 |
+
if sig3==cloop["stage3_sig"] and rec3==cloop["stage3_rec"] and grd3==cloop["stage3_grad"]:
|
|
|
|
| 416 |
st.session_state.msgs["s3"] = "Nice! Final question…"
|
| 417 |
st.session_state.stage = 4
|
| 418 |
safe_rerun()
|
| 419 |
else:
|
| 420 |
+
st.session_state.msgs["s3"] = "Please recheck your answers."
|
|
|
|
|
|
|
|
|
|
| 421 |
st.info(st.session_state.msgs["s3"])
|
| 422 |
|
| 423 |
# ------- Stage 4 -------
|
|
|
|
| 430 |
st.session_state.msgs["s4"] = "✅ Correct."
|
| 431 |
next_loop_or_finish() # resets & advances; only Stage 1 will show next
|
| 432 |
else:
|
| 433 |
+
st.session_state.msgs["s4"] = "Please recheck your answers."
|
| 434 |
st.info(st.session_state.msgs["s4"])
|
| 435 |
|
| 436 |
st.markdown("---")
|
| 437 |
|
| 438 |
+
# Certificate
|
| 439 |
if all_loops_complete_for_current_scenario() and REPORTLAB_AVAILABLE:
|
| 440 |
st.success("Scenario complete. Generate your PDF certificate below.")
|
| 441 |
student_name = st.text_input("Student name for certificate", "")
|