Harshal Vhatkar commited on
Commit
08e10fc
·
1 Parent(s): 7ecae14

add resource gen in course generation

Browse files
Files changed (5) hide show
  1. .gitignore +2 -1
  2. app.py +371 -56
  3. create_course2.py +331 -0
  4. pre_class_analytics4.py +526 -0
  5. session_page.py +155 -118
.gitignore CHANGED
@@ -18,4 +18,5 @@ course_creation.py
18
  topics.json
19
  new_analytics.json
20
  new_analytics2.json
21
- pre_class_analytics.py
 
 
18
  topics.json
19
  new_analytics.json
20
  new_analytics2.json
21
+ pre_class_analytics.py
22
+ sample_files/
app.py CHANGED
@@ -15,7 +15,7 @@ from werkzeug.security import generate_password_hash, check_password_hash
15
  import os
16
  from openai import OpenAI
17
  from dotenv import load_dotenv
18
- from create_course import create_course, courses_collection, generate_perplexity_response, PERPLEXITY_API_KEY
19
  import json
20
  from bson import ObjectId
21
  client = OpenAI(api_key=os.getenv("OPENAI_KEY"))
@@ -653,14 +653,206 @@ def register_page():
653
  st.success(f"Analyst registered successfully! Your username is: {username}")
654
 
655
  # Create Course feature
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
656
  def create_course_form(faculty_name, faculty_id):
657
- """Display enhanced form to create a new course with AI-generated content"""
 
658
  st.title("Create New Course")
659
 
660
  if 'course_plan' not in st.session_state:
661
  st.session_state.course_plan = None
662
  if 'edit_mode' not in st.session_state:
663
  st.session_state.edit_mode = False
 
 
 
 
 
 
 
 
 
664
 
665
  # Initial Course Creation Form
666
  if not st.session_state.course_plan:
@@ -669,6 +861,7 @@ def create_course_form(faculty_name, faculty_id):
669
  with col1:
670
  course_name = st.text_input("Course Name", placeholder="e.g., Introduction to Computer Science")
671
  faculty_info = st.text_input("Faculty", value=faculty_name, disabled=True)
 
672
  with col2:
673
  duration_weeks = st.number_input("Duration (weeks)", min_value=1, max_value=16, value=12)
674
  start_date = st.date_input("Start Date")
@@ -676,13 +869,51 @@ def create_course_form(faculty_name, faculty_id):
676
  generate_button = st.form_submit_button("Generate Course Structure", use_container_width=True)
677
 
678
  if generate_button and course_name:
679
- with st.spinner("Generating course structure..."):
680
  try:
681
- course_plan = generate_perplexity_response(PERPLEXITY_API_KEY, course_name)
682
- # print(course_plan)
683
- st.session_state.course_plan = json.loads(course_plan)
 
 
 
 
 
 
 
 
 
 
 
684
  st.session_state.start_date = start_date
685
  st.session_state.duration_weeks = duration_weeks
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
686
  st.rerun()
687
  except Exception as e:
688
  st.error(f"Error generating course structure: {e}")
@@ -693,6 +924,14 @@ def create_course_form(faculty_name, faculty_id):
693
  if not st.session_state.edit_mode:
694
  st.subheader(st.session_state.course_plan['course_title'])
695
  st.write(st.session_state.course_plan['course_description'])
 
 
 
 
 
 
 
 
696
  edit_button = st.button("Edit Course Details", use_container_width=True)
697
  if edit_button:
698
  st.session_state.edit_mode = True
@@ -722,28 +961,45 @@ def create_course_form(faculty_name, faculty_id):
722
  with st.expander(f"📚 Module {module_idx + 1}: {module['module_title']}", expanded=True):
723
  # Edit module title
724
  new_module_title = st.text_input(
725
- f"Module {module_idx + 1} Title",
726
  value=module['module_title'],
727
  key=f"module_{module_idx}"
728
  )
729
  module['module_title'] = new_module_title
730
 
731
  for sub_idx, sub_module in enumerate(module['sub_modules']):
732
- st.markdown(f"### 📖 {sub_module['title']}")
733
-
734
- # Create sessions for each topic
 
 
 
735
  for topic_idx, topic in enumerate(sub_module['topics']):
 
736
  session_key = f"session_{module_idx}_{sub_idx}_{topic_idx}"
737
 
 
 
 
 
 
 
 
 
738
  with st.container():
 
739
  col1, col2, col3 = st.columns([3, 2, 1])
740
  with col1:
741
  new_topic = st.text_input(
742
- "Topic",
743
- value=topic,
744
  key=f"{session_key}_topic"
745
  )
746
- sub_module['topics'][topic_idx] = new_topic
 
 
 
 
747
 
748
  with col2:
749
  session_date = st.date_input(
@@ -759,6 +1015,59 @@ def create_course_form(faculty_name, faculty_id):
759
  key=f"{session_key}_status"
760
  )
761
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
762
  # Create session object
763
  session = {
764
  "session_id": str(ObjectId()),
@@ -777,58 +1086,64 @@ def create_course_form(faculty_name, faculty_id):
777
  },
778
  "post_class": {
779
  "assignments": []
780
- }
 
781
  }
782
  all_sessions.append(session)
783
  current_date = session_date + timedelta(days=7)
784
-
 
785
  new_course_id = get_new_course_id()
786
  course_title = st.session_state.course_plan['course_title']
 
787
  # Final Save Button
788
- if st.button("Save Course", type="primary", use_container_width=True):
789
- try:
790
- course_doc = {
791
- "course_id": new_course_id,
792
- "title": course_title,
793
- "description": st.session_state.course_plan['course_description'],
794
- "faculty": faculty_name,
795
- "faculty_id": faculty_id,
796
- "duration": f"{st.session_state.duration_weeks} weeks",
797
- "start_date": datetime.combine(st.session_state.start_date, datetime.min.time()),
798
- "created_at": datetime.utcnow(),
799
- "sessions": all_sessions
800
- }
801
-
802
- # Insert into database
803
- courses_collection.insert_one(course_doc)
804
-
805
- st.success("Course successfully created!")
806
 
