Spaces:
Sleeping
Sleeping
| # pages/enter_results.py | |
| import streamlit as st | |
| import pandas as pd | |
| import json | |
| from datetime import datetime | |
| from modules.database import Session, User, Student, AcademicTerm, ComponentMark, ENGINE, log_audit | |
| from modules.grading import (calculate_compiled_score, convert_to_base, compute_total, | |
| get_grade, update_compiled_marks) | |
| def show(): | |
| st.header("βοΈ Enter Results") | |
| session = Session() | |
| user = session.query(User).get(st.session_state.user_id) | |
| my_subjects = [s.strip() for s in user.subjects_taught.split(',')] if user.subjects_taught else [] | |
| if not my_subjects: | |
| st.warning("You have no subjects assigned. Please contact administrator.") | |
| session.close() | |
| st.stop() | |
| active_term = session.query(AcademicTerm).filter_by(is_active=True).first() | |
| if not active_term: | |
| st.warning("No active term set. Please contact administrator.") | |
| session.close() | |
| st.stop() | |
| st.info(f"π Current Term: **{active_term.term_name}** (ID: {active_term.id})") | |
| # Select Class | |
| classes = pd.read_sql("SELECT DISTINCT class_name FROM students ORDER BY class_name", ENGINE) | |
| if classes.empty: | |
| st.info("No students enrolled yet") | |
| session.close() | |
| st.stop() | |
| st.markdown("### Step 1: Select Class") | |
| selected_class = st.selectbox("Class", classes['class_name'].tolist(), label_visibility="collapsed") | |
| # Select what to enter | |
| st.markdown("### Step 2: Select What to Enter") | |
| result_types = ["Coursework (CW)", "Mid of Term (MOT)", "End of Term (EOT)", "All Three"] | |
| selected_type = st.selectbox("Result Type", result_types, label_visibility="collapsed") | |
| # Select Subject | |
| st.markdown("### Step 3: Select Subject") | |
| selected_subject = st.selectbox("Subject", my_subjects, label_visibility="collapsed") | |
| # Get students in this class | |
| students_in_class = pd.read_sql( | |
| f"SELECT id, name, year, subjects FROM students WHERE class_name = '{selected_class}'", | |
| ENGINE | |
| ) | |
| def has_subject(subjects_json, subject): | |
| try: | |
| subjects = json.loads(subjects_json) | |
| return subject in subjects | |
| except: | |
| return False | |
| students_in_class['has_subject'] = students_in_class['subjects'].apply( | |
| lambda x: has_subject(x, selected_subject) | |
| ) | |
| students_with_subject = students_in_class[students_in_class['has_subject']] | |
| if students_with_subject.empty: | |
| st.warning(f"No students in {selected_class} take {selected_subject}") | |
| session.close() | |
| st.stop() | |
| st.markdown("---") | |
| st.subheader(f"Enter {selected_type} for {selected_class} - {selected_subject}") | |
| # Show existing marks for verification | |
| with st.expander("π View Existing Marks for This Class/Subject"): | |
| existing_marks_query = f""" | |
| SELECT s.name, m.coursework_out_of_20, m.midterm_out_of_20, | |
| m.endterm_out_of_60, m.total, m.grade | |
| FROM marks m | |
| JOIN students s ON m.student_id = s.id | |
| WHERE m.term_id = {active_term.id} | |
| AND m.subject = '{selected_subject}' | |
| AND s.class_name = '{selected_class}' | |
| ORDER BY s.name | |
| """ | |
| existing_marks_df = pd.read_sql(existing_marks_query, ENGINE) | |
| if not existing_marks_df.empty: | |
| st.dataframe(existing_marks_df, use_container_width=True) | |
| else: | |
| st.info("No marks entered yet for this class/subject combination") | |
| # Component marks entry system | |
| st.markdown("---") | |
| st.subheader("π Enter Component Marks") | |
| st.info("Enter individual test/paper scores below. The system will automatically compile them into the final scores.") | |
| # Select student | |
| student_name = st.selectbox("Select Student", students_with_subject['name'].tolist()) | |
| student_id = int(students_with_subject[students_with_subject['name'] == student_name].iloc[0]['id']) | |
| # Tabs for different component types | |
| tab1, tab2, tab3, tab4 = st.tabs(["Coursework", "Mid-term", "End-term", "Compile Final Marks"]) | |
| with tab1: | |
| if selected_type in ["Coursework (CW)", "All Three"]: | |
| st.markdown("### π Coursework Components") | |
| # Get existing component marks for coursework | |
| cw_components = pd.read_sql(f""" | |
| SELECT id, component_name, score, total | |
| FROM component_marks | |
| WHERE student_id = {student_id} | |
| AND subject = '{selected_subject}' | |
| AND term_id = {active_term.id} | |
| AND component_type = 'coursework' | |
| ORDER BY component_name | |
| """, ENGINE) | |
| if not cw_components.empty: | |
| st.subheader("Existing Coursework Components") | |
| st.dataframe(cw_components, use_container_width=True) | |
| # Add new component | |
| with st.form("add_cw_component"): | |
| st.markdown("#### Add New Coursework Component") | |
| component_name = st.text_input("Component Name (e.g., Test 1, Assignment 1)") | |
| score = st.number_input("Score", 0.0, 1000.0, step=0.5) | |
| total = st.number_input("Total Marks", 0.1, 1000.0, step=0.5) | |
| if st.form_submit_button("Add Coursework Component"): | |
| if component_name and total > 0: | |
| new_component = ComponentMark( | |
| student_id=student_id, | |
| subject=selected_subject, | |
| term_id=active_term.id, | |
| component_type='coursework', | |
| component_name=component_name, | |
| score=score, | |
| total=total, | |
| submitted_by=st.session_state.user_id | |
| ) | |
| session.add(new_component) | |
| session.commit() | |
| log_audit(session, st.session_state.user_id, "add_cw_component", | |
| f"{student_name} - {selected_subject} - {component_name}") | |
| st.success(f"β Coursework component added: {component_name}") | |
| st.rerun() | |
| else: | |
| st.error("Please provide a component name and total marks") | |
| # Delete component | |
| if not cw_components.empty: | |
| st.markdown("#### Delete Coursework Component") | |
| component_to_delete = st.selectbox("Select Component to Delete", | |
| cw_components['id'].tolist(), | |
| format_func=lambda x: cw_components[cw_components['id']==x]['component_name'].iloc[0]) | |
| if st.button("Delete Selected Component", key="delete_cw"): | |
| component = session.query(ComponentMark).get(component_to_delete) | |
| if component: | |
| component_name = component.component_name | |
| session.delete(component) | |
| session.commit() | |
| log_audit(session, st.session_state.user_id, "delete_cw_component", | |
| f"{student_name} - {selected_subject} - {component_name}") | |
| st.success(f"β Component deleted: {component_name}") | |
| st.rerun() | |
| with tab2: | |
| if selected_type in ["Mid of Term (MOT)", "All Three"]: | |
| st.markdown("### π Mid of Term Components") | |
| mt_components = pd.read_sql(f""" | |
| SELECT id, component_name, score, total | |
| FROM component_marks | |
| WHERE student_id = {student_id} | |
| AND subject = '{selected_subject}' | |
| AND term_id = {active_term.id} | |
| AND component_type = 'midterm' | |
| ORDER BY component_name | |
| """, ENGINE) | |
| if not mt_components.empty: | |
| st.subheader("Existing Mid-term Components") | |
| st.dataframe(mt_components, use_container_width=True) | |
| with st.form("add_mt_component"): | |
| st.markdown("#### Add New Mid-term Component") | |
| component_name = st.text_input("Component Name (e.g., Paper 1, Paper 2)") | |
| score = st.number_input("Score", 0.0, 1000.0, step=0.5) | |
| total = st.number_input("Total Marks", 0.1, 1000.0, step=0.5) | |
| if st.form_submit_button("Add Mid-term Component"): | |
| if component_name and total > 0: | |
| new_component = ComponentMark( | |
| student_id=student_id, | |
| subject=selected_subject, | |
| term_id=active_term.id, | |
| component_type='midterm', | |
| component_name=component_name, | |
| score=score, | |
| total=total, | |
| submitted_by=st.session_state.user_id | |
| ) | |
| session.add(new_component) | |
| session.commit() | |
| log_audit(session, st.session_state.user_id, "add_mt_component", | |
| f"{student_name} - {selected_subject} - {component_name}") | |
| st.success(f"β Mid-term component added: {component_name}") | |
| st.rerun() | |
| else: | |
| st.error("Please provide a component name and total marks") | |
| if not mt_components.empty: | |
| st.markdown("#### Delete Mid-term Component") | |
| component_to_delete = st.selectbox("Select Component to Delete", | |
| mt_components['id'].tolist(), | |
| format_func=lambda x: mt_components[mt_components['id']==x]['component_name'].iloc[0], | |
| key="mt_delete_select") | |
| if st.button("Delete Selected Component", key="delete_mt"): | |
| component = session.query(ComponentMark).get(component_to_delete) | |
| if component: | |
| component_name = component.component_name | |
| session.delete(component) | |
| session.commit() | |
| log_audit(session, st.session_state.user_id, "delete_mt_component", | |
| f"{student_name} - {selected_subject} - {component_name}") | |
| st.success(f"β Component deleted: {component_name}") | |
| st.rerun() | |
| with tab3: | |
| if selected_type in ["End of Term (EOT)", "All Three"]: | |
| st.markdown("### π End of Term Components") | |
| et_components = pd.read_sql(f""" | |
| SELECT id, component_name, score, total | |
| FROM component_marks | |
| WHERE student_id = {student_id} | |
| AND subject = '{selected_subject}' | |
| AND term_id = {active_term.id} | |
| AND component_type = 'endterm' | |
| ORDER BY component_name | |
| """, ENGINE) | |
| if not et_components.empty: | |
| st.subheader("Existing End-term Components") | |
| st.dataframe(et_components, use_container_width=True) | |
| with st.form("add_et_component"): | |
| st.markdown("#### Add New End-term Component") | |
| component_name = st.text_input("Component Name (e.g., Paper 1, Paper 2)") | |
| score = st.number_input("Score", 0.0, 1000.0, step=0.5) | |
| total = st.number_input("Total Marks", 0.1, 1000.0, step=0.5) | |
| if st.form_submit_button("Add End-term Component"): | |
| if component_name and total > 0: | |
| new_component = ComponentMark( | |
| student_id=student_id, | |
| subject=selected_subject, | |
| term_id=active_term.id, | |
| component_type='endterm', | |
| component_name=component_name, | |
| score=score, | |
| total=total, | |
| submitted_by=st.session_state.user_id | |
| ) | |
| session.add(new_component) | |
| session.commit() | |
| log_audit(session, st.session_state.user_id, "add_et_component", | |
| f"{student_name} - {selected_subject} - {component_name}") | |
| st.success(f"β End-term component added: {component_name}") | |
| st.rerun() | |
| else: | |
| st.error("Please provide a component name and total marks") | |
| if not et_components.empty: | |
| st.markdown("#### Delete End-term Component") | |
| component_to_delete = st.selectbox("Select Component to Delete", | |
| et_components['id'].tolist(), | |
| format_func=lambda x: et_components[et_components['id']==x]['component_name'].iloc[0], | |
| key="et_delete_select") | |
| if st.button("Delete Selected Component", key="delete_et"): | |
| component = session.query(ComponentMark).get(component_to_delete) | |
| if component: | |
| component_name = component.component_name | |
| session.delete(component) | |
| session.commit() | |
| log_audit(session, st.session_state.user_id, "delete_et_component", | |
| f"{student_name} - {selected_subject} - {component_name}") | |
| st.success(f"β Component deleted: {component_name}") | |
| st.rerun() | |
| with tab4: | |
| st.markdown("### π Compile and Update Final Marks") | |
| # Get students with components | |
| students_with_components = pd.read_sql(f""" | |
| SELECT DISTINCT s.id, s.name | |
| FROM students s | |
| JOIN component_marks cm ON s.id = cm.student_id | |
| WHERE s.class_name = '{selected_class}' | |
| AND cm.subject = '{selected_subject}' | |
| AND cm.term_id = {active_term.id} | |
| ORDER BY s.name | |
| """, ENGINE) | |
| if students_with_components.empty: | |
| st.info("No students with component marks found. Add component marks first.") | |
| else: | |
| selected_student_compile = st.selectbox("Select Student to Compile", | |
| students_with_components['name'].tolist(), | |
| key="compile_student_select") | |
| student_id_compile = int(students_with_components[students_with_components['name'] == selected_student_compile].iloc[0]['id']) | |
| # Show current compiled scores | |
| cw_score, cw_total = calculate_compiled_score(session, student_id_compile, selected_subject, active_term.id, 'coursework') | |
| mt_score, mt_total = calculate_compiled_score(session, student_id_compile, selected_subject, active_term.id, 'midterm') | |
| et_score, et_total = calculate_compiled_score(session, student_id_compile, selected_subject, active_term.id, 'endterm') | |
| cw_out_of_20 = convert_to_base(cw_score, cw_total, 20) | |
| mt_out_of_20 = convert_to_base(mt_score, mt_total, 20) | |
| et_out_of_60 = convert_to_base(et_score, et_total, 60) | |
| total = compute_total(cw_out_of_20, mt_out_of_20, et_out_of_60) | |
| grade = get_grade(total) | |
| st.subheader(f"Compiled Scores for {selected_student_compile}") | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| st.metric("Coursework (out of 20)", f"{cw_out_of_20:.1f}") | |
| st.write(f"Based on {cw_score:.1f}/{cw_total:.1f}") | |
| with col2: | |
| st.metric("Mid-term (out of 20)", f"{mt_out_of_20:.1f}") | |
| st.write(f"Based on {mt_score:.1f}/{mt_total:.1f}") | |
| with col3: | |
| st.metric("End-term (out of 60)", f"{et_out_of_60:.1f}") | |
| st.write(f"Based on {et_score:.1f}/{et_total:.1f}") | |
| col4, col5 = st.columns(2) | |
| with col4: | |
| st.metric("Total (out of 100)", f"{total:.1f}") | |
| with col5: | |
| st.metric("Grade", grade) | |
| if st.button(f"πΎ Update Final Marks for {selected_student_compile}", use_container_width=True): | |
| try: | |
| total, grade = update_compiled_marks(session, student_id_compile, selected_subject, active_term.id) | |
| log_audit(session, st.session_state.user_id, "update_compiled_marks", | |
| f"{selected_student_compile} - {selected_subject} - {active_term.term_name}") | |
| st.success(f"β Final marks updated for {selected_student_compile}!") | |
| st.success(f"π Total: {total:.1f}/100 | Grade: {grade}") | |
| st.rerun() | |
| except Exception as e: | |
| st.error(f"β Error updating marks: {str(e)}") | |
| session.close() |