diff --git a/.gitignore b/.gitignore index dc92e65..2cbe518 100644 --- a/.gitignore +++ b/.gitignore @@ -149,4 +149,4 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. .idea/ -assignments \ No newline at end of file +scores.db diff --git a/README.md b/README.md index 86235ea..179a2f5 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ Customize, manage templates of rubrics and fast grade HTML/PDF files  + ## Usage @@ -20,8 +21,6 @@ It occupies localhost 5000 port by default. - [Customize host of Flask application](https://flask.palletsprojects.com/en/2.3.x/quickstart/) - [Customize port of Flask application](https://flask.palletsprojects.com/en/2.3.x/server/#address-already-in-use) -Read the [operation manual](manual.md) to preview software functions. - **Release:** 1. Download and unzip the release. diff --git a/app.py b/app.py index bcf94b2..a9b7f6a 100644 --- a/app.py +++ b/app.py @@ -1,114 +1,122 @@ -import re -import pandas as pd -from flask import Flask, render_template, request, send_file, redirect -import os import json +import os +import sqlite3 import webbrowser +import pandas as pd +from flask import Flask, render_template, request, send_file, redirect +from winapi import select_folder -app = Flask(__name__) -assignments = './assignments/' -os.makedirs(assignments, exist_ok=True) -supported_file_types = ('.htm', '.html', '.pdf', '.txt') - - -@app.route('/', methods=['GET']) -def index(): # put application's code here - existed_configs = [fn for fn in os.listdir(assignments) if os.path.splitext(fn)[1] == '.json'] - return render_template('index.html', existed_configs=existed_configs) +def write_sql(db_path, sql, *args): + c = sqlite3.connect(db_path) + r = c.cursor() + r.execute(sql, args) + r.close() + c.commit() + c.close() -@app.route('/delete-config', methods=['GET']) -def delete_config(): - config_path = os.path.join(assignments, request.args.get('name', '')) - if os.path.isfile(config_path): - os.remove(config_path) - return redirect('/') +def read_sql(db_path, sql, *args, one=False): + c = sqlite3.connect(db_path) + r = c.cursor() + r.execute(sql, args) + rst = r.fetchone() if one else r.fetchall() + r.close() + c.close() + return rst -@app.route('/export-config', methods=['GET']) -def export_config(): - config_path = os.path.join(assignments, request.args.get('config', '')) - if os.path.isfile(config_path): - return send_file(config_path, as_attachment=True) - else: - return redirect('/') +app = Flask(__name__) +config_db = "scores.db" +supported_file_types = ('.htm', '.html', '.pdf', '.txt') -@app.route('/import-config', methods=['POST']) -def import_config(): - config = request.files['config'] - json_name = config.filename - cand_saved_path = os.path.join(assignments, json_name) - if os.path.exists(cand_saved_path): - return 'Config file has already existed.' - config.save(cand_saved_path) - return redirect('/') +if not os.path.exists(config_db): + with open("sql/initialize.sql", "r") as f: + commands = f.read().split(';') + for command in commands: + write_sql(config_db, command) +with open("sql/check_files_scored.sql", "r") as f: + check_files_scored = f.read() -def config_pull(config_name): - if not config_name: - return - config_path = os.path.join(assignments, config_name) - if not os.path.isfile(config_path): - return - with open(config_path, 'r') as f: - config = json.load(f) - return config +@app.route('/', methods=['GET']) +def index(): + assignments = read_sql(config_db, "SELECT assignment_id, assignment_name FROM assignments") + return render_template('index.html', existed_configs=assignments) -def config_push(config_name, config_content): - if not config_name: - return - config_path = os.path.join(assignments, config_name) - with open(config_path, 'w') as f: - json.dump(config_content, f, indent=4) +@app.route('/delete-config', methods=['GET']) +def delete_config(): + agn_id = request.args.get('id') + if agn_id: + write_sql(config_db, "DELETE FROM assignments WHERE assignment_id = ?", agn_id) + write_sql(config_db, "DELETE FROM files WHERE assignment_id = ?", agn_id) + write_sql(config_db, "DELETE FROM rubric WHERE assignment_id = ?", agn_id) + write_sql(config_db, "DELETE FROM scores WHERE assignment_id = ?", agn_id) + return redirect('/') @app.route('/config-add', methods=['POST']) def config_add(): name = request.form.get('name') if not name: - return 'Name is empty.' - json_name = re.sub(r'\s', '_', name).lower() + '.json' - cand_saved_path = os.path.join(assignments, json_name) - if os.path.exists(cand_saved_path): - return 'Config file has already existed.' - config = {'base_dir': '', 'name': name, 'rubric': [], 'scores': {}, 'notes': {}, - 'id': 0, 'filenames': []} - config_push(json_name, config) - return redirect('/') - - -@app.route('/config-check-name', methods=['POST']) -def config_check_name(): - config_name = request.form.get('config_name') - config = config_pull(config_name) - if not config: - return 'Config doesn\'t exist.' + return 'Assignment name is empty.' + n_existed = read_sql(config_db, "SELECT count(*) FROM assignments WHERE assignment_name = ?", name, one=True) + n_existed = n_existed[0] + if n_existed == 0: + write_sql(config_db, "INSERT INTO assignments (assignment_name) VALUES (?)", name) + agn_id = read_sql(config_db, "SELECT assignment_id FROM assignments WHERE assignment_name = ?", name, one=True) + agn_id = agn_id[0] base_dir = request.form.get('base_dir') if not (base_dir and os.path.isdir(base_dir)): - return 'The base directory does not exist.' - config['base_dir'] = base_dir - assignment_file_names = [fn for fn in os.listdir(base_dir) if os.path.splitext(fn)[1] in supported_file_types] - if not assignment_file_names: - return f'The base directory is empty. Supported file types {supported_file_types}.' - config["filenames"] = assignment_file_names - rebuild = request.form.get('rebuild') - if rebuild: - config['scores'] = {} - config['notes'] = {} - config_push(config_name, config) + return 'The base directory doesn\'t exist.' + for fn in os.listdir(base_dir): + display_name, ext_name = os.path.splitext(fn) + if ext_name in supported_file_types: + write_sql(config_db, "INSERT INTO files (file_name, display_name, assignment_id) VALUES (?, ?, ?)", + fn, display_name, agn_id) + pointer = read_sql(config_db, "SELECT min(file_id) FROM files WHERE assignment_id = ?", agn_id, one=True) + pointer = pointer[0] + if pointer: + write_sql(config_db, "UPDATE assignments SET base_dir = ?, file_id_pointer = ? WHERE assignment_id = ?", + base_dir, pointer, agn_id) + else: + return f'The base directory is empty. Supported file types: {supported_file_types}.' return redirect('/') -@app.route('/config-rubric', methods=['POST']) -def config_rubric(): - config_name = request.form.get('config_name') - config = config_pull(config_name) - if not config: - return 'Config doesn\'t exist.' +@app.route('/select-base-directory', methods=['GET']) +def select_base_directory(): + base_dir = select_folder() + if base_dir: + return {'error': 0, 'base_dir': base_dir, 'message': ''} + else: + return {'error': 1, 'base_dir': '', 'message': 'Selected base directory doesn\'t exist.'} + + +@app.route('/rubric', methods=['GET']) +def rubric(): + agn_id = request.args.get('id') + agn_name = read_sql(config_db, "SELECT assignment_name FROM assignments WHERE assignment_id = ?", agn_id, one=True) + if not agn_name: + return 'Assignment doesn\'t exist.' + agn_name = agn_name[0] + rubric_json = read_sql(config_db, "SELECT rubric_json FROM rubric WHERE assignment_id = ?", agn_id, one=True) + if rubric_json: + rubric_json = json.loads(rubric_json[0]) + else: + rubric_json = [] + return render_template( + 'rubric.html', rubric=rubric_json, display_name=agn_name, + agn_id=agn_id, + ) + + +@app.route('/rubric-edit', methods=['POST']) +def rubric_edit(): + agn_id = request.form.get('agn_id') + rubric_json = [] try: - config['rubric'] = [] for caption in request.form.keys(): if caption.startswith('caption-'): q_id = caption.removeprefix('caption-') @@ -126,156 +134,173 @@ def config_rubric(): } question['assessment'].append(level) question['assessment'] = sorted(question['assessment'], key=lambda lv: lv['score'], reverse=True) - config['rubric'].append(question) + rubric_json.append(question) except (KeyError, ValueError, TypeError): return 'Submitted data is invalid.' - config_push(config_name, config) + rubric_json = json.dumps(rubric_json) + existed = read_sql(config_db, "SELECT * FROM rubric WHERE assignment_id = ?", agn_id) + if existed: + write_sql(config_db, "UPDATE rubric SET rubric_json = ? WHERE assignment_id = ?", rubric_json, agn_id) + else: + write_sql(config_db, "INSERT INTO rubric (assignment_id, rubric_json) VALUES (?, ?)", agn_id, rubric_json) return redirect('/') -@app.route('/config-fork', methods=['GET']) -def config_fork(): - config_name = request.args.get('config') - config = config_pull(config_name) - if not config: - return 'Config file does not exist.' - return render_template( - 'config.html', rubric=config.get('rubric'), display_name=config.get('name'), - config_name=config_name, - ) - - @app.route('/grade', methods=['GET']) def grade(): - config_name = request.args.get('config') - config = config_pull(config_name) - if not config: - return 'Config file does not exist.' - id_ = config.get('id') - filenames = config.get('filenames') - n = len(filenames) + agn_id = request.args.get('id') try: - scores = config['scores'] - filelist = [(filenames[i], str(i) in scores.keys()) for i in range(n)] - id_str = str(id_) - if id_str in scores.keys(): - raw_scores = config['scores'][id_str] - questions = config['rubric'] - scores = [] - for s, q in zip(raw_scores, questions): - levels = [m['score'] for m in q['assessment']] - scores.append((s, s in levels)) - else: - scores = [] - note = config['notes'].get(id_str, '') - except KeyError: - return 'Config file is invalid.' + title, file_id, base_dir = read_sql( + config_db, "SELECT assignment_name, file_id_pointer, base_dir FROM assignments WHERE assignment_id = ?", + agn_id, one=True + ) + except TypeError: + return 'Assignment doesn\'t exist.' + filelist = read_sql(config_db, check_files_scored, agn_id) + if not filelist: + return 'Base directory is empty.' + max_file_id = max(x[0] for x in filelist) + rubric_ = read_sql(config_db, "SELECT rubric_json FROM rubric WHERE assignment_id = ?", agn_id, one=True) + if rubric_: + rubric_ = json.loads(rubric_[0]) + else: + rubric_ = [] + score_comment = read_sql(config_db, + "SELECT score_list, comment FROM scores WHERE assignment_id = ? AND file_id = ?", + agn_id, file_id, one=True) + if score_comment: + raw_score, comment = json.loads(score_comment[0]), score_comment[1] + score = [] + for s, q in zip(raw_score, rubric_): + levels = [m['score'] for m in q['assessment']] + score.append((s, s in levels)) + else: + score, comment = [], '' return render_template( - 'grade.html', - title=config.get('name'), id=id_, config=config_name, rubric=config.get('rubric'), - current_score=scores, filelist=filelist, note=note, + 'grade.html', title=title, file_id=file_id, filelist=filelist, + rubric=rubric_, agn_id=agn_id, current_score=score, note=comment, n=max_file_id, ) @app.route('/grade-file', methods=['GET']) -def grade_file(): - config_name = request.args.get('config') - config = config_pull(config_name) - if not config: - return 'Config file does not exist.' - filenames = config.get('filenames') - base_dir = config.get('base_dir') - id_ = config.get('id') +def view_file(): + file_id = request.args.get('file_id') + agn_id = request.args.get('assignment_id') try: - current_file = filenames[id_] - current_file = os.path.abspath(os.path.join(base_dir, current_file)) - except (TypeError, IndexError): - return 'File does not exist.' - if os.path.isfile(current_file): - return send_file(current_file) - else: + base_dir, file_name = read_sql(config_db, "SELECT base_dir, file_name FROM assignments a JOIN files f " + "ON a.assignment_id = f.assignment_id WHERE file_id = ? AND " + "a.assignment_id = ?", file_id, agn_id, one=True) + file_path = os.path.abspath(os.path.join(base_dir, file_name)) + assert os.path.isfile(file_path) + except (TypeError, AssertionError): return 'File does not exist.' + return send_file(file_path) @app.route('/grade-jump', methods=['POST']) def grade_jump(): - config_name = request.form.get('config') - config = config_pull(config_name) - if not config: - return 'Config file does not exist.' + agn_id = request.form.get('agn_id') + file_id = request.form.get('file_id') + files = read_sql(config_db, "SELECT file_id FROM files WHERE assignment_id = ?", agn_id) + files = [x[0] for x in files] try: - config['id'] = int(request.form.get('id')) - except (TypeError, ValueError): - return 'Submitted data is invalid.' - config_push(config_name, config) - return redirect('/grade?config=' + config_name) + file_id = int(file_id) + except TypeError: + return 'Do not customize POST information. (File ID is not an integer.)' + if file_id not in files: + return 'File doesn\'t exist.' + write_sql(config_db, "UPDATE assignments SET file_id_pointer = ? WHERE assignment_id = ?", + file_id, agn_id) + return redirect(f'/grade?id={agn_id}') @app.route('/grade-update', methods=['POST']) def grade_submit(): - config_name = request.form.get('config') - config = config_pull(config_name) - if not config: - return 'Config file does not exist.' - id_str = request.form.get('student-id') + agn_id = read_sql( + config_db, "SELECT assignment_id FROM assignments WHERE assignment_id = ?", + request.form.get('agn_id'), one=True + ) + if not agn_id: + return 'Assignment doesn\'t exist.' + agn_id = agn_id[0] + file_id = read_sql( + config_db, "SELECT file_id FROM files WHERE file_id = ?", + request.form.get('student_id'), one=True + ) + if not file_id: + return 'File doesn\'t exist.' + file_id = file_id[0] action = request.form.get('action') + if action == 'Save and continue': + next_file_id = read_sql(config_db, "SELECT min(file_id) FROM files WHERE assignment_id = ? AND file_id > ?", + agn_id, file_id, one=True) + if next_file_id: + write_sql(config_db, "UPDATE assignments SET file_id_pointer = ? WHERE assignment_id = ?", + next_file_id[0], agn_id) + config = read_sql(config_db, 'SELECT rubric_json FROM rubric WHERE assignment_id = ?', agn_id, one=True) try: - id_ = int(id_str) - if action == 'Save and continue': - config['id'] = id_ + 1 - config['scores'][id_str] = [] - for q in config['rubric']: + config = json.loads(config[0]) + score = [] + for q in config: raw_score = request.form.get(q['q_id']) if raw_score == 'c': # customized - score = float(request.form.get(q['q_id'] + '-c')) + s = float(request.form.get(q['q_id'] + '-c')) else: - score = float(raw_score) - config['scores'][id_str].append(score) - note = request.form.get('note') - if note: - config['notes'][id_str] = note + s = float(raw_score) + score.append(s) + comment = request.form.get('note') + n_existed = read_sql(config_db, "SELECT count(*) FROM scores WHERE assignment_id = ? AND file_id = ?", + agn_id, file_id, one=True) + n_existed = n_existed[0] + if n_existed == 0: + write_sql(config_db, "INSERT INTO scores (assignment_id, file_id, score_list, comment) VALUES (?, ?, ?, ?)", + agn_id, file_id, json.dumps(score), comment) + else: + write_sql(config_db, "UPDATE scores SET score_list = ?, comment = ? WHERE assignment_id = ? AND file_id = ?", + json.dumps(score), comment, agn_id, file_id) except (TypeError, ValueError, KeyError): return 'Submitted data is invalid.' - config_push(config_name, config) - return redirect('/grade?config=' + config_name) + return redirect(f'/grade?id={agn_id}') @app.route('/analyze', methods=['GET']) def analyze(): - config_name = request.args.get('config') - config = config_pull(config_name) - if not config: - return 'Config file does not exist.' - columns = [q.get('caption', 'No caption') for q in config.get('rubric', [])] - try: - filenames, _ = zip(*map(os.path.splitext, config.get('filenames'))) - except ValueError: - return 'The directory is empty.' - scores = pd.DataFrame(index=filenames, columns=columns) - notes = pd.DataFrame(index=filenames, columns=['Note']) - for k, v in config.get('scores', {}).items(): - try: - row_id = int(k) % len(filenames) - except ValueError: - continue - scores.iloc[row_id, :] = v - for k, v in config.get('notes', {}).items(): - try: - row_id = int(k) % len(filenames) - except ValueError: - continue - notes['Note'][row_id] = v + agn_id = request.args.get('id') + assignment_name = read_sql(config_db, "SELECT assignment_name FROM assignments WHERE assignment_id = ?", + agn_id, one=True) + if not assignment_name: + return 'Assignment doesn\'t exist.' + assignment_name = assignment_name[0] + rubric_ = read_sql(config_db, "SELECT rubric_json FROM rubric WHERE assignment_id = ?", agn_id, one=True) + if not rubric_: + return 'Rubric doesn\'t exist.' + rubric_ = json.loads(rubric_[0]) + questions = [q.get('caption', 'No caption') for q in rubric_] + + c = sqlite3.connect(config_db) + raw_scores = pd.read_sql(con=c, sql="SELECT display_name, score_list, comment FROM files JOIN scores " + "ON files.file_id = scores.file_id") + c.close() + scores = pd.DataFrame( + data=raw_scores['score_list'].apply(json.loads).tolist(), + columns=questions + ) + scores.index = raw_scores['display_name'] + notes = raw_scores[['comment']].copy() + notes.index = raw_scores['display_name'] + notes.replace('', pd.NA, inplace=True) + notes.dropna(inplace=True) return render_template( 'analyze.html', - caption=config.get('name'), + caption=assignment_name, scores=scores.to_html( classes='table table-bordered table-striped', - float_format=lambda f: format(f, 'g'), - na_rep='', + float_format=lambda g: format(g, 'g'), + na_rep='', index_names=False, ), notes=notes.to_html( classes='table table-bordered table-striped', - na_rep='', + index_names=False, ), ) diff --git a/app.spec b/app.spec index 4a5be9e..d4519f4 100644 --- a/app.spec +++ b/app.spec @@ -7,7 +7,7 @@ block_cipher = None a = Analysis( ['app.py'], pathex=[], - binaries=[('templates', 'templates')], + binaries=[('templates', 'templates'), ('sql', 'sql')], datas=[], hiddenimports=[], hookspath=[], diff --git a/assets/image-20230531164128264.png b/assets/image-20230531164128264.png deleted file mode 100644 index 2c11e68..0000000 Binary files a/assets/image-20230531164128264.png and /dev/null differ diff --git a/assets/image-20230531164353459.png b/assets/image-20230531164353459.png deleted file mode 100644 index 84ae229..0000000 Binary files a/assets/image-20230531164353459.png and /dev/null differ diff --git a/assets/image-20230531165522028.png b/assets/image-20230531165522028.png deleted file mode 100644 index 8f75519..0000000 Binary files a/assets/image-20230531165522028.png and /dev/null differ diff --git a/assets/image-20230531165703612.png b/assets/image-20230531165703612.png deleted file mode 100644 index 52457c1..0000000 Binary files a/assets/image-20230531165703612.png and /dev/null differ diff --git a/assets/image-20230531165843515.png b/assets/image-20230531165843515.png deleted file mode 100644 index d8cd8c2..0000000 Binary files a/assets/image-20230531165843515.png and /dev/null differ diff --git a/assets/image-20230531170025106.png b/assets/image-20230531170025106.png deleted file mode 100644 index 2ce33e5..0000000 Binary files a/assets/image-20230531170025106.png and /dev/null differ diff --git a/assets/image-20230531170205660.png b/assets/image-20230531170205660.png deleted file mode 100644 index 9b9b84b..0000000 Binary files a/assets/image-20230531170205660.png and /dev/null differ diff --git a/assets/image-20230531170448762.png b/assets/image-20230531170448762.png deleted file mode 100644 index 5f9d75e..0000000 Binary files a/assets/image-20230531170448762.png and /dev/null differ diff --git a/assets/image-20230531170755053.png b/assets/image-20230531170755053.png deleted file mode 100644 index 88acdde..0000000 Binary files a/assets/image-20230531170755053.png and /dev/null differ diff --git a/assignments/qun_ml_presentation.json b/assignments/qun_ml_presentation.json deleted file mode 100644 index 2fbffa9..0000000 --- a/assignments/qun_ml_presentation.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "base_dir": "C:\\Users\\cloudy\\DataGripProjects\\QUN-ML-Scores\\presentation", - "name": "QUN ML Presentation", - "rubric": [ - { - "q_id": "1", - "caption": "\u95ee\u9898\u5b9a\u4e49", - "assessment": [ - { - "score": 5.0, - "definition": "\u8868\u8ff0\u4e86\u5bf9\u95ee\u9898\u7684\u7406\u89e3\uff0c\u5728\u9898\u76ee\u5185\u5bb9\u7684\u57fa\u7840\u4e0a\u4f5c\u51fa\u5ef6\u62d3\uff0c\u8865\u5145\u4e86\u5fc5\u8981\u7684\u6570\u636e\uff0c\u4f5c\u51fa\u5efa\u6a21\u5047\u8bbe\u3002" - }, - { - "score": 3.0, - "definition": "\u8868\u8ff0\u4e86\u5bf9\u95ee\u9898\u7684\u7406\u89e3\uff0c\u5728\u9898\u76ee\u5185\u5bb9\u7684\u57fa\u7840\u4e0a\u4f5c\u51fa\u5ef6\u62d3\uff0c\u4f46\u7f3a\u5c11\u6570\u636e\u6216\u5efa\u6a21\u5047\u8bbe\u3002" - }, - { - "score": 0.0, - "definition": "\u7b80\u5355\u91cd\u590d\u9898\u76ee\u5185\u5bb9\uff0c\u6ca1\u6709\u8868\u8fbe\u5efa\u6a21\u7406\u89e3\u3002" - } - ] - }, - { - "q_id": "2", - "caption": "\u7406\u8bba\u65b9\u6cd5", - "assessment": [ - { - "score": 10.0, - "definition": "\u80fd\u591f\u6839\u636e\u95ee\u9898\u7279\u5f81\uff0c\u79d1\u5b66\u5730\u9009\u62e9\u7406\u8bba\u65b9\u6cd5\uff0c\u5e76\u5f62\u6210\u89e3\u51b3\u95ee\u9898\u7684\u5b8c\u6574\u903b\u8f91\uff0c\u8868\u8fbe\u4e86\u81ea\u5df1\u7684\u5b66\u672f\u7406\u89e3\u3002" - }, - { - "score": 6.0, - "definition": "\u80fd\u591f\u5e94\u7528\u4e00\u4e9b\u65b9\u6cd5\u5206\u6790\u95ee\u9898\uff0c\u4f46\u6ca1\u6709\u5f62\u6210\u89e3\u51b3\u95ee\u9898\u7684\u5b8c\u6574\u903b\u8f91\u3002" - }, - { - "score": 2.0, - "definition": "\u5217\u4e3e\u7406\u8bba\u77e5\u8bc6\uff0c\u4e0d\u80fd\u4f53\u73b0\u7406\u89e3\u4e0e\u5e94\u7528\u3002" - } - ] - }, - { - "q_id": "3", - "caption": "\u8ba8\u8bba", - "assessment": [ - { - "score": 5.0, - "definition": "\u8ba8\u8bba\u4e86\u6240\u7528\u65b9\u6cd5\u7684\u95ee\u9898\u4e0e\u5c40\u9650\u6027\uff0c\u5e76\u63d0\u51fa\u53ef\u80fd\u7684\u6539\u8fdb\u65b9\u5411\uff0c\u6216\u6b63\u786e\u8bf4\u660e\u4e86\u65e0\u6cd5\u6539\u8fdb\u7684\u539f\u56e0\u3002" - }, - { - "score": 3.0, - "definition": "\u8ba8\u8bba\u4e86\u6240\u7528\u65b9\u6cd5\u7684\u95ee\u9898\u4e0e\u5c40\u9650\u6027\uff0c\u4f46\u6ca1\u6709\u4f5c\u51fa\u8bc1\u660e\u6216\u5c55\u793a\u5f97\u51fa\u7ed3\u8bba\u7684\u4f9d\u636e\u3002" - }, - { - "score": 0.0, - "definition": "\u6ca1\u6709\u5206\u6790\u6240\u7528\u65b9\u6cd5\u7684\u95ee\u9898\u4e0e\u5c40\u9650\u6027\u3002" - } - ] - }, - { - "q_id": "4", - "caption": "\u5e94\u7528\u5b9e\u4f8b", - "assessment": [ - { - "score": 10.0, - "definition": "\u5c55\u793a\u4e86\u6bcf\u4e2a\u95ee\u9898\u7684\u5efa\u6a21\u7ed3\u679c\uff0c\u4f7f\u7528\u7f8e\u89c2\u3001\u6b63\u786e\u7684\u56fe\u8868\u5c55\u793a\u6570\u636e\uff0c\u4f7f\u7528\u4e86\u5408\u9002\u7684\u6a21\u578b\u8bc4\u4ef7\u65b9\u6cd5\uff0c\u5f97\u51fa\u6709\u610f\u4e49\u7684\u7ed3\u8bba\u3002" - }, - { - "score": 6.0, - "definition": "\u5c55\u793a\u4e86\u6bcf\u4e2a\u95ee\u9898\u7684\u5efa\u6a21\u7ed3\u679c\uff0c\u4f7f\u7528\u56fe\u8868\u5c55\u793a\u6570\u636e\uff0c\u4f46\u6570\u636e\u53ef\u89c6\u5316\u7684\u65b9\u6cd5\u6709\u6b20\u7f3a\uff0c\u6216\u7f3a\u5c11\u6a21\u578b\u8bc4\u4ef7\u3002" - }, - { - "score": 2.0, - "definition": "\u5efa\u6a21\u7ed3\u679c\u4e0d\u5b8c\u6574\uff0c\u6216\u903b\u8f91\u51cc\u4e71\u3002" - } - ] - } - ], - "scores": { - "0": [ - 5.0, - 10.0, - 5.0, - 10.0 - ] - }, - "notes": {}, - "id": 0, - "filenames": [ - "2022360482.pdf", - "2022360501.pdf", - "2022360512.pdf", - "2022360522.pdf", - "2022360524.pdf", - "2022360533.pdf", - "2022360548.pdf", - "2022360558.pdf", - "2022360577.pdf", - "2022360581.pdf" - ] -} \ No newline at end of file diff --git a/sql/check_files_scored.sql b/sql/check_files_scored.sql new file mode 100644 index 0000000..bf5c8e3 --- /dev/null +++ b/sql/check_files_scored.sql @@ -0,0 +1,4 @@ +select files.file_id, display_name, score_list is not null +from files left join scores +on files.file_id = scores.file_id and files.assignment_id = scores.assignment_id +where files.assignment_id = ? \ No newline at end of file diff --git a/sql/initialize.sql b/sql/initialize.sql new file mode 100644 index 0000000..df00897 --- /dev/null +++ b/sql/initialize.sql @@ -0,0 +1,31 @@ +create table assignments +( + assignment_id integer + primary key autoincrement, + assignment_name text, + base_dir text, + file_id_pointer integer +); + +create table files +( + file_id integer + primary key autoincrement, + file_name text, + display_name text, + assignment_id integer +); + +create table rubric +( + assignment_id integer, + rubric_json text +); + +create table scores +( + assignment_id integer, + file_id integer, + score_list text, + comment text +); \ No newline at end of file diff --git a/templates/analyze.html b/templates/analyze.html index c370005..9336cc5 100644 --- a/templates/analyze.html +++ b/templates/analyze.html @@ -10,11 +10,11 @@
{{ caption }}
+Scores
{{ scores | safe }} -Notes
+Comments
{{ notes | safe }}