807
- # Update faculty collection
808
- faculty_collection.update_one(
809
- {"_id": st.session_state.user_id},
810
- {
811
- "$push": {
812
- "courses_taught": {
813
- "course_id": new_course_id,
814
- "title": course_title,
815
- }
816
  }
817
- },
818
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
819
 
820
- # Clear session state
821
- st.session_state.course_plan = None
822
- st.session_state.edit_mode = False
823
-
824
- # Optional: Add a button to view the created course
825
- if st.button("View Course"):
826
- # Add navigation logic here
827
- pass
828
-
829
- except Exception as e:
830
- st.error(f"Error saving course: {e}")
831
-
832
  from research_assistant_dashboard import display_research_assistant_dashboard
833
  from goals2 import display_analyst_dashboard
834
  def enroll_in_course(course_id, course_title, student):
 
15
  import os
16
  from openai import OpenAI
17
  from dotenv import load_dotenv
18
+ from create_course2 import create_course, courses_collection, generate_perplexity_response, generate_session_resources, PERPLEXITY_API_KEY, validate_course_plan
19
  import json
20
  from bson import ObjectId
21
  client = OpenAI(api_key=os.getenv("OPENAI_KEY"))
 
653
  st.success(f"Analyst registered successfully! Your username is: {username}")
654
 
655
  # Create Course feature
656
+ # def create_course_form2(faculty_name, faculty_id):
657
+ # """Display enhanced form to create a new course with AI-generated content"""
658
+ # st.title("Create New Course")
659
+
660
+ # if 'course_plan' not in st.session_state:
661
+ # st.session_state.course_plan = None
662
+ # if 'edit_mode' not in st.session_state:
663
+ # st.session_state.edit_mode = False
664
+
665
+ # # Initial Course Creation Form
666
+ # if not st.session_state.course_plan:
667
+ # with st.form("initial_course_form"):
668
+ # col1, col2 = st.columns(2)
669
+ # with col1:
670
+ # course_name = st.text_input("Course Name", placeholder="e.g., Introduction to Computer Science")
671
+ # faculty_info = st.text_input("Faculty", value=faculty_name, disabled=True)
672
+ # with col2:
673
+ # duration_weeks = st.number_input("Duration (weeks)", min_value=1, max_value=16, value=12)
674
+ # start_date = st.date_input("Start Date")
675
+
676
+ # generate_button = st.form_submit_button("Generate Course Structure", use_container_width=True)
677
+
678
+ # if generate_button and course_name:
679
+ # with st.spinner("Generating course structure..."):
680
+ # try:
681
+ # course_plan = generate_perplexity_response(PERPLEXITY_API_KEY, course_name)
682
+ # # print(course_plan)
683
+ # st.session_state.course_plan = json.loads(course_plan)
684
+ # st.session_state.start_date = start_date
685
+ # st.session_state.duration_weeks = duration_weeks
686
+ # st.rerun()
687
+ # except Exception as e:
688
+ # st.error(f"Error generating course structure: {e}")
689
+
690
+ # # Display and Edit Generated Course Content
691
+ # if st.session_state.course_plan:
692
+ # with st.expander("Course Overview", expanded=True):
693
+ # if not st.session_state.edit_mode:
694
+ # st.subheader(st.session_state.course_plan['course_title'])
695
+ # st.write(st.session_state.course_plan['course_description'])
696
+ # edit_button = st.button("Edit Course Details", use_container_width=True)
697
+ # if edit_button:
698
+ # st.session_state.edit_mode = True
699
+ # st.rerun()
700
+ # else:
701
+ # with st.form("edit_course_details"):
702
+ # st.session_state.course_plan['course_title'] = st.text_input(
703
+ # "Course Title",
704
+ # value=st.session_state.course_plan['course_title']
705
+ # )
706
+ # st.session_state.course_plan['course_description'] = st.text_area(
707
+ # "Course Description",
708
+ # value=st.session_state.course_plan['course_description']
709
+ # )
710
+ # if st.form_submit_button("Save Course Details"):
711
+ # st.session_state.edit_mode = False
712
+ # st.rerun()
713
+
714
+ # # Display Modules and Sessions
715
+ # st.subheader("Course Modules and Sessions")
716
+
717
+ # start_date = st.session_state.start_date
718
+ # current_date = start_date
719
+
720
+ # all_sessions = []
721
+ # for module_idx, module in enumerate(st.session_state.course_plan['modules']):
722
+ # with st.expander(f"📚 Module {module_idx + 1}: {module['module_title']}", expanded=True):
723
+ # # Edit module title
724
+ # new_module_title = st.text_input(
725
+ # f"Module {module_idx + 1} Title",
726
+ # value=module['module_title'],
727
+ # key=f"module_{module_idx}"
728
+ # )
729
+ # module['module_title'] = new_module_title
730
+
731
+ # for sub_idx, sub_module in enumerate(module['sub_modules']):
732
+ # st.markdown(f"### 📖 {sub_module['title']}")
733
+
734
+ # # Create sessions for each topic
735
+ # for topic_idx, topic in enumerate(sub_module['topics']):
736
+ # session_key = f"session_{module_idx}_{sub_idx}_{topic_idx}"
737
+
738
+ # with st.container():
739
+ # col1, col2, col3 = st.columns([3, 2, 1])
740
+ # with col1:
741
+ # new_topic = st.text_input(
742
+ # "Topic",
743
+ # value=topic,
744
+ # key=f"{session_key}_topic"
745
+ # )
746
+ # sub_module['topics'][topic_idx] = new_topic
747
+
748
+ # with col2:
749
+ # session_date = st.date_input(
750
+ # "Session Date",
751
+ # value=current_date,
752
+ # key=f"{session_key}_date"
753
+ # )
754
+
755
+ # with col3:
756
+ # session_status = st.selectbox(
757
+ # "Status",
758
+ # options=["upcoming", "in-progress", "completed"],
759
+ # key=f"{session_key}_status"
760
+ # )
761
+
762
+ # # Create session object
763
+ # session = {
764
+ # "session_id": str(ObjectId()),
765
+ # "title": new_topic,
766
+ # "date": datetime.combine(session_date, datetime.min.time()),
767
+ # "status": session_status,
768
+ # "module_name": module['module_title'],
769
+ # "created_at": datetime.utcnow(),
770
+ # "pre_class": {
771
+ # "resources": [],
772
+ # "completion_required": True
773
+ # },
774
+ # "in_class": {
775
+ # "quiz": [],
776
+ # "polls": []
777
+ # },
778
+ # "post_class": {
779
+ # "assignments": []
780
+ # }
781
+ # }
782
+ # all_sessions.append(session)
783
+ # current_date = session_date + timedelta(days=7)
784
+
785
+ # new_course_id = get_new_course_id()
786
+ # course_title = st.session_state.course_plan['course_title']
787
+ # # Final Save Button
788
+ # if st.button("Save Course", type="primary", use_container_width=True):
789
+ # try:
790
+ # course_doc = {
791
+ # "course_id": new_course_id,
792
+ # "title": course_title,
793
+ # "description": st.session_state.course_plan['course_description'],
794
+ # "faculty": faculty_name,
795
+ # "faculty_id": faculty_id,
796
+ # "duration": f"{st.session_state.duration_weeks} weeks",
797
+ # "start_date": datetime.combine(st.session_state.start_date, datetime.min.time()),
798
+ # "created_at": datetime.utcnow(),
799
+ # "sessions": all_sessions
800
+ # }
801
+
802
+ # # Insert into database
803
+ # courses_collection.insert_one(course_doc)
804
+
805
+ # st.success("Course successfully created!")
806
+
807
+ # # Update faculty collection
808
+ # faculty_collection.update_one(
809
+ # {"_id": st.session_state.user_id},
810
+ # {
811
+ # "$push": {
812
+ # "courses_taught": {
813
+ # "course_id": new_course_id,
814
+ # "title": course_title,
815
+ # }
816
+ # }
817
+ # },
818
+ # )
819
+
820
+ # # Clear session state
821
+ # st.session_state.course_plan = None
822
+ # st.session_state.edit_mode = False
823
+
824
+ # # Optional: Add a button to view the created course
825
+ # if st.button("View Course"):
826
+ # # Add navigation logic here
827
+ # pass
828
+
829
+ # except Exception as e:
830
+ # st.error(f"Error saving course: {e}")
831
+
832
+
833
+ def remove_json_backticks(json_string):
834
+ """Remove backticks and 'json' from the JSON object string"""
835
+ return json_string.replace("```json", "").replace("```", "").strip()
836
+
837
+
838
  def create_course_form(faculty_name, faculty_id):
839
+ """Display enhanced form to create a new course with AI-generated content and resources"""
840
+
841
  st.title("Create New Course")
842
 
843
  if 'course_plan' not in st.session_state:
844
  st.session_state.course_plan = None
845
  if 'edit_mode' not in st.session_state:
846
  st.session_state.edit_mode = False
847
+ if 'resources_map' not in st.session_state:
848
+ st.session_state.resources_map = {}
849
+ if 'start_date' not in st.session_state:
850
+ st.session_state.start_date = None
851
+ if 'duration_weeks' not in st.session_state:
852
+ st.session_state.duration_weeks = None
853
+ if 'sessions_per_week' not in st.session_state:
854
+ st.session_state.sessions_per_week = None
855
+
856
 
857
  # Initial Course Creation Form
858
  if not st.session_state.course_plan:
 
861
  with col1:
862
  course_name = st.text_input("Course Name", placeholder="e.g., Introduction to Computer Science")
863
  faculty_info = st.text_input("Faculty", value=faculty_name, disabled=True)
864
+ sessions_per_week = st.number_input("Sessions Per Week", min_value=1, max_value=5, value=2)
865
  with col2:
866
  duration_weeks = st.number_input("Duration (weeks)", min_value=1, max_value=16, value=12)
867
  start_date = st.date_input("Start Date")
 
869
  generate_button = st.form_submit_button("Generate Course Structure", use_container_width=True)
870
 
871
  if generate_button and course_name:
872
+ with st.spinner("Generating course structure and resources..."):
873
  try:
874
+ # Generate course plan with resources
875
+ course_plan = generate_perplexity_response(
876
+ PERPLEXITY_API_KEY,
877
+ course_name,
878
+ duration_weeks,
879
+ sessions_per_week
880
+ )
881
+ try:
882
+ course_plan_json = json.loads(course_plan)
883
+ validate_course_plan(course_plan_json)
884
+ st.session_state.course_plan = course_plan_json
885
+ except (json.JSONDecodeError, ValueError) as e:
886
+ st.error(f"Error in course plan structure: {e}")
887
+ return
888
  st.session_state.start_date = start_date
889
  st.session_state.duration_weeks = duration_weeks
890
+ st.session_state.sessions_per_week = sessions_per_week
891
+
892
+ # Generate resources for all sessions
893
+ session_titles = []
894
+ for module in st.session_state.course_plan['modules']:
895
+ for sub_module in module['sub_modules']:
896
+ for topic in sub_module['topics']:
897
+ # session_titles.append(topic['title'])
898
+ # session_titles.append(topic)
899
+ if isinstance(topic, dict):
900
+ session_titles.append(topic['title'])
901
+ else:
902
+ session_titles.append(topic)
903
+ # In generate_session_resources function, add validation:
904
+ if not session_titles:
905
+ return json.dumps({"session_resources": []})
906
+ resources_response = generate_session_resources(PERPLEXITY_API_KEY, session_titles)
907
+ without_backticks = remove_json_backticks(resources_response)
908
+ resources = json.loads(without_backticks)
909
+ st.session_state.resources_map = {
910
+ resource['session_title']: resource['resources']
911
+ for resource in resources['session_resources']
912
+ }
913
+ # Add error handling for the resources map
914
+ # if st.session_state.resources_map is None:
915
+ # st.session_state.resources_map = {}
916
+
917
  st.rerun()
918
  except Exception as e:
919
  st.error(f"Error generating course structure: {e}")
 
924
  if not st.session_state.edit_mode:
925
  st.subheader(st.session_state.course_plan['course_title'])
926
  st.write(st.session_state.course_plan['course_description'])
927
+ col1, col2, col3 = st.columns(3)
928
+ with col1:
929
+ st.write(f"**Start Date:** {st.session_state.start_date}")
930
+ with col2:
931
+ st.write(f"**Duration (weeks):** {st.session_state.duration_weeks}")
932
+ with col3:
933
+ st.write(f"**Sessions Per Week:** {st.session_state.sessions_per_week}")
934
+
935
  edit_button = st.button("Edit Course Details", use_container_width=True)
936
  if edit_button:
937
  st.session_state.edit_mode = True
 
961
  with st.expander(f"📚 Module {module_idx + 1}: {module['module_title']}", expanded=True):
962
  # Edit module title
963
  new_module_title = st.text_input(
964
+ f"Edit Module Title",
965
  value=module['module_title'],
966
  key=f"module_{module_idx}"
967
  )
968
  module['module_title'] = new_module_title
969
 
970
  for sub_idx, sub_module in enumerate(module['sub_modules']):
971
+ st.markdown("<br>", unsafe_allow_html=True) # Add gap between sessions
972
+ # st.markdown(f"### 📖 {sub_module['title']}")
973
+ st.markdown(f'<h3 style="font-size: 1.25rem;">📖 Chapter {sub_idx + 1}: {sub_module["title"]}</h3>', unsafe_allow_html=True)
974
+ # Possible fix:
975
+ # Inside the loop where topics are being processed:
976
+
977
  for topic_idx, topic in enumerate(sub_module['topics']):
978
+ st.markdown("<br>", unsafe_allow_html=True) # Add gap between sessions
979
  session_key = f"session_{module_idx}_{sub_idx}_{topic_idx}"
980
 
981
+ # Get topic title based on type
982
+ if isinstance(topic, dict):
983
+ current_topic_title = topic.get('title', '')
984
+ current_topic_display = current_topic_title
985
+ else:
986
+ current_topic_title = str(topic)
987
+ current_topic_display = current_topic_title
988
+
989
  with st.container():
990
+ # Session Details
991
  col1, col2, col3 = st.columns([3, 2, 1])
992
  with col1:
993
  new_topic = st.text_input(
994
+ f"Session {topic_idx + 1} Title",
995
+ value=current_topic_display,
996
  key=f"{session_key}_topic"
997
  )
998
+ # Update the topic in the data structure
999
+ if isinstance(topic, dict):
1000
+ topic['title'] = new_topic
1001
+ else:
1002
+ sub_module['topics'][topic_idx] = new_topic
1003
 
1004
  with col2:
1005
  session_date = st.date_input(
 
1015
  key=f"{session_key}_status"
1016
  )
1017
 
1018
+ # Display Resources
1019
+ if st.session_state.resources_map:
1020
+ # Try both the full topic title and the display title
1021
+ resources = None
1022
+ if isinstance(topic, dict) and topic.get('title') in st.session_state.resources_map:
1023
+ resources = st.session_state.resources_map[topic['title']]
1024
+ elif current_topic_title in st.session_state.resources_map:
1025
+ resources = st.session_state.resources_map[current_topic_title]
1026
+
1027
+ if resources:
1028
+ with st.container():
1029
+ # st.markdown("#### 📚 Session Resources")
1030
+ st.markdown(f'<h4 style="font-size: 1.25rem;">📚 Session Resources</h4>', unsafe_allow_html=True)
1031
+ # Readings Tab
1032
+ if resources.get('readings'):
1033
+ st.markdown(f'<h5 style="font-size: 1.1rem; margin-top: 1rem;">📖 External Resources</h5>', unsafe_allow_html=True)
1034
+ col1, col2 = st.columns(2)
1035
+ for idx, reading in enumerate(resources['readings']):
1036
+ with col1 if idx % 2 == 0 else col2:
1037
+ st.markdown(f"""
1038
+ - **{reading['title']}**
1039
+ - Type: {reading['type']}
1040
+ - Estimated reading time: {reading['estimated_read_time']}
1041
+ - [Access Resource]({reading['url']})
1042
+ """)
1043
+
1044
+ # Books Tab and Additional Resources Tab side-by-side
1045
+ col1, col2 = st.columns(2)
1046
+
1047
+ with col1:
1048
+ if resources.get('books'):
1049
+ st.markdown(f'<h5 style="font-size: 1.1rem; margin-top: 1rem;">📚 Reference Books</h5>', unsafe_allow_html=True)
1050
+ for book in resources['books']:
1051
+ with st.container():
1052
+ st.markdown(f"""
1053
+ - **{book['title']}**
1054
+ - Author: {book['author']}
1055
+ - ISBN: {book['isbn']}
1056
+ - Chapters: {book['chapters']}
1057
+ """)
1058
+
1059
+ with col2:
1060
+ if resources.get('additional_resources'):
1061
+ st.markdown(f'<h5 style="font-size: 1.1rem; margin-top: 1rem;">🔗 Additional Study Resources</h5>', unsafe_allow_html=True)
1062
+ for resource in resources['additional_resources']:
1063
+ with st.container():
1064
+ st.markdown(f"""
1065
+ - **{resource['title']}**
1066
+ - Type: {resource['type']}
1067
+ - Description: {resource['description']}
1068
+ - [Access Resource]({resource['url']})
1069
+ """)
1070
+
1071
  # Create session object
1072
  session = {
1073
  "session_id": str(ObjectId()),
 
1086
  },
1087
  "post_class": {
1088
  "assignments": []
1089
+ },
1090
+ "external_resources": st.session_state.resources_map.get(current_topic_title, {})
1091
  }
