Tesneem commited on
Commit
698e5ee
Β·
verified Β·
1 Parent(s): 1dac6b0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +136 -24
app.py CHANGED
@@ -178,6 +178,39 @@ def mongo_get_likert_grouped(uri: str, db: str, coll: str, student: str, stage:
178
  return {g: _norm_01(avg.get(g)) for g in SKILL_GROUPS.keys()}
179
  except Exception:
180
  return {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
 
182
  # ------------------- UI -------------------
183
  st.title("πŸ“Š Student Skill Radar")
@@ -199,6 +232,8 @@ with st.sidebar:
199
  overlay_sources = st.toggle("Overlay all sources when '(All)' selected", value=False)
200
  chart_title = st.text_input("Chart title", value="")
201
 
 
 
202
  # start_str = start_dt.strftime("%Y-%m-%d") if isinstance(start_dt, date) else None
203
  # end_str = end_dt.strftime("%Y-%m-%d") if isinstance(end_dt, date) else None
204
 
@@ -494,30 +529,107 @@ def fetch_student_stage_summary(
494
  "notable_quotes": notable_quotes,
495
  }
496
 
497
- # ---------- Render the summary panel dynamically ----------
498
- if mongo_uri and student_choice != "(All)" and source_choice != "(All)":
499
- stage = SOURCE_TO_STAGE.get(source_choice.strip())
500
- if stage:
501
- # set to your actual summaries collection name
502
- summaries_coll_name = "summaries_IFE_2025"
503
- summary = fetch_student_stage_summary(
504
- mongo_uri, db_name, summaries_coll_name, coll_name,
505
- student=student_choice, stage=stage
506
- )
507
- if summary:
508
- st.markdown("---")
509
- st.subheader(f"Summary β€” {student_choice} ({stage.replace('_', ' ').title()})")
510
- c1, c2 = st.columns(2)
511
- with c1:
512
- st.markdown(f"**Most Consistent:** {summary.get('most_consistent') or 'β€”'}")
513
- st.markdown(f"**Most Developed:** {summary.get('most_developed') or 'β€”'}")
514
- with c2:
515
- strengths = summary.get("top_strengths") or []
516
- st.markdown("**Top Strengths:** " + (", ".join(strengths) if strengths else "β€”"))
517
-
518
- st.markdown("**Notable Quotes:**")
519
- for q in (summary.get("notable_quotes") or [])[:3]:
520
- st.markdown(f"> {q}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
521
 
522
 
523
 
 
178
  return {g: _norm_01(avg.get(g)) for g in SKILL_GROUPS.keys()}
179
  except Exception:
180
  return {}
181
+
182
+ # ---- Analyses (Markdown) helpers ----
183
+ ANALYSES_DIR = os.getenv("ANALYSES_DIR", "student_analyses") # folder in your HF Space
184
+
185
+ def _normalize_name(s: str) -> str:
186
+ # Lower, remove non-alphanumerics, collapse spaces/underscores
187
+ import re, unicodedata
188
+ s = unicodedata.normalize("NFKC", s or "").strip().lower()
189
+ s = re.sub(r"[^\w\s]", "", s)
190
+ s = re.sub(r"[\s_]+", " ", s).strip()
191
+ return s
192
+
193
+ @st.cache_data(show_spinner=False)
194
+ def _build_analysis_index(analyses_dir: str) -> dict:
195
+ """Return dict: normalized_name -> file_path for *.md under analyses_dir."""
196
+ import os, glob
197
+ index = {}
198
+ if not os.path.isdir(analyses_dir):
199
+ return index
200
+ for path in glob.glob(os.path.join(analyses_dir, "*.md")):
201
+ base = os.path.splitext(os.path.basename(path))[0] # "Student_Name"
202
+ # accept both "Student Name" and "Student_Name" as same
203
+ norm = _normalize_name(base.replace("_", " "))
204
+ index[norm] = path
205
+ return index
206
+
207
+ @st.cache_data(show_spinner=False)
208
+ def _load_markdown(path: str) -> str:
209
+ try:
210
+ with open(path, "r", encoding="utf-8") as f:
211
+ return f.read()
212
+ except Exception:
213
+ return ""
214
 
215
  # ------------------- UI -------------------
216
  st.title("πŸ“Š Student Skill Radar")
 
232
  overlay_sources = st.toggle("Overlay all sources when '(All)' selected", value=False)
233
  chart_title = st.text_input("Chart title", value="")
234
 
235
+
236
+
237
  # start_str = start_dt.strftime("%Y-%m-%d") if isinstance(start_dt, date) else None
238
  # end_str = end_dt.strftime("%Y-%m-%d") if isinstance(end_dt, date) else None
239
 
 
529
  "notable_quotes": notable_quotes,
530
  }
531
 
532
+ # # ---------- Render the summary panel dynamically ----------
533
+ # if mongo_uri and student_choice != "(All)" and source_choice != "(All)":
534
+ # stage = SOURCE_TO_STAGE.get(source_choice.strip())
535
+ # if stage:
536
+ # # set to your actual summaries collection name
537
+ # summaries_coll_name = "summaries_IFE_2025"
538
+ # summary = fetch_student_stage_summary(
539
+ # mongo_uri, db_name, summaries_coll_name, coll_name,
540
+ # student=student_choice, stage=stage
541
+ # )
542
+ # if summary:
543
+ # st.markdown("---")
544
+ # st.subheader(f"Summary β€” {student_choice} ({stage.replace('_', ' ').title()})")
545
+ # c1, c2 = st.columns(2)
546
+ # with c1:
547
+ # st.markdown(f"**Most Consistent:** {summary.get('most_consistent') or 'β€”'}")
548
+ # st.markdown(f"**Most Developed:** {summary.get('most_developed') or 'β€”'}")
549
+ # with c2:
550
+ # strengths = summary.get("top_strengths") or []
551
+ # st.markdown("**Top Strengths:** " + (", ".join(strengths) if strengths else "β€”"))
552
+
553
+ # st.markdown("**Notable Quotes:**")
554
+ # for q in (summary.get("notable_quotes") or [])[:3]:
555
+ # st.markdown(f"> {q}")
556
+ # ------------------- Output (Tabs) -------------------
557
+ tab_radar, tab_analyses = st.tabs(["πŸ“ˆ Radar", "πŸ“ Analyses"])
558
+
559
+ with tab_radar:
560
+ # plot the radar (uses df_plot, avg_label already computed above)
561
+ fig = plot_radar(df_plot, grouped, chart_title, avg_label=avg_label)
562
+ st.plotly_chart(fig, use_container_width=True)
563
+ st.caption(f"{len(df_final)} line(s) aggregated." if not df_final.empty else "No data.")
564
+
565
+ # Dynamic stage summary panel (only if a specific student + source selected,
566
+ # and only if they actually answered that week)
567
+ if mongo_uri and student_choice != "(All)" and source_choice != "(All)":
568
+ # Map the selected source (e.g., 'onboarding_responses') to stage (e.g., 'onboarding')
569
+ stage = SOURCE_TO_STAGE.get(source_choice.strip())
570
+ if stage:
571
+ # summaries collection name (adjust if needed)
572
+ summaries_coll_name = "summaries_IFE_2025"
573
+
574
+ summary = fetch_student_stage_summary(
575
+ mongo_uri, db_name, summaries_coll_name, coll_name,
576
+ student=student_choice, stage=stage
577
+ )
578
+ if summary:
579
+ st.markdown("---")
580
+ st.subheader(f"Summary β€” {student_choice} ({stage.replace('_', ' ').title()})")
581
+
582
+ c1, c2 = st.columns(2)
583
+ with c1:
584
+ st.markdown(f"**Most Consistent:** {summary.get('most_consistent') or 'β€”'}")
585
+ st.markdown(f"**Most Developed:** {summary.get('most_developed') or 'β€”'}")
586
+ with c2:
587
+ strengths = summary.get("top_strengths") or []
588
+ st.markdown("**Top Strengths:** " + (", ".join(strengths) if strengths else "β€”"))
589
+
590
+ st.markdown("**Notable Quotes:**")
591
+ for q in (summary.get("notable_quotes") or [])[:3]:
592
+ st.markdown(f"> {q}")
593
+
594
+ with tab_analyses:
595
+ st.subheader("Student Analysis (Markdown)")
596
+
597
+ # Use the folder you defined at top (ANALYSES_DIR), or expose it in the sidebar if you prefer.
598
+ idx = _build_analysis_index(ANALYSES_DIR)
599
+
600
+ if student_choice == "(All)":
601
+ st.info("Pick a specific student on the left to view their analysis.")
602
+ # (Optional) show what's available so you can browse:
603
+ if idx:
604
+ st.caption("Available analyses:")
605
+ st.write(", ".join(sorted({name.title() for name in idx.keys()})))
606
+ else:
607
+ # Normalize the selected student name to match filenames
608
+ norm = _normalize_name(student_choice)
609
+ path = idx.get(norm)
610
+
611
+ # If exact match not found, try simple underscore variant
612
+ if not path:
613
+ alt = student_choice.replace(" ", "_")
614
+ path = idx.get(_normalize_name(alt))
615
+
616
+ if path:
617
+ md = _load_markdown(path)
618
+ if md.strip():
619
+ st.markdown(md, unsafe_allow_html=False)
620
+ # Optional download button
621
+ with open(path, "rb") as f:
622
+ st.download_button(
623
+ "Download analysis (.md)", f,
624
+ file_name=os.path.basename(path), mime="text/markdown"
625
+ )
626
+ else:
627
+ st.warning("Analysis file found but empty.")
628
+ else:
629
+ st.warning(f"No analysis found for **{student_choice}** in `{ANALYSES_DIR}` yet.")
630
+ if idx:
631
+ st.caption("Available analyses:")
632
+ st.write(", ".join(sorted({name.title() for name in idx.keys()})))
633
 
634
 
635