def practice(): if not auth.user: session.flash = "Please Login" return redirect(URL('default', 'index')) if not session.timezoneoffset: session.timezoneoffset = 0 feedback_saved = request.vars.get('feedback_saved', None) if feedback_saved is None: feedback_saved = "" (now, now_local, message1, message2, practice_graded, spacing, interleaving, practice_completion_count, remaining_days, max_days, max_questions, day_points, question_points, presentable_flashcards, available_flashcards_num, practiced_today_count, questions_to_complete_day, practice_today_left, points_received, total_possible_points, flashcard_creation_method) = _get_practice_data(auth.user, float(session.timezoneoffset) if 'timezoneoffset' in session else 0) if message1 != "": session.flash = message1 + " " + message2 return redirect(URL('practiceNotStartedYet', vars=dict(message1=message1, message2=message2))) # Since each authenticated user has only one active course, we retrieve the course this way. course = db(db.courses.id == auth.user.course_id).select().first() all_flashcards = db((db.user_topic_practice.course_name == auth.user.course_name) & (db.user_topic_practice.user_id == auth.user.id) & (db.user_topic_practice.chapter_label == db.chapters.chapter_label) & (db.user_topic_practice.sub_chapter_label == db.sub_chapters.sub_chapter_label) & (db.chapters.course_id == auth.user.course_name) & (db.sub_chapters.chapter_id == db.chapters.id)) \ .select(db.chapters.chapter_name, db.sub_chapters.sub_chapter_name, db.user_topic_practice.i_interval, db.user_topic_practice.next_eligible_date, db.user_topic_practice.e_factor, db.user_topic_practice.q, db.user_topic_practice.last_completed, orderby=db.user_topic_practice.id) for f_card in all_flashcards: if interleaving == 1: f_card["remaining_days"] = max(0, (f_card.user_topic_practice.next_eligible_date - now_local.date()).days) # f_card["mastery_percent"] = int(100 * f_card["remaining_days"] // 55) f_card["mastery_percent"] = int(f_card["remaining_days"]) else: # The maximum q is 5.0 and the minimum e_factor that indicates mastery of the topic is 2.5. `5 * 2.5 = 12.5` # I learned that when students under the blocking condition answer something wrong multiple times, # it becomes too difficult for them to pass it and the system asks them the same question many times # (because most subchapters have only one question). To solve this issue, I changed the blocking formula. f_card["mastery_percent"] = int(100 * f_card.user_topic_practice.e_factor * f_card.user_topic_practice.q / 12.5) if f_card["mastery_percent"] > 100: f_card["mastery_percent"] = 100 f_card["mastery_color"] = "danger" if f_card["mastery_percent"] >= 75: f_card["mastery_color"] = "success" elif f_card["mastery_percent"] >= 50: f_card["mastery_color"] = "info" elif f_card["mastery_percent"] >= 25: f_card["mastery_color"] = "warning" # If the student has any flashcards to practice and has not practiced enough to get their points for today or they # have intrinsic motivation to practice beyond what they are expected to do. if available_flashcards_num > 0 and (practiced_today_count != questions_to_complete_day or request.vars.willing_to_continue or spacing == 0): # Present the first one. flashcard = presentable_flashcards[0] # Get eligible questions. questions = _get_qualified_questions(course.base_course, flashcard.chapter_label, flashcard.sub_chapter_label) # Find index of the last question asked. question_names = [q.name for q in questions] qIndex = question_names.index(flashcard.question_name) # present the next one in the list after the last one that was asked question = questions[(qIndex + 1) % len(questions)] # This replacement is to render images question.htmlsrc = bytes(question.htmlsrc).decode('utf8').replace('src="../_static/', 'src="../static/' + course[ 'course_name'] + '/_static/') question.htmlsrc = question.htmlsrc.replace("../_images", "/{}/static/{}/_images".format(request.application, course.course_name)) autogradable = 1 # If it is possible to autograde it: if ((question.autograde is not None) or (question.question_type is not None and question.question_type in ['mchoice', 'parsonsprob', 'fillintheblank', 'clickablearea', 'dragndrop'])): autogradable = 2 questioninfo = [question.htmlsrc, question.name, question.id, autogradable] # This is required to check the same question in do_check_answer(). flashcard.question_name = question.name # This is required to only check answers after this timestamp in do_check_answer(). flashcard.last_presented = now flashcard.timezoneoffset = float(session.timezoneoffset) if 'timezoneoffset' in session else 0 flashcard.update_record() else: questioninfo = None # Add a practice completion record for today, if there isn't one already. practice_completion_today = db((db.user_topic_practice_Completion.course_name == auth.user.course_name) & (db.user_topic_practice_Completion.user_id == auth.user.id) & (db.user_topic_practice_Completion.practice_completion_date == now_local.date())) if practice_completion_today.isempty(): db.user_topic_practice_Completion.insert( user_id=auth.user.id, course_name=auth.user.course_name, practice_completion_date=now_local.date() ) if practice_graded == 1: # send practice grade via lti, if setup for that lti_record = _get_lti_record(session.oauth_consumer_key) practice_grade = _get_student_practice_grade(auth.user.id, auth.user.course_name) course_settings = _get_course_practice_record(auth.user.course_name) practice_completion_count = _get_practice_completion(auth.user.id, auth.user.course_name, spacing) if spacing == 1: total_possible_points = day_points * max_days points_received = day_points * practice_completion_count else: total_possible_points = question_points * max_questions points_received = question_points * practice_completion_count if lti_record and \ practice_grade and \ practice_grade.lis_outcome_url and \ practice_grade.lis_result_sourcedid and \ course_settings: if spacing == 1: send_lti_grade(assignment_points=max_days, score=practice_completion_count, consumer=lti_record.consumer, secret=lti_record.secret, outcome_url=practice_grade.lis_outcome_url, result_sourcedid=practice_grade.lis_result_sourcedid) else: send_lti_grade(assignment_points=max_questions, score=practice_completion_count, consumer=lti_record.consumer, secret=lti_record.secret, outcome_url=practice_grade.lis_outcome_url, result_sourcedid=practice_grade.lis_result_sourcedid) return dict(course=course, q=questioninfo, all_flashcards=all_flashcards, flashcard_count=available_flashcards_num, # The number of days the student has completed their practice. practice_completion_count=practice_completion_count, remaining_days=remaining_days, max_questions=max_questions, max_days=max_days, # The number of times remaining to practice today to get the completion point. practice_today_left=practice_today_left, # The number of times this user has submitted their practice from the beginning of today (12:00 am) # till now. practiced_today_count=practiced_today_count, total_today_count=min(practice_today_left + practiced_today_count, questions_to_complete_day), questions_to_complete_day=questions_to_complete_day, points_received=points_received, total_possible_points=total_possible_points, practice_graded=practice_graded, spacing=spacing, interleaving=interleaving, flashcard_creation_method=flashcard_creation_method, feedback_saved=feedback_saved)
def practice(): if not session.timezoneoffset: session.timezoneoffset = 0 feedback_saved = request.vars.get("feedback_saved", None) if feedback_saved is None: feedback_saved = "" ( now, now_local, message1, message2, practice_graded, spacing, interleaving, practice_completion_count, remaining_days, max_days, max_questions, day_points, question_points, presentable_flashcards, available_flashcards_num, practiced_today_count, questions_to_complete_day, practice_today_left, points_received, total_possible_points, flashcard_creation_method, ) = _get_practice_data( auth.user, float(session.timezoneoffset) if "timezoneoffset" in session else 0, db, ) try: db.useinfo.insert( sid=auth.user.username, act=message1 or "beginning practice", div_id="/runestone/assignments/practice", event="practice", timestamp=datetime.datetime.utcnow(), course_id=auth.user.course_name, ) except Exception as e: logger.error(f"failed to insert log record for practice: {e}") if message1 != "": # session.flash = message1 + " " + message2 return redirect( URL("practiceNotStartedYet", vars=dict(message1=message1, message2=message2))) # Since each authenticated user has only one active course, we retrieve the course this way. course = db(db.courses.id == auth.user.course_id).select().first() all_flashcards = db( (db.user_topic_practice.course_name == auth.user.course_name) & (db.user_topic_practice.user_id == auth.user.id) & (db.user_topic_practice.chapter_label == db.chapters.chapter_label) & (db.user_topic_practice.sub_chapter_label == db.sub_chapters.sub_chapter_label) & (db.chapters.course_id == course.base_course) & (db.sub_chapters.chapter_id == db.chapters.id)).select( db.chapters.chapter_name, db.sub_chapters.sub_chapter_name, db.user_topic_practice.i_interval, db.user_topic_practice.next_eligible_date, db.user_topic_practice.e_factor, db.user_topic_practice.q, db.user_topic_practice.last_completed, orderby=db.user_topic_practice.id, ) for f_card in all_flashcards: if interleaving == 1: f_card["remaining_days"] = max( 0, (f_card.user_topic_practice.next_eligible_date - now_local.date()).days, ) # f_card["mastery_percent"] = int(100 * f_card["remaining_days"] // 55) f_card["mastery_percent"] = int(f_card["remaining_days"]) else: # The maximum q is 5.0 and the minimum e_factor that indicates mastery of the topic is 2.5. `5 * 2.5 = 12.5` # I learned that when students under the blocking condition answer something wrong multiple times, # it becomes too difficult for them to pass it and the system asks them the same question many times # (because most subchapters have only one question). To solve this issue, I changed the blocking formula. f_card["mastery_percent"] = int( 100 * f_card.user_topic_practice.e_factor * f_card.user_topic_practice.q / 12.5) if f_card["mastery_percent"] > 100: f_card["mastery_percent"] = 100 f_card["mastery_color"] = "danger" if f_card["mastery_percent"] >= 75: f_card["mastery_color"] = "success" elif f_card["mastery_percent"] >= 50: f_card["mastery_color"] = "info" elif f_card["mastery_percent"] >= 25: f_card["mastery_color"] = "warning" # If an instructor removes the practice flag from a question in the middle of the semester # and students are in the middle of practicing it, the following code makes sure the practice tool does not crash. questions = [] if len(presentable_flashcards) > 0: # Present the first one. flashcard = presentable_flashcards[0] # Get eligible questions. questions = _get_qualified_questions(course.base_course, flashcard.chapter_label, flashcard.sub_chapter_label, db) # If the student has any flashcards to practice and has not practiced enough to get their points for today or they # have intrinsic motivation to practice beyond what they are expected to do. if (available_flashcards_num > 0 and len(questions) > 0 and (practiced_today_count != questions_to_complete_day or request.vars.willing_to_continue or spacing == 0)): # Find index of the last question asked. question_names = [q.name for q in questions] try: qIndex = question_names.index(flashcard.question_name) except Exception: qIndex = 0 # present the next one in the list after the last one that was asked question = questions[(qIndex + 1) % len(questions)] # This replacement is to render images question.htmlsrc = question.htmlsrc.replace( 'src="../_static/', 'src="' + get_course_url("_static/")) question.htmlsrc = question.htmlsrc.replace("../_images/", get_course_url("_images/")) autogradable = 1 # If it is possible to autograde it: if (question.autograde is not None) or (question.question_type is not None and question.question_type in [ "mchoice", "parsonsprob", "fillintheblank", "clickablearea", "dragndrop", ]): autogradable = 2 questioninfo = [ question.htmlsrc, question.name, question.id, autogradable ] # This is required to check the same question in do_check_answer(). flashcard.question_name = question.name # This is required to only check answers after this timestamp in do_check_answer(). flashcard.last_presented = now flashcard.timezoneoffset = (float(session.timezoneoffset) if "timezoneoffset" in session else 0) flashcard.update_record() else: questioninfo = None # Add a practice completion record for today, if there isn't one already. practice_completion_today = db( (db.user_topic_practice_completion.course_name == auth.user.course_name) & (db.user_topic_practice_completion.user_id == auth.user.id) & (db.user_topic_practice_completion.practice_completion_date == now_local.date())) if practice_completion_today.isempty(): db.user_topic_practice_completion.insert( user_id=auth.user.id, course_name=auth.user.course_name, practice_completion_date=now_local.date(), ) practice_completion_count = _get_practice_completion( auth.user.id, auth.user.course_name, spacing, db) if practice_graded == 1: # send practice grade via lti, if setup for that lti_record = _get_lti_record(session.oauth_consumer_key) practice_grade = _get_student_practice_grade( auth.user.id, auth.user.course_name) course_settings = _get_course_practice_record( auth.user.course_name) if spacing == 1: total_possible_points = day_points * max_days points_received = day_points * practice_completion_count else: total_possible_points = question_points * max_questions points_received = question_points * practice_completion_count if (lti_record and practice_grade and practice_grade.lis_outcome_url and practice_grade.lis_result_sourcedid and course_settings): if spacing == 1: send_lti_grade( assignment_points=max_days, score=practice_completion_count, consumer=lti_record.consumer, secret=lti_record.secret, outcome_url=practice_grade.lis_outcome_url, result_sourcedid=practice_grade. lis_result_sourcedid, ) else: send_lti_grade( assignment_points=max_questions, score=practice_completion_count, consumer=lti_record.consumer, secret=lti_record.secret, outcome_url=practice_grade.lis_outcome_url, result_sourcedid=practice_grade. lis_result_sourcedid, ) set_latex_preamble(course.base_course) return dict( course=course, q=questioninfo, all_flashcards=all_flashcards, flashcard_count=available_flashcards_num, # The number of days the student has completed their practice. practice_completion_count=practice_completion_count, remaining_days=remaining_days, max_questions=max_questions, max_days=max_days, # The number of times remaining to practice today to get the completion point. practice_today_left=practice_today_left, # The number of times this user has submitted their practice from the beginning of today (12:00 am) # till now. practiced_today_count=practiced_today_count, total_today_count=min(practice_today_left + practiced_today_count, questions_to_complete_day), questions_to_complete_day=questions_to_complete_day, points_received=points_received, total_possible_points=total_possible_points, practice_graded=practice_graded, spacing=spacing, interleaving=interleaving, flashcard_creation_method=flashcard_creation_method, feedback_saved=feedback_saved, )
def practice(): if not auth.user: session.flash = "Please Login" return redirect(URL('default', 'index')) if not session.timezoneoffset: session.timezoneoffset = 0 feedback_saved = request.vars.get('feedback_saved', None) if feedback_saved is None: feedback_saved = "" (now, now_local, message1, message2, practice_graded, spacing, interleaving, practice_completion_count, remaining_days, max_days, max_questions, day_points, question_points, presentable_flashcards, available_flashcards_num, practiced_today_count, questions_to_complete_day, practice_today_left, points_received, total_possible_points, flashcard_creation_method) = _get_practice_data(auth.user, float(session.timezoneoffset) if 'timezoneoffset' in session else 0) if message1 != "": session.flash = message1 + " " + message2 return redirect(URL('practiceNotStartedYet', vars=dict(message1=message1, message2=message2))) # Since each authenticated user has only one active course, we retrieve the course this way. course = db(db.courses.id == auth.user.course_id).select().first() all_flashcards = db((db.user_topic_practice.course_name == auth.user.course_name) & (db.user_topic_practice.user_id == auth.user.id) & (db.user_topic_practice.chapter_label == db.chapters.chapter_label) & (db.user_topic_practice.sub_chapter_label == db.sub_chapters.sub_chapter_label) & (db.chapters.course_id == auth.user.course_name) & (db.sub_chapters.chapter_id == db.chapters.id)) \ .select(db.chapters.chapter_name, db.sub_chapters.sub_chapter_name, db.user_topic_practice.i_interval, db.user_topic_practice.next_eligible_date, db.user_topic_practice.e_factor, db.user_topic_practice.q, db.user_topic_practice.last_completed, orderby=db.user_topic_practice.id) for f_card in all_flashcards: if interleaving == 1: f_card["remaining_days"] = max(0, (f_card.user_topic_practice.next_eligible_date - now_local.date()).days) # f_card["mastery_percent"] = int(100 * f_card["remaining_days"] // 55) f_card["mastery_percent"] = int(f_card["remaining_days"]) else: # The maximum q is 5.0 and the minimum e_factor that indicates mastery of the topic is 2.5. `5 * 2.5 = 12.5` # I learned that when students under the blocking condition answer something wrong multiple times, # it becomes too difficult for them to pass it and the system asks them the same question many times # (because most subchapters have only one question). To solve this issue, I changed the blocking formula. f_card["mastery_percent"] = int(100 * f_card.user_topic_practice.e_factor * f_card.user_topic_practice.q / 12.5) if f_card["mastery_percent"] > 100: f_card["mastery_percent"] = 100 f_card["mastery_color"] = "danger" if f_card["mastery_percent"] >= 75: f_card["mastery_color"] = "success" elif f_card["mastery_percent"] >= 50: f_card["mastery_color"] = "info" elif f_card["mastery_percent"] >= 25: f_card["mastery_color"] = "warning" # If the student has any flashcards to practice and has not practiced enough to get their points for today or they # have intrinsic motivation to practice beyond what they are expected to do. if available_flashcards_num > 0 and (practiced_today_count != questions_to_complete_day or request.vars.willing_to_continue or spacing == 0): # Present the first one. flashcard = presentable_flashcards[0] # Get eligible questions. questions = _get_qualified_questions(course.base_course, flashcard.chapter_label, flashcard.sub_chapter_label) # Find index of the last question asked. question_names = [q.name for q in questions] qIndex = question_names.index(flashcard.question_name) # present the next one in the list after the last one that was asked question = questions[(qIndex + 1) % len(questions)] # This replacement is to render images question.htmlsrc = bytes(question.htmlsrc).decode('utf8').replace('src="../_static/', 'src="../static/' + course[ 'course_name'] + '/_static/') question.htmlsrc = question.htmlsrc.replace("../_images", "/{}/static/{}/_images".format(request.application, course.course_name)) autogradable = 1 # If it is possible to autograde it: if ((question.autograde is not None) or (question.question_type is not None and question.question_type in ['mchoice', 'parsonsprob', 'fillintheblank', 'clickablearea', 'dragndrop'])): autogradable = 2 questioninfo = [question.htmlsrc, question.name, question.id, autogradable] # This is required to check the same question in do_check_answer(). flashcard.question_name = question.name # This is required to only check answers after this timestamp in do_check_answer(). flashcard.last_presented = now flashcard.timezoneoffset = float(session.timezoneoffset) if 'timezoneoffset' in session else 0 flashcard.update_record() else: questioninfo = None # Add a practice completion record for today, if there isn't one already. practice_completion_today = db((db.user_topic_practice_Completion.course_name == auth.user.course_name) & (db.user_topic_practice_Completion.user_id == auth.user.id) & (db.user_topic_practice_Completion.practice_completion_date == now_local.date())) if practice_completion_today.isempty(): db.user_topic_practice_Completion.insert( user_id=auth.user.id, course_name=auth.user.course_name, practice_completion_date=now_local.date() ) if practice_graded == 1: # send practice grade via lti, if setup for that lti_record = _get_lti_record(session.oauth_consumer_key) practice_grade = _get_student_practice_grade(auth.user.id, auth.user.course_name) course_settings = _get_course_practice_record(auth.user.course_name) practice_completion_count = _get_practice_completion(auth.user.id, auth.user.course_name, spacing) if spacing == 1: total_possible_points = day_points * max_days points_received = day_points * practice_completion_count else: total_possible_points = question_points * max_questions points_received = question_points * practice_completion_count if lti_record and \ practice_grade and \ practice_grade.lis_outcome_url and \ practice_grade.lis_result_sourcedid and \ course_settings: if spacing == 1: send_lti_grade(assignment_points=max_days, score=practice_completion_count, consumer=lti_record.consumer, secret=lti_record.secret, outcome_url=practice_grade.lis_outcome_url, result_sourcedid=practice_grade.lis_result_sourcedid) else: send_lti_grade(assignment_points=max_questions, score=practice_completion_count, consumer=lti_record.consumer, secret=lti_record.secret, outcome_url=practice_grade.lis_outcome_url, result_sourcedid=practice_grade.lis_result_sourcedid) return dict(course=course, course_name=auth.user.course_name, course_id=auth.user.course_name, q=questioninfo, all_flashcards=all_flashcards, flashcard_count=available_flashcards_num, # The number of days the student has completed their practice. practice_completion_count=practice_completion_count, remaining_days=remaining_days, max_questions=max_questions, max_days=max_days, # The number of times remaining to practice today to get the completion point. practice_today_left=practice_today_left, # The number of times this user has submitted their practice from the beginning of today (12:00 am) # till now. practiced_today_count=practiced_today_count, total_today_count=min(practice_today_left + practiced_today_count, questions_to_complete_day), questions_to_complete_day=questions_to_complete_day, points_received=points_received, total_possible_points=total_possible_points, practice_graded=practice_graded, spacing=spacing, interleaving=interleaving, flashcard_creation_method=flashcard_creation_method, feedback_saved=feedback_saved)