1092
  all_sessions.append(session)
1093
  current_date = session_date + timedelta(days=7)
1094
+
1095
+
1096
  new_course_id = get_new_course_id()
1097
  course_title = st.session_state.course_plan['course_title']
1098
+
1099
  # Final Save Button
1100
+ if st.button("Save Course", type="primary", use_container_width=True):
1101
+ try:
1102
+ course_doc = {
1103
+ "course_id": new_course_id,
1104
+ "title": course_title,
1105
+ "description": st.session_state.course_plan['course_description'],
1106
+ "faculty": faculty_name,
1107
+ "faculty_id": faculty_id,
1108
+ "duration": f"{st.session_state.duration_weeks} weeks",
1109
+ "sessions_per_week": st.session_state.sessions_per_week,
1110
+ "start_date": datetime.combine(st.session_state.start_date, datetime.min.time()),
1111
+ "created_at": datetime.utcnow(),
1112
+ "sessions": all_sessions
1113
+ }
1114
+
1115
+ # Insert into database
1116
+ courses_collection.insert_one(course_doc)
1117
+ st.success("Course successfully created!")
1118
 
1119
+ # Update faculty collection
1120
+ faculty_collection.update_one(
1121
+ {"_id": st.session_state.user_id},
1122
+ {
1123
+ "$push": {
1124
+ "courses_taught": {
1125
+ "course_id": new_course_id,
1126
+ "title": course_title,
 
1127
  }
1128
+ }
1129
+ }
1130
+ )
1131
+
1132
+ # Clear session state
1133
+ st.session_state.course_plan = None
1134
+ st.session_state.edit_mode = False
1135
+ st.session_state.resources_map = {}
1136
+
1137
+ # Optional: Add a button to view the created course
1138
+ if st.button("View Course"):
1139
+ # Add navigation logic here
1140
+ pass
1141
+
1142
+ except Exception as e:
1143
+ st.error(f"Error saving course: {e}")
1144
+
1145
+
1146
 
 
 
 
 
 
 
 
 
 
 
 
 
1147
  from research_assistant_dashboard import display_research_assistant_dashboard
1148
  from goals2 import display_analyst_dashboard
1149
  def enroll_in_course(course_id, course_title, student):
create_course2.py ADDED
@@ -0,0 +1,331 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime, timedelta
2
+ import os
3
+ from typing import Dict, List, Any
4
+ from pymongo import MongoClient
5
+ import requests
6
+ import uuid
7
+ import openai
8
+ from openai import OpenAI
9
+ import streamlit as st
10
+ from bson import ObjectId
11
+ from dotenv import load_dotenv
12
+ import json
13
+
14
+ load_dotenv()
15
+ MONGODB_URI = os.getenv("MONGO_URI")
16
+ PERPLEXITY_API_KEY = os.getenv("PERPLEXITY_KEY")
17
+ OPENAI_API_KEY = os.getenv("OPENAI_KEY")
18
+
19
+ client = MongoClient(MONGODB_URI)
20
+ db = client['novascholar_db']
21
+ courses_collection = db['courses']
22
+
23
+ def generate_perplexity_response(api_key, course_name, duration_weeks, sessions_per_week):
24
+ headers = {
25
+ "accept": "application/json",
26
+ "content-type": "application/json",
27
+ "authorization": f"Bearer {api_key}"
28
+ }
29
+
30
+ # Calculate sessions based on duration
31
+ total_sessions = duration_weeks * sessions_per_week # Assuming 2 sessions per week
32
+
33
+ prompt = f"""
34
+ You are an expert educational AI assistant specializing in curriculum design and instructional planning. Your task is to generate a comprehensive, academically rigorous course structure for the course {course_name} that fits exactly within {duration_weeks} weeks with {total_sessions} total sessions ({sessions_per_week} sessions per week).
35
+
36
+ Please generate a detailed course structure in JSON format following these specifications:
37
+
38
+ 1. The course structure must be designed for exactly {duration_weeks} weeks with {total_sessions} total sessions
39
+ 2. Each module should contain an appropriate number of sessions that sum up to exactly {total_sessions}
40
+ 3. Each session should be designed for a 1-1.5-hour class duration
41
+ 4. Follow standard academic practices and nomenclature
42
+ 5. Ensure progressive complexity from foundational to advanced concepts
43
+ 6. The course_title should exactly match the course name provided
44
+ 7. Ensure that the property names are enclosed in double quotes (") and followed by a colon (:), and the values are enclosed in double quotes (").
45
+ 8. **DO NOT INCLUDE THE WORD JSON IN THE OUTPUT STRING, DO NOT INCLUDE BACKTICKS (```) IN THE OUTPUT, AND DO NOT INCLUDE ANY OTHER TEXT, OTHER THAN THE ACTUAL JSON RESPONSE. START THE RESPONSE STRING WITH AN OPEN CURLY BRACE {{ AND END WITH A CLOSING CURLY BRACE }}.**
46
+
47
+ The JSON response should follow this structure:
48
+ {{
49
+ "course_title": "string",
50
+ "course_description": "string",
51
+ "total_duration_weeks": {duration_weeks},
52
+ "sessions_per_week": {sessions_per_week},
53
+ "total_sessions": {total_sessions},
54
+ "modules": [
55
+ {{
56
+ "module_title": "string",
57
+ "module_duration_sessions": number,
58
+ "sub_modules": [
59
+ {{
60
+ "title": "string",
61
+ "topics": [
62
+ {{
63
+ "title": "string",
64
+ "short_description": "string",
65
+ "concise_learning_objectives": ["string"]
66
+ }}
67
+ ]
68
+ }}
69
+ ]
70
+ }}
71
+ ]
72
+ }}
73
+
74
+ Ensure that:
75
+ 1. The sum of all module_duration_sessions equals exactly {total_sessions}
76
+ 2. Each topic has clear learning objectives
77
+ 3. Topics build upon each other logically
78
+ 4. Content is distributed evenly across the available sessions
79
+ 5. **This Instruction is Strictly followed: **DO NOT INCLUDE THE WORD JSON IN THE OUTPUT STRING, DO NOT INCLUDE BACKTICKS (```) IN THE OUTPUT, AND DO NOT INCLUDE ANY OTHER TEXT, OTHER THAN THE ACTUAL JSON RESPONSE. START THE RESPONSE STRING WITH AN OPEN CURLY BRACE {{ AND END WITH A CLOSING CURLY BRACE }}.****
80
+
81
+ """
82
+
83
+ messages = [
84
+ {
85
+ "role": "system",
86
+ "content": (
87
+ "You are an expert educational AI assistant specializing in course design and curriculum planning. "
88
+ "Your task is to generate accurate, detailed, and structured educational content that precisely fits "
89
+ "the specified duration."
90
+ ),
91
+ },
92
+ {
93
+ "role": "user",
94
+ "content": prompt
95
+ },
96
+ ]
97
+
98
+ try:
99
+ client = OpenAI(api_key=api_key, base_url="https://api.perplexity.ai")
100
+ response = client.chat.completions.create(
101
+ model="llama-3.1-sonar-small-128k-online",
102
+ messages=messages
103
+ )
104
+ content = response.choices[0].message.content
105
+
106
+ # Validate session count
107
+ course_plan = json.loads(content)
108
+ total_planned_sessions = sum(
109
+ module.get('module_duration_sessions', 0)
110
+ for module in course_plan.get('modules', [])
111
+ )
112
+
113
+ if abs(total_planned_sessions - total_sessions) > 5:
114
+ raise ValueError(f"Generated plan has {total_planned_sessions} sessions, but {total_sessions} were requested")
115
+
116
+ return content
117
+ except Exception as e:
118
+ st.error(f"Failed to fetch data from Perplexity API: {e}")
119
+ return ""
120
+
121
+ def generate_session_resources(api_key, session_titles: List[str]):
122
+ """
123
+ Generate relevant resources for each session title separately
124
+ """
125
+ resources_prompt = f"""
126
+ You are an expert educational content curator. For each session title provided, suggest highly relevant and accurate learning resources.
127
+ Please provide resources for these sessions: {session_titles}
128
+
129
+ For each session, provide resources in this JSON format:
130
+ {{
131
+ "session_resources": [
132
+ {{
133
+ "session_title": "string",
134
+ "resources": {{
135
+ "readings": [
136
+ {{
137
+ "title": "string",
138
+ "url": "string",
139
+ "type": "string",
140
+ "estimated_read_time": "string"
141
+ }}
142
+ ],
143
+ "books": [
144
+ {{
145
+ "title": "string",
146
+ "author": "string",
147
+ "isbn": "string",
148
+ "chapters": "string"
149
+ }}
150
+ ],
151
+ "additional_resources": [
152
+ {{
153
+ "title": "string",
154
+ "url": "string",
155
+ "type": "string",
156
+ "description": "string"
157
+ }}
158
+ ]
159
+ }}
160
+ }}
161
+ ]
162
+ }}
163
+
164
+ Guidelines:
165
+ 1. Ensure all URLs are real and currently active
166
+ 2. Prioritize high-quality, authoritative sources
167
+ 3. Include 1-2 resources of each type
168
+ 5. For readings, include a mix of academic and practical resources. It can exceed to 3-4 readings
169
+ 6. Book references should be real, recently published works
170
+ 7. Additional resources can include tools, documentation, or practice platforms
171
+ 8. Ensure that the property names are enclosed in double quotes (") and followed by a colon (:), and the values are enclosed in double quotes (").
172
+ 9. ***NOTE: **DO NOT INCLUDE THE WORD JSON IN THE OUTPUT STRING, DO NOT INCLUDE BACKTICKS (```) IN THE OUTPUT, AND DO NOT INCLUDE ANY OTHER TEXT, OTHER THAN THE ACTUAL JSON RESPONSE. START THE RESPONSE STRING WITH AN OPEN CURLY BRACE {{ AND END WITH A CLOSING CURLY BRACE }}.**
173
+ """
174
+
175
+ messages = [
176
+ {
177
+ "role": "system",
178
+ "content": "You are an expert educational content curator, focused on providing accurate and relevant learning resources.",
179
+ },
180
+ {
181
+ "role": "user",
182
+ "content": resources_prompt
183
+ },
184
+ ]
185
+
186
+ try:
187
+ client = OpenAI(api_key=api_key, base_url="https://api.perplexity.ai")
188
+ response = client.chat.completions.create(
189
+ model="llama-3.1-sonar-small-128k-online",
190
+ messages=messages
191
+ )
192
+ print("Response is: \n", response.choices[0].message.content)
193
+ # try:
194
+ # return json.loads(response.choices[0].message.content)
195
+ # except json.JSONDecodeError as e:
196
+ # st.error(f"Failed to decode JSON response: {e}")
197
+ # return None
198
+ return response.choices[0].message.content
199
+ except Exception as e:
200
+ st.error(f"Failed to generate resources: {e}")
201
+ return None
202
+
203
+ def validate_course_plan(course_plan):
204
+ required_fields = ['course_title', 'course_description', 'modules']
205
+ if not all(field in course_plan for field in required_fields):
206
+ raise ValueError("Invalid course plan structure")
207
+
208
+ for module in course_plan['modules']:
209
+ if 'module_title' not in module or 'sub_modules' not in module:
210
+ raise ValueError("Invalid module structure")
211
+
212
+ def create_session(title: str, date: datetime, module_name: str, resources: dict):
213
+ """Create a session document with pre-class, in-class, and post-class components."""
214
+ return {
215
+ "session_id": ObjectId(),
216
+ "title": title,
217
+ "date": date,
218
+ "status": "upcoming",
219
+ "created_at": datetime.utcnow(),
220
+ "module_name": module_name,
221
+ "pre_class": {
222
+ "resources": [],
223
+ "completion_required": True
224
+ },
225
+ "in_class": {
226
+ "quiz": [],
227
+ "polls": []
228
+ },
229
+ "post_class": {
230
+ "assignments": []
231
+ },
232
+ "external_resources": {
233
+ "readings": resources.get("readings", []),
234
+ "books": resources.get("books", []),
235
+ "additional_resources": resources.get("additional_resources", [])
236
+ }
237
+ }
238
+
239
+ def create_course(course_name: str, start_date: datetime, duration_weeks: int, sessions_per_week: int):
240
+ # First generate a course plan using Perplexity API
241
+ # course_plan = generate_perplexity_response(PERPLEXITY_API_KEY, course_name, duration_weeks, sessions_per_week)
242
+ # course_plan_json = json.loads(course_plan)
243
+
244
+ # print("Course Structure is: \n", course_plan_json);
245
+
246
+ # Earlier Code:
247
+ # Generate sessions for each module with resources
248
+ # all_sessions = []
249
+ # current_date = start_date
250
+
251
+ # for module in course_plan_json['modules']:
252
+ # for sub_module in module['sub_modules']:
253
+ # for topic in sub_module['topics']:
254
+ # session = create_session(
255
+ # title=topic['title'],
256
+ # date=current_date,
257
+ # module_name=module['module_title'],
258
+ # resources=topic['resources']
259
+ # )
260
+ # all_sessions.append(session)
261
+ # current_date += timedelta(days=3.5) # Spacing sessions evenly across the week
262
+
263
+ # return course_plan_json, all_sessions
264
+
265
+ # New Code:
266
+ # Extract all session titles
267
+ session_titles = []
268
+ # Load the course plan JSON
269
+ course_plan_json = {}
270
+ with open('sample_files/sample_course.json', 'r') as file:
271
+ course_plan_json = json.load(file)
272
+
273
+ for module in course_plan_json['modules']:
274
+ for sub_module in module['sub_modules']:
275
+ for topic in sub_module['topics']:
276
+ session_titles.append(topic['title'])
277
+
278
+ # Generate resources for all sessions
279
+ session_resources = generate_session_resources(PERPLEXITY_API_KEY, session_titles)
280
+ # print("Session Resources are: \n", session_resources)
281
+ resources = json.loads(session_resources)
282
+ # print("Resources JSON is: \n", resources_json)
283
+
284
+ # print("Session Resources are: \n", session_resources)
285
+
286
+ # Create a mapping of session titles to their resources
287
+
288
+ # Import Resources JSON
289
+ # resources = {}
290
+ # with open('sample_files/sample_course_resources.json', 'r') as file:
291
+ # resources = json.load(file)
292
+
293
+ resources_map = {
294
+ resource['session_title']: resource['resources']
295
+ for resource in resources['session_resources']
296
+ }
297
+ print("Resources Map is: \n", resources_map)
298
+ # print("Sample is: ", resources_map.get('Overview of ML Concepts, History, and Applications'));
299
+ # Generate sessions with their corresponding resources
300
+ all_sessions = []
301
+ current_date = start_date
302
+
303
+ for module in course_plan_json['modules']:
304
+ for sub_module in module['sub_modules']:
305
+ for topic in sub_module['topics']:
306
+ session = create_session(
307
+ title=topic['title'],
308
+ date=current_date,
309
+ module_name=module['module_title'],
310
+ resources=resources_map.get(topic['title'], {})
311
+ )
312
+ all_sessions.append(session)
313
+ current_date += timedelta(days=3.5)
314
+
315
+ print("All Sessions are: \n", all_sessions)
316
+
317
+ def get_new_course_id():
318
+ """Generate a new course ID by incrementing the last course ID"""
319
+ last_course = courses_collection.find_one(sort=[("course_id", -1)])
320
+ if last_course:
321
+ last_course_id = int(last_course["course_id"][2:])
322
+ new_course_id = f"CS{last_course_id + 1}"
323
+ else:
324
+ new_course_id = "CS101"
325
+ return new_course_id
326
+
327
+ # if __name__ == "__main__":
328
+ # course_name = "Introduction to Machine Learning"
329
+ # start_date = datetime(2022, 9, 1)
330
+ # duration_weeks = 4
331
+ # create_course(course_name, start_date, duration_weeks, 3)
pre_class_analytics4.py ADDED
@@ -0,0 +1,526 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import numpy as np
3
+ from datetime import datetime
4
+ from typing import List, Dict, Any, Tuple
5
+ import spacy
6
+ from collections import Counter, defaultdict
7
+ from sklearn.feature_extraction.text import TfidfVectorizer
8
+ from sklearn.metrics.pairwise import cosine_similarity
9
+ from textblob import TextBlob
10
+ import networkx as nx
11
+ from scipy import stats
12
+ import logging
13
+ import json
14
+ from dataclasses import dataclass
15
+ from enum import Enum
16
+
17
+ # Configure logging
18
+ logging.basicConfig(level=logging.INFO)
19
+ logger = logging.getLogger(__name__)
20
+
21
+ class TopicDifficulty(Enum):
22
+ EASY = "easy"
23
+ MODERATE = "moderate"
24
+ DIFFICULT = "difficult"
25
+ VERY_DIFFICULT = "very_difficult"
26
+
27
+ @dataclass
28
+ class QuestionMetrics:
29
+ complexity_score: float
30
+ follow_up_count: int
31
+ clarification_count: int
32
+ time_spent: float
33
+ sentiment_score: float
34
+
35
+ @dataclass
36
+ class TopicInsights:
37
+ difficulty_level: TopicDifficulty
38
+ common_confusion_points: List[str]
39
+ question_patterns: List[str]
40
+ time_distribution: Dict[str, float]
41
+ engagement_metrics: Dict[str, float]
42
+ recommended_focus_areas: List[str]
43
+
44
+ class PreClassAnalytics:
45
+ def __init__(self, nlp_model: str = "en_core_web_lg"):
46
+ """Initialize the analytics system with necessary components."""
47
+ self.nlp = spacy.load(nlp_model)
48
+ self.question_indicators = {
49
+ "what", "why", "how", "when", "where", "which", "who",
50
+ "whose", "whom", "can", "could", "would", "will", "explain"
51
+ }
52
+ self.confusion_indicators = {
53
+ "confused", "don't understand", "unclear", "not clear",
54
+ "stuck", "difficult", "hard", "help", "explain again"
55
+ }
56
+ self.follow_up_indicators = {
57
+ "also", "another", "additionally", "furthermore", "moreover",
58
+ "besides", "related", "similarly", "again"
59
+ }
60
+
61
+ def preprocess_chat_history(self, chat_history: List[Dict]) -> pd.DataFrame:
62
+ """Convert chat history to DataFrame with enhanced features."""
63
+ messages = []
64
+ for chat in chat_history:
65
+ user_id = chat['user_id']['$oid']
66
+ for msg in chat['messages']:
67
+ messages.append({
68
+ 'user_id': user_id,
69
+ 'timestamp': pd.to_datetime(msg['timestamp']['$date']),
70
+ 'prompt': msg['prompt'],
71
+ 'response': msg['response'],
72
+ 'is_question': any(q in msg['prompt'].lower() for q in self.question_indicators),
73
+ 'shows_confusion': any(c in msg['prompt'].lower() for c in self.confusion_indicators),
74
+ 'is_followup': any(f in msg['prompt'].lower() for f in self.follow_up_indicators)
75
+ })
76
+
77
+ df = pd.DataFrame(messages)
78
+ df['sentiment'] = df['prompt'].apply(lambda x: TextBlob(x).sentiment.polarity)
79
+ return df
80
+
81
+ def extract_topic_hierarchies(self, df: pd.DataFrame) -> Dict[str, List[str]]:
82
+ """Extract hierarchical topic relationships from conversations."""
83
+ topic_hierarchy = defaultdict(list)
84
+
85
+ for _, row in df.iterrows():
86
+ doc = self.nlp(row['prompt'])
87
+
88
+ # Extract main topics and subtopics using noun chunks and dependencies
89
+ main_topics = []
90
+ subtopics = []
91
+
92
+ for chunk in doc.noun_chunks:
93
+ if chunk.root.dep_ in ('nsubj', 'dobj'):
94
+ main_topics.append(chunk.text.lower())
95
+ else:
96
+ subtopics.append(chunk.text.lower())
97
+
98
+ # Build hierarchy
99
+ for main_topic in main_topics:
100
+ topic_hierarchy[main_topic].extend(subtopics)
101
+
102
+ # Clean and deduplicate
103
+ return {k: list(set(v)) for k, v in topic_hierarchy.items()}
104
+
105
+ def analyze_topic_difficulty(self, df: pd.DataFrame, topic: str) -> TopicDifficulty:
106
+ """Determine topic difficulty based on various metrics."""
107
+ topic_msgs = df[df['prompt'].str.contains(topic, case=False)]
108
+
109
+ # Calculate difficulty indicators
110
+ confusion_rate = topic_msgs['shows_confusion'].mean()
111
+ question_rate = topic_msgs['is_question'].mean()
112
+ follow_up_rate = topic_msgs['is_followup'].mean()
113
+ avg_sentiment = topic_msgs['sentiment'].mean()
114
+
115
+ # Calculate composite difficulty score
116
+ difficulty_score = (
117
+ confusion_rate * 0.4 +
118
+ question_rate * 0.3 +
119
+ follow_up_rate * 0.2 +
120
+ (1 - (avg_sentiment + 1) / 2) * 0.1
121
+ )
122
+
123
+ # Map score to difficulty level
124
+ if difficulty_score < 0.3:
125
+ return TopicDifficulty.EASY
126
+ elif difficulty_score < 0.5:
127
+ return TopicDifficulty.MODERATE
128
+ elif difficulty_score < 0.7:
129
+ return TopicDifficulty.DIFFICULT
130
+ else:
131
+ return TopicDifficulty.VERY_DIFFICULT
132
+
133
+ def identify_confusion_patterns(self, df: pd.DataFrame, topic: str) -> List[str]:
134
+ """Identify common patterns in student confusion."""
135
+ confused_msgs = df[
136
+ (df['prompt'].str.contains(topic, case=False)) &
137
+ (df['shows_confusion'])
138
+ ]['prompt']
139
+
140
+ patterns = []
141
+ for msg in confused_msgs:
142
+ doc = self.nlp(msg)
143
+
144
+ # Extract key phrases around confusion indicators
145
+ for sent in doc.sents:
146
+ for token in sent:
147
+ if token.text.lower() in self.confusion_indicators:
148
+ # Get context window around confusion indicator
149
+ context = sent.text
150
+ patterns.append(context)
151
+
152
+ # Group similar patterns
153
+ if patterns:
154
+ vectorizer = TfidfVectorizer(ngram_range=(1, 3))
155
+ tfidf_matrix = vectorizer.fit_transform(patterns)
156
+ similarity_matrix = cosine_similarity(tfidf_matrix)
157
+
158
+ # Cluster similar patterns
159
+ G = nx.Graph()
160
+ for i in range(len(patterns)):
161
+ for j in range(i + 1, len(patterns)):
162
+ if similarity_matrix[i][j] > 0.5: # Similarity threshold
163
+ G.add_edge(i, j)
164
+
165
+ # Extract representative patterns from each cluster
166
+ clusters = list(nx.connected_components(G))
167
+ return [patterns[min(cluster)] for cluster in clusters]
168
+
169
+ return []
170
+
171
+ def analyze_question_patterns(self, df: pd.DataFrame, topic: str) -> List[str]:
172
+ """Analyze patterns in student questions about the topic."""
173
+ topic_questions = df[
174
+ (df['prompt'].str.contains(topic, case=False)) &
175
+ (df['is_question'])
176
+ ]['prompt']
177
+
178
+ question_types = defaultdict(list)
179
+ for question in topic_questions:
180
+ doc = self.nlp(question)
181
+
182
+ # Categorize questions
183
+ if any(token.text.lower() in {"what", "define", "explain"} for token in doc):
184
+ question_types["conceptual"].append(question)
185
+ elif any(token.text.lower() in {"how", "steps", "process"} for token in doc):
186
+ question_types["procedural"].append(question)
187
+ elif any(token.text.lower() in {"why", "reason", "because"} for token in doc):
188
+ question_types["reasoning"].append(question)
189
+ else:
190
+ question_types["other"].append(question)
191
+
192
+ # Extract patterns from each category
193
+ patterns = []
194
+ for category, questions in question_types.items():
195
+ if questions:
196
+ vectorizer = TfidfVectorizer(ngram_range=(1, 3))
197
+ tfidf_matrix = vectorizer.fit_transform(questions)
198
+
199
+ # Get most representative questions
200
+ feature_array = np.mean(tfidf_matrix.toarray(), axis=0)
201
+ tfidf_sorting = np.argsort(feature_array)[::-1]
202
+ features = vectorizer.get_feature_names_out()
203
+
204
+ patterns.append(f"{category}: {' '.join(features[tfidf_sorting[:3]])}")
205
+
206
+ return patterns
207
+
208
+ def analyze_time_distribution(self, df: pd.DataFrame, topic: str) -> Dict[str, float]:
209
+ """Analyze time spent on different aspects of the topic."""
210
+ topic_msgs = df[df['prompt'].str.contains(topic, case=False)].copy()
211
+ if len(topic_msgs) < 2:
212
+ return {}
213
+
214
+ topic_msgs['time_diff'] = topic_msgs['timestamp'].diff()
215
+
216
+ # Calculate time distribution
217
+ distribution = {
218
+ 'total_time': topic_msgs['time_diff'].sum().total_seconds() / 60,
219
+ 'avg_time_per_message': topic_msgs['time_diff'].mean().total_seconds() / 60,
220
+ 'max_time_gap': topic_msgs['time_diff'].max().total_seconds() / 60,
221
+ 'time_spent_on_questions': topic_msgs[topic_msgs['is_question']]['time_diff'].sum().total_seconds() / 60,
222
+ 'time_spent_on_confusion': topic_msgs[topic_msgs['shows_confusion']]['time_diff'].sum().total_seconds() / 60
223
+ }
224
+
225
+ return distribution
226
+
227
+ def calculate_engagement_metrics(self, df: pd.DataFrame, topic: str) -> Dict[str, float]:
228
+ """Calculate student engagement metrics for the topic."""
229
+ topic_msgs = df[df['prompt'].str.contains(topic, case=False)]
230
+
231
+ metrics = {
232
+ 'message_count': len(topic_msgs),
233
+ 'question_ratio': topic_msgs['is_question'].mean(),
234
+ 'confusion_ratio': topic_msgs['shows_confusion'].mean(),
235
+ 'follow_up_ratio': topic_msgs['is_followup'].mean(),
236
+ 'avg_sentiment': topic_msgs['sentiment'].mean(),
237
+ 'engagement_score': 0.0 # Will be calculated below
238
+ }
239
+
240
+ # Calculate engagement score
241
+ metrics['engagement_score'] = (
242
+ metrics['message_count'] * 0.3 +
243
+ metrics['question_ratio'] * 0.25 +
244
+ metrics['follow_up_ratio'] * 0.25 +
245
+ (metrics['avg_sentiment'] + 1) / 2 * 0.2 # Normalize sentiment to 0-1
246
+ )
247
+
248
+ return metrics
249
+
250
+ def generate_topic_insights(self, df: pd.DataFrame, topic: str) -> TopicInsights:
251
+ """Generate comprehensive insights for a topic."""
252
+ difficulty = self.analyze_topic_difficulty(df, topic)
253
+ confusion_points = self.identify_confusion_patterns(df, topic)
254
+ question_patterns = self.analyze_question_patterns(df, topic)
255
+ time_distribution = self.analyze_time_distribution(df, topic)
256
+ engagement_metrics = self.calculate_engagement_metrics(df, topic)
257
+
258
+ # Generate recommended focus areas based on insights
259
+ focus_areas = []
260
+
261
+ if difficulty in (TopicDifficulty.DIFFICULT, TopicDifficulty.VERY_DIFFICULT):
262
+ focus_areas.append("Fundamental concept reinforcement needed")
263
+
264
+ if confusion_points:
265
+ focus_areas.append(f"Address common confusion around: {', '.join(confusion_points[:3])}")
266
+
267
+ if engagement_metrics['confusion_ratio'] > 0.3:
268
+ focus_areas.append("Consider alternative teaching approaches")
269
+
270
+ if time_distribution.get('time_spent_on_questions', 0) > time_distribution.get('total_time', 0) * 0.5:
271
+ focus_areas.append("More practical examples or demonstrations needed")
272
+
273
+ return TopicInsights(
274
+ difficulty_level=difficulty,
275
+ common_confusion_points=confusion_points,
276
+ question_patterns=question_patterns,
277
+ time_distribution=time_distribution,
278
+ engagement_metrics=engagement_metrics,
279
+ recommended_focus_areas=focus_areas
280
+ )
281
+
282
+ def analyze_student_progress(self, df: pd.DataFrame) -> Dict[str, Any]:
283
+ """Analyze individual student progress and learning patterns."""
284
+ student_progress = {}
285
+
286
+ for student_id in df['user_id'].unique():
287
+ student_msgs = df[df['user_id'] == student_id]
288
+
289
+ # Calculate student-specific metrics
290
+ progress = {
291
+ 'total_messages': len(student_msgs),
292
+ 'questions_asked': student_msgs['is_question'].sum(),
293
+ 'confusion_instances': student_msgs['shows_confusion'].sum(),
294
+ 'avg_sentiment': student_msgs['sentiment'].mean(),
295
+ 'topic_engagement': {},
296
+ 'learning_pattern': self._identify_learning_pattern(student_msgs)
297
+ }
298
+
299
+ # Analyze topic-specific engagement
300
+ topics = self.extract_topic_hierarchies(student_msgs)
301
+ for topic in topics:
302
+ topic_msgs = student_msgs[student_msgs['prompt'].str.contains(topic, case=False)]
303
+ progress['topic_engagement'][topic] = {
304
+ 'message_count': len(topic_msgs),
305
+ 'confusion_rate': topic_msgs['shows_confusion'].mean(),
306
+ 'sentiment_trend': stats.linregress(
307
+ range(len(topic_msgs)),
308
+ topic_msgs['sentiment']
309
+ ).slope
310
+ }
311
+
312
+ student_progress[student_id] = progress
313
+
314
+ return student_progress
315
+
316
+ def _identify_learning_pattern(self, student_msgs: pd.DataFrame) -> str:
317
+ """Identify student's learning pattern based on their interaction style."""
318
+ # Calculate key metrics
319
+ question_ratio = student_msgs['is_question'].mean()
320
+ confusion_ratio = student_msgs['shows_confusion'].mean()
321
+ follow_up_ratio = student_msgs['is_followup'].mean()
322
+ sentiment_trend = stats.linregress(
323
+ range(len(student_msgs)),
324
+ student_msgs['sentiment']
325
+ ).slope
326
+
327
+ # Identify pattern
328
+ if question_ratio > 0.6:
329
+ return "Inquisitive Learner"
330
+ elif confusion_ratio > 0.4:
331
+ return "Needs Additional Support"
332
+ elif follow_up_ratio > 0.5:
333
+ return "Deep Dive Learner"
334
+ elif sentiment_trend > 0:
335
+ return "Progressive Learner"
336
+ else:
337
+ return "Steady Learner"
338
+
339
+ def generate_comprehensive_report(self, chat_history: List[Dict]) -> Dict[str, Any]:
340
+ """Generate a comprehensive analytics report."""
341
+ # Preprocess chat history
342
+ df = self.preprocess_chat_history(chat_history)
343
+
344
+ # Extract topics
345
+ topics = self.extract_topic_hierarchies(df)
346
+
347
+ report = {
348
+ 'topics': {},
349
+ 'student_progress': self.analyze_student_progress(df),
350
+ 'overall_metrics': {
351
+ 'total_conversations': len(df),
352
+ 'unique_students': df['user_id'].nunique(),
353
+ 'avg_sentiment': df['sentiment'].mean(),
354
+ 'most_discussed_topics': Counter(
355
+ topic for topics_list in topics.values()
356
+ for topic in topics_list
357
+ ).most_common(5)
358
+ }
359
+ }
360
+
361
+ # Generate topic-specific insights
362
+ for main_topic, subtopics in topics.items():
363
+ subtopic_insights = {}
364
+ for subtopic in subtopics:
365
+ subtopic_insights[subtopic] = {
366
+ 'insights': self.generate_topic_insights(df, subtopic),
367
+ 'related_topics': [t for t in subtopics if t != subtopic],
368
+ 'student_engagement': {
369
+ student_id: self.calculate_engagement_metrics(
370
+ df[df['user_id'] == student_id],
371
+ subtopic
372
+ )
373
+ for student_id in df['user_id'].unique()
374
+ }
375
+ }
376
+
377
+ report['topics'][main_topic] = {
378
+ 'insights': self.generate_topic_insights(df, main_topic),
379
+ 'subtopics': subtopic_insights,
380
+ 'topic_relationships': {
381
+ 'hierarchy_depth': len(subtopics),
382
+ 'connection_strength': self._calculate_topic_connections(df, main_topic, subtopics),
383
+ 'progression_path': self._identify_topic_progression(df, main_topic, subtopics)
384
+ }
385
+ }
386
+
387
+ # Add temporal analysis
388
+ report['temporal_analysis'] = {
389
+ 'daily_engagement': df.groupby(df['timestamp'].dt.date).agg({
390
+ 'user_id': 'count',
391
+ 'is_question': 'sum',
392
+ 'shows_confusion': 'sum',
393
+ 'sentiment': 'mean'
394
+ }).to_dict(),
395
+ 'peak_activity_hours': df.groupby(df['timestamp'].dt.hour)['user_id'].count().nlargest(3).to_dict(),
396
+ 'learning_trends': self._analyze_learning_trends(df)
397
+ }
398
+
399
+ # Add recommendations
400
+ report['recommendations'] = self._generate_recommendations(report)
401
+
402
+ return report
403
+
404
+ def _calculate_topic_connections(self, df: pd.DataFrame, main_topic: str, subtopics: List[str]) -> Dict[str, float]:
405
+ """Calculate connection strength between topics based on co-occurrence."""
406
+ connections = {}
407
+ main_topic_msgs = df[df['prompt'].str.contains(main_topic, case=False)]
408
+
409
+ for subtopic in subtopics:
410
+ cooccurrence = df[
411
+ df['prompt'].str.contains(main_topic, case=False) &
412
+ df['prompt'].str.contains(subtopic, case=False)
413
+ ].shape[0]
414
+
415
+ connection_strength = cooccurrence / len(main_topic_msgs) if len(main_topic_msgs) > 0 else 0
416
+ connections[subtopic] = connection_strength
417
+
418
+ return connections
419
+
420
+ def _identify_topic_progression(self, df: pd.DataFrame, main_topic: str, subtopics: List[str]) -> List[str]:
421
+ """Identify optimal topic progression path based on student interactions."""
422
+ topic_difficulties = {}
423
+
424
+ for subtopic in subtopics:
425
+ difficulty = self.analyze_topic_difficulty(df, subtopic)
426
+ topic_difficulties[subtopic] = difficulty.value
427
+
428
+ # Sort subtopics by difficulty
429
+ return sorted(subtopics, key=lambda x: topic_difficulties[x])
430
+
431
+ def _analyze_learning_trends(self, df: pd.DataFrame) -> Dict[str, Any]:
432
+ """Analyze overall learning trends across the dataset."""
433
+ return {
434
+ 'sentiment_trend': stats.linregress(
435
+ range(len(df)),
436
+ df['sentiment']
437
+ )._asdict(),
438
+ 'confusion_trend': stats.linregress(
439
+ range(len(df)),
440
+ df['shows_confusion']
441
+ )._asdict(),
442
+ 'engagement_progression': self._calculate_engagement_progression(df)
443
+ }
444
+
445
+ def _calculate_engagement_progression(self, df: pd.DataFrame) -> Dict[str, float]:
446
+ """Calculate how student engagement changes over time."""
447
+ df['week'] = df['timestamp'].dt.isocalendar().week
448
+ weekly_engagement = df.groupby('week').agg({
449
+ 'is_question': 'mean',
450
+ 'shows_confusion': 'mean',
451
+ 'is_followup': 'mean',
452
+ 'sentiment': 'mean'
453
+ })
454
+
455
+ return {
456
+ 'question_trend': stats.linregress(
457
+ range(len(weekly_engagement)),
458
+ weekly_engagement['is_question']
459
+ ).slope,
460
+ 'confusion_trend': stats.linregress(
461
+ range(len(weekly_engagement)),
462
+ weekly_engagement['shows_confusion']
463
+ ).slope,
464
+ 'follow_up_trend': stats.linregress(
465
+ range(len(weekly_engagement)),
466
+ weekly_engagement['is_followup']
467
+ ).slope,
468
+ 'sentiment_trend': stats.linregress(
469
+ range(len(weekly_engagement)),
470
+ weekly_engagement['sentiment']
471
+ ).slope
472
+ }
473
+
474
+ def _generate_recommendations(self, report: Dict[str, Any]) -> List[str]:
475
+ """Generate actionable recommendations based on the analysis."""
476
+ recommendations = []
477
+
478
+ # Analyze difficulty distribution
479
+ difficult_topics = [
480
+ topic for topic, data in report['topics'].items()
481
+ if data['insights'].difficulty_level in
482
+ (TopicDifficulty.DIFFICULT, TopicDifficulty.VERY_DIFFICULT)
483
+ ]
484
+
485
+ if difficult_topics:
486
+ recommendations.append(
487
+ f"Consider providing additional resources for challenging topics: {', '.join(difficult_topics)}"
488
+ )
489
+
490
+ # Analyze student engagement
491
+ avg_engagement = np.mean([
492
+ progress['questions_asked'] / progress['total_messages']
493
+ for progress in report['student_progress'].values()
494
+ ])
495
+
496
+ if avg_engagement < 0.3:
497
+ recommendations.append(
498
+ "Implement more interactive elements to increase student engagement"
499
+ )
500
+
501
+ # Analyze temporal patterns
502
+ peak_hours = list(report['temporal_analysis']['peak_activity_hours'].keys())
503
+ recommendations.append(
504
+ f"Consider scheduling additional support during peak activity hours: {peak_hours}"
505
+ )
506
+
507
+ # Analyze learning trends
508
+ sentiment_trend = report['temporal_analysis']['learning_trends']['sentiment_trend']
509
+ if sentiment_trend < 0:
510
+ recommendations.append(
511
+ "Review teaching approach to address declining student satisfaction"
512
+ )
513
+
514
+ return recommendations
515
+
516
+ if __name__ == "__main__":
517
+ # Load chat history data
518
+ with open("chat_history_corpus.json", "r", encoding="utf-8") as file:
519
+ chat_history = json.load(file)
520
+
521
+ # Initialize analytics system
522
+ analytics = PreClassAnalytics()
523
+
524
+ # Generate comprehensive report
525
+ report = analytics.generate_comprehensive_report(chat_history)
526
+ print(json.dumps(report, indent=2))
session_page.py CHANGED
@@ -1288,11 +1288,41 @@ def get_preclass_analytics(session):
1288
  # return refined_report
1289
 
1290
  # Use the 2nd analytice engine (using LLM):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1291
  analytics_generator = NovaScholarAnalytics()
1292
  analytics2 = analytics_generator.generate_analytics(all_chat_histories, topics)
1293
  # enriched_analytics = analytics_generator._enrich_analytics(analytics2)
1294
  print("Analytics is: ", analytics2)
1295
- return analytics2
 
 
 
 
1296
  # print(json.dumps(analytics, indent=2))
1297
 
1298
 
@@ -1305,9 +1335,16 @@ def display_preclass_analytics2(session, course_id):
1305
  # Initialize or get analytics data from session state
1306
  if 'analytics_data' not in st.session_state:
1307
  st.session_state.analytics_data = get_preclass_analytics(session)
 
 
1308
 
1309
- analytics = st.session_state.analytics_data
 
 
 
 
1310
 
 
1311
  # Enhanced CSS for better styling and interactivity
1312
  st.markdown("""
1313
  <style>
@@ -1497,133 +1534,133 @@ def display_preclass_analytics2(session, course_id):
1497
  if 'topic_indices' not in st.session_state:
1498
  st.session_state.topic_indices = list(range(len(analytics["topic_wise_insights"])))
1499
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1500
 
1501
- st.markdown('<div class="topic-list">', unsafe_allow_html=True)
1502
- for idx in st.session_state.topic_indices:
1503
- topic = analytics["topic_wise_insights"][idx]
1504
- topic_id = f"topic_{idx}"
1505
-
1506
- # Create clickable header
1507
- col1, col2 = st.columns([3, 1])
1508
- with col1:
1509
- if st.button(
1510
- topic["topic"],
1511
- key=f"topic_button_{idx}",
1512
- use_container_width=True,
1513
- type="secondary"
1514
- ):
1515
- st.session_state.expanded_topic = topic_id if st.session_state.expanded_topic != topic_id else None
1516
-
1517
- with col2:
1518
- st.markdown(f"""
1519
- <div style="text-align: right;">
1520
- <span class="topic-struggling-rate">{topic["struggling_percentage"]*100:.1f}% Struggling</span>
1521
- </div>
1522
- """, unsafe_allow_html=True)
1523
-
1524
- # Show content if topic is expanded
1525
- if st.session_state.expanded_topic == topic_id:
1526
  st.markdown(f"""
1527
- <div class="topic-content">
1528
- <div class="section-heading">Key Issues</div>
1529
- <ul>
1530
- {"".join([f"<li>{issue}</li>" for issue in topic["key_issues"]])}
1531
- </ul>
1532
- <div class="section-heading">Key Misconceptions</div>
1533
- <ul>
1534
- {"".join([f"<li>{misc}</li>" for misc in topic["key_misconceptions"]])}
1535
- </ul>
1536
  </div>
1537
  """, unsafe_allow_html=True)
1538
- st.markdown('</div>', unsafe_allow_html=True)
1539
 
1540
- # AI Recommendations Section
1541
- st.markdown('<h2 class="section-title">AI-Powered Recommendations</h2>', unsafe_allow_html=True)
1542
- st.markdown('<div class="recommendation-grid">', unsafe_allow_html=True)
1543
- for idx, rec in enumerate(analytics["ai_recommended_actions"]):
1544
- st.markdown(f"""
1545
- <div class="recommendation-card">
1546
- <h4>
1547
- <span>Recommendation {idx + 1}</span>
1548
- <span class="priority-badge">{rec["priority"]}</span>
1549
- </h4>
1550
- <p>{rec["action"]}</p>
1551
- <p><span class="reason">Reason:</span> {rec["reasoning"]}</p>
1552
- <p><span class="reason">Expected Outcome:</span> {rec["expected_outcome"]}</p>
1553
- </div>
1554
- """, unsafe_allow_html=True)
1555
- st.markdown('</div>', unsafe_allow_html=True)
1556
-
1557
- # Student Analytics Section
1558
- st.markdown('<h2 class="section-title">Student Analytics</h2>', unsafe_allow_html=True)
1559
-
1560
- # Filters
1561
- with st.container():
1562
- # st.markdown('<div class="student-filters">', unsafe_allow_html=True)
1563
- col1, col2, col3 = st.columns(3)
1564
- with col1:
1565
- concept_understanding = st.selectbox(
1566
- "Filter by Understanding",
1567
- ["All", "Strong", "Moderate", "Needs Improvement"]
1568
- )
1569
- with col2:
1570
- participation_level = st.selectbox(
1571
- "Filter by Participation",
1572
- ["All", "High (>80%)", "Medium (50-80%)", "Low (<50%)"]
1573
- )
1574
- with col3:
1575
- struggling_topic = st.selectbox(
1576
- "Filter by Struggling Topic",
1577
- ["All"] + list(set([topic for student in analytics["student_analytics"]
1578
- for topic in student["struggling_topics"]]))
1579
- )
1580
- # st.markdown('</div>', unsafe_allow_html=True)
1581
-
1582
- # Display student metrics in a grid
1583
- st.markdown('<div class="analytics-grid">', unsafe_allow_html=True)
1584
- for student in analytics["student_analytics"]:
1585
- # Apply filters
1586
- if (concept_understanding != "All" and
1587
- student["engagement_metrics"]["concept_understanding"].replace("_", " ").title() != concept_understanding):
1588
- continue
1589
-
1590
- participation = student["engagement_metrics"]["participation_level"] * 100
1591
- if participation_level != "All":
1592
- if participation_level == "High (>80%)" and participation <= 80:
1593
- continue
1594
- elif participation_level == "Medium (50-80%)" and (participation < 50 or participation > 80):
1595
- continue
1596
- elif participation_level == "Low (<50%)" and participation >= 50:
1597
  continue
1598
 
1599
- if struggling_topic != "All" and struggling_topic not in student["struggling_topics"]:
1600
- continue
 
 
 
 
 
 
 
 
 
1601
 
1602
- st.markdown(f"""
1603
- <div class="student-metrics-card">
1604
- <div class="header">
1605
- <span class="student-id">Student {student["student_id"][-6:]}</span>
1606
- </div>
1607
- <div class="metrics-grid">
1608
- <div class="metric-box">
1609
- <div class="label">Participation</div>
1610
- <div class="value">{student["engagement_metrics"]["participation_level"]*100:.1f}%</div>
1611
- </div>
1612
- <div class="metric-box">
1613
- <div class="label">Understanding</div>
1614
- <div class="value">{student["engagement_metrics"]["concept_understanding"].replace('_', ' ').title()}</div>
1615
- </div>
1616
- <div class="struggling-topics">
1617
- <div class="label">Struggling Topics: </div>
1618
- <div class="value">{", ".join(student["struggling_topics"]) if student["struggling_topics"] else "None"}</div>
1619
  </div>
1620
- <div class="recommendation-text">
1621
- {student["personalized_recommendation"]}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1622
  </div>
1623
  </div>
1624
- </div>
1625
- """, unsafe_allow_html=True)
1626
- st.markdown('</div>', unsafe_allow_html=True)
1627
 
1628
  def reset_analytics_state():
1629
  """
 
1288
  # return refined_report
1289
 
1290
  # Use the 2nd analytice engine (using LLM):
1291
+ fallback_analytics = {
1292
+ "topic_insights": [],
1293
+ "student_insights": [],
1294
+ "recommended_actions": [
1295
+ {
1296
+ "action": "Review analytics generation process",
1297
+ "priority": "high",
1298
+ "target_group": "system_administrators",
1299
+ "reasoning": "Analytics generation failed",
1300
+ "expected_impact": "Restore analytics functionality"
1301
+ }
1302
+ ],
1303
+ "course_health": {
1304
+ "overall_engagement": 0,
1305
+ "critical_topics": [],
1306
+ "class_distribution": {
1307
+ "high_performers": 0,
1308
+ "average_performers": 0,
1309
+ "at_risk": 0
1310
+ }
1311
+ },
1312
+ "intervention_metrics": {
1313
+ "immediate_attention_needed": [],
1314
+ "monitoring_required": []
1315
+ }
1316
+ }
1317
  analytics_generator = NovaScholarAnalytics()
1318
  analytics2 = analytics_generator.generate_analytics(all_chat_histories, topics)
1319
  # enriched_analytics = analytics_generator._enrich_analytics(analytics2)
1320
  print("Analytics is: ", analytics2)
1321
+
1322
+ if analytics2 == fallback_analytics:
1323
+ return None
1324
+ else:
1325
+ return analytics2
1326
  # print(json.dumps(analytics, indent=2))
1327
 
1328
 
 
1335
  # Initialize or get analytics data from session state
1336
  if 'analytics_data' not in st.session_state:
1337
  st.session_state.analytics_data = get_preclass_analytics(session)
1338
+ if 'topic_indices'not in st.session_state:
1339
+ st.session_state.topic_indices = None
1340
 
1341
+ if st.session_state.analytics_data:
1342
+ analytics = st.session_state.analytics_data
1343
+ else:
1344
+ st.info("No analytics data found for this session.")
1345
+ return
1346
 
1347
+ print(analytics)
1348
  # Enhanced CSS for better styling and interactivity
1349
  st.markdown("""
1350
  <style>
 
1534
  if 'topic_indices' not in st.session_state:
1535
  st.session_state.topic_indices = list(range(len(analytics["topic_wise_insights"])))
1536
 
1537
+ if st.session_state.topic_indices:
1538
+ st.markdown('<div class="topic-list">', unsafe_allow_html=True)
1539
+ for idx in st.session_state.topic_indices:
1540
+ topic = analytics["topic_wise_insights"][idx]
1541
+ topic_id = f"topic_{idx}"
1542
+
1543
+ # Create clickable header
1544
+ col1, col2 = st.columns([3, 1])
1545
+ with col1:
1546
+ if st.button(
1547
+ topic["topic"],
1548
+ key=f"topic_button_{idx}",
1549
+ use_container_width=True,
1550
+ type="secondary"
1551
+ ):
1552
+ st.session_state.expanded_topic = topic_id if st.session_state.expanded_topic != topic_id else None
1553
+
1554
+ with col2:
1555
+ st.markdown(f"""
1556
+ <div style="text-align: right;">
1557
+ <span class="topic-struggling-rate">{topic["struggling_percentage"]*100:.1f}% Struggling</span>
1558
+ </div>
1559
+ """, unsafe_allow_html=True)
1560
+
1561
+ # Show content if topic is expanded
1562
+ if st.session_state.expanded_topic == topic_id:
1563
+ st.markdown(f"""
1564
+ <div class="topic-content">
1565
+ <div class="section-heading">Key Issues</div>
1566
+ <ul>
1567
+ {"".join([f"<li>{issue}</li>" for issue in topic["key_issues"]])}
1568
+ </ul>
1569
+ <div class="section-heading">Key Misconceptions</div>
1570
+ <ul>
1571
+ {"".join([f"<li>{misc}</li>" for misc in topic["key_misconceptions"]])}
1572
+ </ul>
1573
+ </div>
1574
+ """, unsafe_allow_html=True)
1575
+ st.markdown('</div>', unsafe_allow_html=True)
1576
 
1577
+ # AI Recommendations Section
1578
+ st.markdown('<h2 class="section-title">AI-Powered Recommendations</h2>', unsafe_allow_html=True)
1579
+ st.markdown('<div class="recommendation-grid">', unsafe_allow_html=True)
1580
+ for idx, rec in enumerate(analytics["ai_recommended_actions"]):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1581
  st.markdown(f"""
1582
+ <div class="recommendation-card">
1583
+ <h4>
1584
+ <span>Recommendation {idx + 1}</span>
1585
+ <span class="priority-badge">{rec["priority"]}</span>
1586
+ </h4>
1587
+ <p>{rec["action"]}</p>
1588
+ <p><span class="reason">Reason:</span> {rec["reasoning"]}</p>
1589
+ <p><span class="reason">Expected Outcome:</span> {rec["expected_outcome"]}</p>
 
1590
  </div>
1591
  """, unsafe_allow_html=True)
1592
+ st.markdown('</div>', unsafe_allow_html=True)
1593
 
1594
+ # Student Analytics Section
1595
+ st.markdown('<h2 class="section-title">Student Analytics</h2>', unsafe_allow_html=True)
1596
+
1597
+ # Filters
1598
+ with st.container():
1599
+ # st.markdown('<div class="student-filters">', unsafe_allow_html=True)
1600
+ col1, col2, col3 = st.columns(3)
1601
+ with col1:
1602
+ concept_understanding = st.selectbox(
1603
+ "Filter by Understanding",
1604
+ ["All", "Strong", "Moderate", "Needs Improvement"]
1605
+ )
1606
+ with col2:
1607
+ participation_level = st.selectbox(
1608
+ "Filter by Participation",
1609
+ ["All", "High (>80%)", "Medium (50-80%)", "Low (<50%)"]
1610
+ )
1611
+ with col3:
1612
+ struggling_topic = st.selectbox(
1613
+ "Filter by Struggling Topic",
1614
+ ["All"] + list(set([topic for student in analytics["student_analytics"]
1615
+ for topic in student["struggling_topics"]]))
1616
+ )
1617
+ # st.markdown('</div>', unsafe_allow_html=True)
1618
+
1619
+ # Display student metrics in a grid
1620
+ st.markdown('<div class="analytics-grid">', unsafe_allow_html=True)
1621
+ for student in analytics["student_analytics"]:
1622
+ # Apply filters
1623
+ if (concept_understanding != "All" and
1624
+ student["engagement_metrics"]["concept_understanding"].replace("_", " ").title() != concept_understanding):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1625
  continue
1626
 
1627
+ participation = student["engagement_metrics"]["participation_level"] * 100
1628
+ if participation_level != "All":
1629
+ if participation_level == "High (>80%)" and participation <= 80:
1630
+ continue
1631
+ elif participation_level == "Medium (50-80%)" and (participation < 50 or participation > 80):
1632
+ continue
1633
+ elif participation_level == "Low (<50%)" and participation >= 50:
1634
+ continue
1635
+
1636
+ if struggling_topic != "All" and struggling_topic not in student["struggling_topics"]:
1637
+ continue
1638
 
1639
+ st.markdown(f"""
1640
+ <div class="student-metrics-card">
1641
+ <div class="header">
1642
+ <span class="student-id">Student {student["student_id"][-6:]}</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
1643
  </div>
1644
+ <div class="metrics-grid">
1645
+ <div class="metric-box">
1646
+ <div class="label">Participation</div>
1647
+ <div class="value">{student["engagement_metrics"]["participation_level"]*100:.1f}%</div>
1648
+ </div>
1649
+ <div class="metric-box">
1650
+ <div class="label">Understanding</div>
1651
+ <div class="value">{student["engagement_metrics"]["concept_understanding"].replace('_', ' ').title()}</div>
1652
+ </div>
1653
+ <div class="struggling-topics">
1654
+ <div class="label">Struggling Topics: </div>
1655
+ <div class="value">{", ".join(student["struggling_topics"]) if student["struggling_topics"] else "None"}</div>
1656
+ </div>
1657
+ <div class="recommendation-text">
1658
+ {student["personalized_recommendation"]}
1659
+ </div>
1660
  </div>
1661
  </div>
1662
+ """, unsafe_allow_html=True)
1663
+ st.markdown('</div>', unsafe_allow_html=True)
 
1664
 
1665
  def reset_analytics_state():
1666
  """